From fd736a88d8895ee3955abcd5da010b18cac8e7ad Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Mon, 13 May 2024 12:05:24 +0800 Subject: [PATCH 01/14] add github superlinter and auto release job --- .github/workflows/lint.yml | 36 ++++++++++++++ .github/workflows/release.yml | 93 +++++++++++++++++++++++++++++++++++ README.md | 46 +++++++++++------ README_hans.md | 47 ++++++++++++------ 4 files changed, 190 insertions(+), 32 deletions(-) create mode 100755 .github/workflows/lint.yml create mode 100755 .github/workflows/release.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100755 index 00000000..ee9ec1f3 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,36 @@ +name: Lint + +on: + push: + branches: + - master + pull_request: + branches: + - master + +permissions: { } + +jobs: + build: + name: Lint + runs-on: ubuntu-latest + + permissions: + contents: read + packages: read + # To report GitHub Actions status checks + statuses: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + # super-linter needs the full git history to get the + # list of files that changed across commits + fetch-depth: 0 + + - name: Super-linter + uses: super-linter/super-linter@v6.4.1 # x-release-please-version + env: + # To report GitHub Actions status checks + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100755 index 00000000..7eef1f9d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,93 @@ +name: Release + +on: + release: + types: [released] + +defaults: + run: + shell: bash + +jobs: + generate_zip: + name: Generate release zip + runs-on: [ubuntu-latest] + timeout-minutes: 60 + steps: + - name: Checkout Source Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + clean: true + + - name: Show Output Directory For Debug Purpose + run: | + apt update && apt install zip || exit 1 + echo “show runner hostname” + hostname + echo “show runner user” + whoami + echo “show runner pwd” + pwd + echo “show runner kernel” + uname -a + echo “show runner pwd file list” + ls + ls -alht custom_components/midea_ac_lan/ || exit 1 + echo "show manifest.json for debug" + cat custom_components/midea_ac_lan/manifest.json || exit 1 + sudo mkdir -p /github/workspace/artifacts || exit 1 + sudo chown -R $(id -u):$(id -g) /github/workspace/artifacts || exit 1 + sudo chmod -R 755 /github/workspace/artifacts || exit 1 + cd custom_components/midea_ac_lan/ || exit 1 + zip -r ../midea_ac_lan.zip ./* || exit 1 + cp ../midea_ac_lan.zip /github/workspace/artifacts/midea_ac_lan.zip || exit 1 + ls -alht /github/workspace/artifacts/ || exit 1 + shell: bash + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: midea_ac_lan.zip + path: /github/workspace/artifacts + + upload_release: + needs: generate_zip + name: Publish release + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + runs-on: ubuntu-latest + steps: + + - name: Download All Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + pattern: midea_ac_lan.zip + merge-multiple: true + + - name: Display Structure of Artifacts Files + run: ls -R + + - name: Show Working Directory For Debug Purpose + run: | + echo “show runner hostname” + hostname + echo “show runner user” + whoami + echo “show runner pwd” + pwd + echo “show runner kernel” + uname -a + echo “show runner pwd file list” + ls + ls -alht + echo “show runner artifacts” + ls -alht artifacts || exit 0 + echo "github.ref: ${{github.ref}} " + + - name: Upload images to Release Asset + uses: softprops/action-gh-release@v2 + with: + files: artifacts/midea_ac_lan.zip + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index a7349c75..40b5449a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Midea AC LAN + [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) [![Donate](https://img.shields.io/badge/donate-BuyMeCoffee-yellow.svg)](https://www.buymeacoffee.com/georgezhao2010) [![Stable](https://img.shields.io/github/v/release/CyrielRct/midea_ac_lan)](https://github.com/CyrielRct/midea_ac_lan/releases/latest) +[![Super-Linter](https://github.com///actions/workflows//badge.svg)](https://github.com/marketplace/actions/super-linter) -> :warning: **This is a fork of Midea_ac_lan done by Georgezhao **: As the project is in a vegetative state we have done a fork and merged some pending fixes. +> :warning: **This is a fork of midea_ac_lan done by Georgezhao**: As the project is in a vegetative state we have done a fork and merged some pending fixes. I'm trying to get in touch with the maintainer at the moment to find a solution, and at the same time we're looking for people available to maintain the project, which is actively used by the community. please contact me if you can help ! English | [简体中文](README_hans.md) @@ -22,13 +24,13 @@ Thanks also to [@NeoAcheron](https://github.com/NeoAcheron/midea-ac-py). ***❗Note: Home Assistant 2023.1 or higher required for this integration*** -# Supported brands +## Supported brands ![ariston](brands/ariston.png) ![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) And more. -# Supported appliances +## Supported appliances | Type | Name | Documents | |------|----------------------------|--------------------| @@ -67,12 +69,14 @@ And more. | FC | Air Purifier | [FC.md](doc/FC.md) | | FD | Humidifier | [FD.md](doc/FD.md) | -# Installation -Search 'Midea AC LAN' in HACS and install, or copy all files in `custom_components/midea_ac_lan` from [Latest Release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest) to your `/custom_components/midea_ac_lan` in Home Assistant manually. +## Installation + +Search 'Midea AC LAN' in HACS and install, or copy all files in `custom_components/midea_ac_lan` from [Latest Release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest) to your `/custom_components/midea_ac_lan` in Home Assistant manually. Restart Home Assistant. -# Add device +## Add device + ***❗Note: First, set a static IP address for your appliance in the router, in case the IP address of the appliance changes after set-up.*** After installation, search and add component Midea AC LAN in Home Assistant integrations page. @@ -83,15 +87,18 @@ Or click [![Configuration](https://my.home-assistant.io/badges/config_flow_start After the account is configured, Click 'ADD DEVICE' once more to add new device. You could repeat the above action to add multiple devices. -## Discover automatically +### Discover automatically + Using this option, the component could auto-discover and list Midea M-Smart appliances in network or specified IP address, select one and add it in. You can also use an IP address to search within a specified network, such as `192.168.1.255`. ***❗Note: Discovery automatically requires your appliances and your Home Assistant must be in the same sub-network. Otherwise, devices may not be auto-discovered. Check this by yourself.*** -## Configure manually +### Configure manually + If you already know following information, you could add the appliance manually. + - Appliance code - Appliance type (one of [Supported appliances](README.md#supported-appliances)) - IP address @@ -100,30 +107,35 @@ If you already know following information, you could add the appliance manually. - Token - Key -## List all appliances only +### List all appliances only + Using this option, you can list all discoverable Midea M-Smart devices on the network, along with their IDs, types, SNs, and other information. ***❗Note: For certain reasons, not all supported devices may be listed here.*** -# Configure +## Configure Configure can be found in `Settings -> Devices & Services -> Midea AC LAN -> Devices -> CONFIGURE`. You can re-set the IP address when device IP changed. You can also add extra sensor and switch entities or customize your own device. -## IP address +### IP address + Set the IP address of device. You can reset this when your device IP is changed. -## Refresh interval +### Refresh interval + Set the interval for actively refreshing the status of a single device (the unit is second) (30 by default and 0 means not refresh actively) Mostly the status update of Midea devices relies on the active information notification of the device, in which condition the status update in HA still works normally even if the refresh interval is set to be “0”. This component will also actively query the device status at regular intervals, and the default time is 30 seconds. Some devices do not have active information notifications when their status changed, so synchronization with the status in HA will be slower. If you are very concerned about the synchronization speed of the status, you can try to set a shorter status refresh interval. ***❗Note: shorter refresh interval may mean more power consumption*** -## Extra sensor and switch entities +### Extra sensor and switch entities + After configuration, one of few main entity (e.g. climate entity) may be generated . If you want to make the attributes to extra sensor and switch entities, click CONFIGURE in Midea AC LAN integration card to choose (if your devices supported). -## Customize +### Customize + Some types of device have their own configuration items, if your device does not work properly, you may need to customize it. Refer to the device documentation for specific information. The format of customizations must be JSON. @@ -131,13 +143,15 @@ The format of customizations must be JSON. If multiple customization items need to be configured, the settings must comply with the JSON format. Example + ```json {"refresh_interval": 15, "fan_speed": 100} ``` -# Debug +## Debug Turn on the debug log out,config in configuration.yaml + ```yaml logger: default: warn @@ -145,7 +159,7 @@ logger: custom_components.midea_ac_lan: debug ``` -# Support my works +## Support my works If you like this integration, why do not you support my works by buying me a coffee? diff --git a/README_hans.md b/README_hans.md index 3af0d757..54b335c6 100644 --- a/README_hans.md +++ b/README_hans.md @@ -1,4 +1,5 @@ # Midea AC LAN + [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) [![Donate](https://img.shields.io/badge/donate-BuyMeCoffee-yellow.svg)](https://www.buymeacoffee.com/georgezhao2010) [![Stable](https://img.shields.io/github/v/release/CyrielRct/midea_ac_lan)](https://github.com/CyrielRct/midea_ac_lan/releases/latest) @@ -19,13 +20,13 @@ ***❗注意: 本集成需要Home Assistant 2023.1或更高版本*** -# 已支持的品牌 +## 已支持的品牌 -![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) +![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) 以及更多。 -# 已支持的设备 +## 已支持的设备 | 类型 | 名称 | 文档 | |----|-------------------|------------------------------| @@ -64,12 +65,14 @@ | FC | 空气净化器 | [FC_hans.md](doc/FC_hans.md) | | FD | 加湿器 | [FD_hans.md](doc/FD_hans.md) | -# 安装 +## 安装 + 在HACS中搜索'Midea AC LAN'并安装, 或者从[Latest release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest)下载最新的Release版本, 将其中的`custom_components/midea_ac_lan`放到你的Home Assistant的`custom_components/midea_ac_lan`中。 重启Home Assistant -# 添加设备 +## 添加设备 + ***❗注意: 首先, 在路由器上为你的设备设置一个静态IP地址, 以防设置后设备的IP地址发生改变。*** 安装之后, 在Home Assistant的集成界面搜索添加集成Midea AC LAN, 如果需要添加多台设备, 多次添加本集成并执行自动配置即可。 @@ -80,15 +83,18 @@ 完成美的账户配置之后, 点击'添加设备'进行设备添加。你可以多次重复操作以添加多台设备。 -## Discover automatically / 自动搜索 +### Discover automatically / 自动搜索 + 使用此选项, 组件会列出网络上或者指定IP地址上的设备, 选择一个并进行添加。 你也可以使用IP地址在指定网络中搜索, 比如`192.168.1.255` ***❗注意: 自动配置要求设备必须与HA在同一网段, 否则可能搜索不到设备, 请自行确认这点*** -## Configure manually / 手动配置 +### Configure manually / 手动配置 + 如果之前你已经通过其它集成手工配置过设备, 并知道以下信息, 也可以进行手动配置 + - 设备ID - 设备类型 ([已支持的设备](README_hans.md#%E5%B7%B2%E6%94%AF%E6%8C%81%E7%9A%84%E8%AE%BE%E5%A4%87)之一) - IP地址 @@ -97,20 +103,23 @@ - Token - Key -## List all appliances only / 仅列出所有设备 +### List all appliances only / 仅列出所有设备 使用此选择, 可以列出网络中所有可以被搜索到的美的M-Smart设备, 以及他们的ID, 类型, SN等信息 ***❗注意: 出于某些原因, 可能不是所有受支持的设备都能于此列出*** -# 配置 +## 配置 + 集成配置位于`配置 -> 设备与服务 -> Midea AC LAN -> 设备 -> 选项`。 在配置中, 你可以在设备IP改变后重新指定IP地址, 也可以增加扩展的传感器或开关等实体或者自定义你的设备 -## IP地址 +### IP地址 + 指定设备的IP地址。当你的设备IP地址变动后, 可以重新设定它 -## 刷新间隔 +### 刷新间隔 + 指定单台设备的主动状态刷新间隔 (单位为秒) (默认值为30, 设0代表不进行主动刷新) 大部分的美的设备在自身状态改变时会主动发送通知, 在这种情况下, 即使将改值设为0, 也不会影响HA中的设备状态更新。组件默认每间隔30秒会进行主动刷新。部分设备没有状态改变时主动通知的机制, 状态同步就会表现得很慢, 这种情况下可以尝试更短的主动状态更新间隔。 @@ -118,22 +127,27 @@ ***❗注意: 更小的更新间隔意味着更多的能源消耗*** ## 额外的传感器及开关实体 + 配置完成后, 可能会默认生成一个或几个主要实体(比如climate实体)。如果需要其它属性生成为扩展的传感器及开关实体, 在Midea AC LAN集成卡片上点击'选项', 并选择要生成的传感器及开关(如果你的设备支持该属性)。 -## 自定义 -某些类型的设备有它们自己的自定义项, 可以通过自定义项定制设备的特点。如果你的设备无法正常工作, 也许你需要自定义它。请参阅设备文档以获取具体信息。 +### 自定义 + +某些类型的设备有它们自己的自定义项, 可以通过自定义项定制设备的特点。如果你的设备无法正常工作, 也许你需要自定义它。请参阅设备文档以获取具体信息。 自定义的格式必须是JSON。 如果要设置多个自定义项, 请注意遵循JSON的格式要求 示例 + ```json {"refresh_interval": 15, "fan_speed": 100} ``` -# 调试 +## 调试 + 要打开调试日志输出, 在configuration.yaml中做如下配置 + ```yaml logger: default: warn @@ -141,7 +155,8 @@ logger: custom_components.midea_ac_lan: debug ``` -# 支持我的工作 +## 支持我的工作 + 如果喜欢这个集成, 可以使用支付宝或者微信支付赞助我来支持我的工作。 -![alipay](doc/images/alipay.png) ![wechatpay](doc/images/wechatpay.png) \ No newline at end of file +![alipay](doc/images/alipay.png) ![wechatpay](doc/images/wechatpay.png) From 1e65adad79b4fa5dfac394df2f0e7cafb6de00b4 Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Mon, 13 May 2024 12:29:36 +0800 Subject: [PATCH 02/14] rename and upadte linter --- .github/pull_request_template.md | 16 ++++++++++++++++ .github/workflows/{lint.yml => linter.yml} | 4 ++-- .github/workflows/release.yml | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 .github/pull_request_template.md rename .github/workflows/{lint.yml => linter.yml} (91%) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..4727beff --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +## PR Brief Description: + + +## Changes Detail or Reason: + + +## Releted issue: + +fix #X + + diff --git a/.github/workflows/lint.yml b/.github/workflows/linter.yml similarity index 91% rename from .github/workflows/lint.yml rename to .github/workflows/linter.yml index ee9ec1f3..900d7248 100755 --- a/.github/workflows/lint.yml +++ b/.github/workflows/linter.yml @@ -1,4 +1,4 @@ -name: Lint +name: Linter on: push: @@ -33,4 +33,4 @@ jobs: uses: super-linter/super-linter@v6.4.1 # x-release-please-version env: # To report GitHub Actions status checks - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7eef1f9d..6d706a14 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,4 +90,4 @@ jobs: with: files: artifacts/midea_ac_lan.zip env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b5b49988cf6d8804fb6419a268f7a5424fef3f75 Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Mon, 13 May 2024 13:33:01 +0800 Subject: [PATCH 03/14] update based on super linter error --- .github/ISSUE_TEMPLATE/1-issue_zh-cn.yml | 5 +++-- .github/ISSUE_TEMPLATE/2-issue_en.yml | 1 + .../ISSUE_TEMPLATE/3-enhancement_zh-cn.yml | 1 + .github/ISSUE_TEMPLATE/4-enhancement_en.yml | 1 + .github/ISSUE_TEMPLATE/config.yml | 1 + .github/pull_request_template.md | 10 ++++------ .github/workflows/codeql.yml | 3 +++ .github/workflows/linter.yml | 1 + .github/workflows/release.yml | 5 ++++- .github/workflows/validate.yml | 19 ++++++++++--------- .../midea_ac_lan/midea/core/cloud.py | 6 +++--- doc/AC.md | 14 ++++++++++---- doc/C3.md | 11 ++++++++--- doc/C3_hans.md | 9 +++++++-- doc/E2.md | 10 +++++++--- doc/E3.md | 11 +++++++---- doc/E3_hans.md | 9 +++++++-- doc/E6.md | 6 ++++-- doc/E6_hans.md | 5 ++++- doc/E8.md | 6 ++++-- doc/E8_hans.md | 6 ++++-- doc/EA.md | 5 ++++- doc/EA_hans.md | 5 ++++- doc/EC.md | 5 ++++- doc/EC_hans.md | 5 ++++- doc/ED.md | 5 ++++- doc/ED_hans.md | 5 ++++- doc/FA.md | 7 ++++++- doc/FA_hans.md | 7 ++++++- doc/FB.md | 6 +++++- doc/FB_hans.md | 6 +++++- doc/FC.md | 6 ++++-- doc/FC_hans.md | 4 +++- doc/FD.md | 8 ++++++-- doc/FD_hans.md | 7 ++++++- 35 files changed, 159 insertions(+), 62 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/1-issue_zh-cn.yml b/.github/ISSUE_TEMPLATE/1-issue_zh-cn.yml index 2fddbef9..bef73140 100644 --- a/.github/ISSUE_TEMPLATE/1-issue_zh-cn.yml +++ b/.github/ISSUE_TEMPLATE/1-issue_zh-cn.yml @@ -1,3 +1,4 @@ +--- name: 错误报告 description: 集成或实体无法正常工作 labels: ["bug"] @@ -5,13 +6,13 @@ body: - type: input attributes: label: HA版本 - placeholder: 2023.4.2 + placeholder: 2024.5.3 - type: input validations: required: true attributes: label: 集成版本 - placeholder: 0.3.18 + placeholder: 0.3.23 - type: input validations: required: true diff --git a/.github/ISSUE_TEMPLATE/2-issue_en.yml b/.github/ISSUE_TEMPLATE/2-issue_en.yml index 2c6bde0f..dc43c48c 100644 --- a/.github/ISSUE_TEMPLATE/2-issue_en.yml +++ b/.github/ISSUE_TEMPLATE/2-issue_en.yml @@ -1,3 +1,4 @@ +--- name: Bug report description: Intergration or entity is not working right labels: ["bug"] diff --git a/.github/ISSUE_TEMPLATE/3-enhancement_zh-cn.yml b/.github/ISSUE_TEMPLATE/3-enhancement_zh-cn.yml index e52552a1..a1c2fc27 100644 --- a/.github/ISSUE_TEMPLATE/3-enhancement_zh-cn.yml +++ b/.github/ISSUE_TEMPLATE/3-enhancement_zh-cn.yml @@ -1,3 +1,4 @@ +--- name: 新功能请求 description: 请求增加新设备支持或请求为已有设备增加新的功能 labels: ["enhancement"] diff --git a/.github/ISSUE_TEMPLATE/4-enhancement_en.yml b/.github/ISSUE_TEMPLATE/4-enhancement_en.yml index 0cd2a3e3..b30f84cb 100644 --- a/.github/ISSUE_TEMPLATE/4-enhancement_en.yml +++ b/.github/ISSUE_TEMPLATE/4-enhancement_en.yml @@ -1,3 +1,4 @@ +--- name: Enhancement description: New feature or new device support request labels: ["enhancement"] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a49eab2f..c96a9ab2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,2 @@ +--- blank_issues_enabled: true \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4727beff..448d0a6d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,16 +1,14 @@ -## PR Brief Description: +# PR Brief Description +## Reason & Detail -## Changes Detail or Reason: - - -## Releted issue: +## Releted issue fix #X diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 98eb3686..babce74d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,3 +1,4 @@ +--- name: "CodeQL" on: @@ -8,6 +9,8 @@ on: # schedule: # - cron: "42 8 * * 6" +permissions: { } + jobs: analyze: name: Analyze diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 900d7248..d2b00af8 100755 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,3 +1,4 @@ +--- name: Linter on: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d706a14..d3d74650 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,9 +1,12 @@ +--- name: Release on: release: types: [released] +permissions: { } + defaults: run: shell: bash @@ -90,4 +93,4 @@ jobs: with: files: artifacts/midea_ac_lan.zip env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 37d63485..00613ca0 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1,23 +1,24 @@ - +--- name: HACS Validation - on: pull_request: push: # schedule: # - cron: "0 0 * * *" +permissions: { } + jobs: validate: name: HASS and Hassfest Validation runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v4" + - uses: "actions/checkout@v4" - - name: HACS Action - uses: hacs/action@main - with: - category: "integration" + - name: HACS Action + uses: hacs/action@main + with: + category: "integration" - - name: Hassfest validation - uses: "home-assistant/actions/hassfest@master" + - name: Hassfest validation + uses: "home-assistant/actions/hassfest@master" diff --git a/custom_components/midea_ac_lan/midea/core/cloud.py b/custom_components/midea_ac_lan/midea/core/cloud.py index e92d55c2..fe29427a 100644 --- a/custom_components/midea_ac_lan/midea/core/cloud.py +++ b/custom_components/midea_ac_lan/midea/core/cloud.py @@ -280,7 +280,7 @@ async def list_appliances(self, home_id) -> dict | None: "sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "", "sn8": appliance.get("sn8", "00000000"), "model_number": model_number, - "manufacturer_code":appliance.get("enterpriseCode", "0000"), + "manufacturer_code": appliance.get("enterpriseCode", "0000"), "model": appliance.get("productModel"), "online": appliance.get("onlineStatus") == "1", } @@ -464,7 +464,7 @@ async def list_appliances(self, home_id) -> dict | None: "sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "", "sn8": "", "model_number": model_number, - "manufacturer_code":appliance.get("enterpriseCode", "0000"), + "manufacturer_code": appliance.get("enterpriseCode", "0000"), "model": "", "online": appliance.get("onlineStatus") == "1", } @@ -624,7 +624,7 @@ async def list_appliances(self, home_id) -> dict | None: "sn": appliance.get("sn"), "sn8": "", "model_number": model_number, - "manufacturer_code":appliance.get("enterpriseCode", "0000"), + "manufacturer_code": appliance.get("enterpriseCode", "0000"), "model": "", "online": appliance.get("onlineStatus") == "1", } diff --git a/doc/AC.md b/doc/AC.md index d3bf130c..2d17a630 100644 --- a/doc/AC.md +++ b/doc/AC.md @@ -1,5 +1,7 @@ # Air Conditioner + ## Features + - Supports target temperature - Supports run mode - Supports fan mode @@ -8,6 +10,7 @@ - Supports build-in fresh air system ### Supported Run-Modes + - Comfort Mode - ECO Mode - Boost Mode @@ -22,16 +25,18 @@ - Power consumption analysis method (1 by default) - There are 3 different methods to analyze the power consumption of an AC, but I don’t know which is right for your device. - If the power consumption data looks incorrect, try another method and see if they are correct. + There are 3 different methods to analyze the power consumption of an AC, but I don’t know which is right for your device. + If the power consumption data looks incorrect, try another method and see if they are correct. The options include 1, 2, and 3. - + ```json {"power_analysis_method": 2} ``` ## Entities + ### Default entity + | EntityID | Class | Description | |----------------------------|---------|----------------| | climate.{DEVICEID}_climate | climate | Climate entity | @@ -89,6 +94,7 @@ Set the attribute of appliance. Service data: | value | Range 1 to 100 or "auto" | Example + ```yaml service: midea_ac_lan.set_attribute data: @@ -103,4 +109,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: auto -``` \ No newline at end of file +``` diff --git a/doc/C3.md b/doc/C3.md index 03322a69..9934854b 100644 --- a/doc/C3.md +++ b/doc/C3.md @@ -1,10 +1,14 @@ # Heat Pump Wi-Fi Controller + ## Features + - Supports target temperature - Supports run mode ## Entities + ### Default entity + | EntityID | Class | Description | |--------------------------------------|--------------|----------------------| | climate.{DEVICEID}_climate_zone1 | climate | Zone1 climate entity | @@ -20,7 +24,7 @@ | binary_sensor.{DEVICEID}_zone1_room_temp_mode | binary_sensor | Zone1 Room Temperature Mode | | binary_sensor.{DEVICEID}_zone2_room_temp_mode | binary_sensor | Zone2 Room Temperature Mode | | binary_sensor.{DEVICEID}_status_dhw | binary_sensor | DHW Status | -| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status |**** +| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status | | binary_sensor.{DEVICEID}_status_ibh | binary_sensor | IBH Status | | binary_sensor.{DEVICEID}_status_heating | binary_sensor | Heating Status | | sensor.{DEVICEID}_error_code | sensor | Error Code | @@ -33,7 +37,7 @@ | switch.{DEVICEID}_eco_mode | switch | ECO Mode | | switch.{DEVICEID}_fast_dhw | switch | Fast DHW | | switch.{DEVICEID}_silent_mode | switch | Silent Mode | -| switch.{DEVICEID}_tbh | switch | TBH | +| switch.{DEVICEID}_tbh | switch | TBH | | switch.{DEVICEID}_zone1_curve | switch | Zone1 Curve | | switch.{DEVICEID}_zone2_curve | switch | Zone2 Curve | | switch.{DEVICEID}_zone1_power | switch | Zone1 Power | @@ -54,10 +58,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: zone1_curve value: true -``` \ No newline at end of file +``` diff --git a/doc/C3_hans.md b/doc/C3_hans.md index 792b16de..53503c28 100644 --- a/doc/C3_hans.md +++ b/doc/C3_hans.md @@ -1,10 +1,14 @@ # 热泵空调Wi-Fi线控器 + ## 特性 + - 支持目标温度设定 - 支持运行模式设定 ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |--------------------------------------|--------------|----------| | climate.{DEVICEID}_climate_zone1 | climate | 区域1恒温器实体 | @@ -20,7 +24,7 @@ | binary_sensor.{DEVICEID}_zone1_room_temp_mode | binary_sensor | Zone1 Room Temperature Mode | 区域1室温模式 | | binary_sensor.{DEVICEID}_zone2_room_temp_mode | binary_sensor | Zone2 Room Temperature Mode | 区域2室温模式 | | binary_sensor.{DEVICEID}_status_dhw | binary_sensor | DHW Status | DHW状态 | -| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status | TBH状态 | +| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status | TBH状态 | | binary_sensor.{DEVICEID}_status_ibh | binary_sensor | IBH Status | IBH状态 | | binary_sensor.{DEVICEID}_status_heating | binary_sensor | Heating Status | 加热状态 | | sensor.{DEVICEID}_error_code | sensor | Error Code | 错误码 | @@ -54,10 +58,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: zone1_curve value: true -``` \ No newline at end of file +``` diff --git a/doc/E2.md b/doc/E2.md index 0f0ed336..b40c6a18 100644 --- a/doc/E2.md +++ b/doc/E2.md @@ -1,9 +1,11 @@ # Electric Water Heater + ## Features -- Supports target temperature +- Supports target temperature ## Customize + - Set the protocol of device ("auto" by default). There are 2 different protocol version to control Electric Water Heater, "old protocol" or "new protocol". If you can't control your device, try change this item and see if it works. @@ -14,7 +16,9 @@ The options include true, false, and "auto". ``` ## Entities + ### Default entity + | EntityID | Class | Description | |--------------------------------------|--------------|---------------------| | water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | @@ -35,7 +39,6 @@ The options include true, false, and "auto". ## Services - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -49,10 +52,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: variable_heating value: true -``` \ No newline at end of file +``` diff --git a/doc/E3.md b/doc/E3.md index 58f09778..a732db42 100644 --- a/doc/E3.md +++ b/doc/E3.md @@ -1,12 +1,13 @@ # Gas Water Heater + ## Features -- Supports target temperature +- Supports target temperature ## Customize - Set the temperature precision for whole or halves (false for whole and true for halves, default by false) - + If the temperature value displayed on your water heater is twice the actual value, please set this value to true. ```json @@ -14,7 +15,9 @@ ``` ## Entities + ### Default entity + | EntityID | Class | Description | |--------------------------------------|--------------|---------------------| | water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | @@ -33,7 +36,6 @@ ## Services - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -47,10 +49,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: smart_volume value: true -``` \ No newline at end of file +``` diff --git a/doc/E3_hans.md b/doc/E3_hans.md index 97b2566a..dcab3919 100644 --- a/doc/E3_hans.md +++ b/doc/E3_hans.md @@ -1,11 +1,13 @@ # 燃气热水器 + ## 特性 + - 支持温度设定 ## 自定义 - 设置热水器温度基数为整度还是半度 (false 为整度 true 为半度, 默认为 false) - + 如果你的热水器显示的温度为实际温度的两倍,请将该值设为true。 ```json @@ -13,7 +15,9 @@ ``` ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |--------------------------------------|--------------|-------| | water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | @@ -45,10 +49,11 @@ | value | true or false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: smart_volume value: true -``` \ No newline at end of file +``` diff --git a/doc/E6.md b/doc/E6.md index 677f32f0..f54964e9 100644 --- a/doc/E6.md +++ b/doc/E6.md @@ -1,7 +1,9 @@ # Gas Boilers ## Entities + ### Default entity + | EntityID | Class | Description | |----------------------------------------------|--------------|-----------------------------| | water_heater.{DEVICEID}_water_heater_heating | water_heater | Heating water heater entity | @@ -20,7 +22,6 @@ ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -34,10 +35,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: main_power value: true -``` \ No newline at end of file +``` diff --git a/doc/E6_hans.md b/doc/E6_hans.md index 1f3ca6a8..6d481ab8 100644 --- a/doc/E6_hans.md +++ b/doc/E6_hans.md @@ -1,7 +1,9 @@ # 壁挂炉 ## 生成实体 + ### 默认实体 + | 实体ID | 类型 | 描述 | |----------------------------------------------|--------------|---------| | water_heater.{DEVICEID}_water_heater_heating | water_heater | 取暖热水器实体 | @@ -33,10 +35,11 @@ | value | true or false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: main_power value: true -``` \ No newline at end of file +``` diff --git a/doc/E8.md b/doc/E8.md index 0af848e3..e2c75c6b 100644 --- a/doc/E8.md +++ b/doc/E8.md @@ -1,7 +1,9 @@ # Electric Slow Cooker ## Entities + ### Default entity + No default entity. ### Extra entities @@ -17,6 +19,6 @@ No default entity. | sensor.{DEVICEID}_target_temperature | sensor | Target Temperature | | sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | - ## Service -No services. \ No newline at end of file + +No services. diff --git a/doc/E8_hans.md b/doc/E8_hans.md index 05755269..605603c3 100644 --- a/doc/E8_hans.md +++ b/doc/E8_hans.md @@ -1,7 +1,9 @@ # 慢炖锅 ## 实体 + ### 默认实体 + 无默认实体 ### 扩展实体 @@ -17,6 +19,6 @@ | sensor.{DEVICEID}_target_temperature | sensor | Target Temperature | 设定温度 | | sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | - ## 服务 -无服务 \ No newline at end of file + +无服务 diff --git a/doc/EA.md b/doc/EA.md index 947aff00..c399f3e3 100644 --- a/doc/EA.md +++ b/doc/EA.md @@ -1,7 +1,9 @@ # Electric Rice Cooker ## Entities + ### Default entity + No default entity. ### Extra entities @@ -18,4 +20,5 @@ No default entity. | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | ## Service -No services. \ No newline at end of file + +No services. diff --git a/doc/EA_hans.md b/doc/EA_hans.md index 5475a332..dbb1a0c3 100644 --- a/doc/EA_hans.md +++ b/doc/EA_hans.md @@ -1,7 +1,9 @@ # 电饭煲 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -18,4 +20,5 @@ | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | ## 服务 -无服务 \ No newline at end of file + +无服务 diff --git a/doc/EC.md b/doc/EC.md index 8fbec05e..6a383310 100644 --- a/doc/EC.md +++ b/doc/EC.md @@ -1,7 +1,9 @@ # Electric Pressure Cooker ## Entities + ### Default entity + No default entity. ### Extra entities @@ -18,4 +20,5 @@ No default entity. | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | ## Service -No services. \ No newline at end of file + +No services. diff --git a/doc/EC_hans.md b/doc/EC_hans.md index d4776336..22fba03b 100644 --- a/doc/EC_hans.md +++ b/doc/EC_hans.md @@ -1,7 +1,9 @@ # 电压力锅 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -18,4 +20,5 @@ | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | ## 服务 -无服务 \ No newline at end of file + +无服务 diff --git a/doc/ED.md b/doc/ED.md index 1446afb2..6301bfa8 100644 --- a/doc/ED.md +++ b/doc/ED.md @@ -1,7 +1,9 @@ # Water Drinking Appliance ## Entities + ### Default entity + No default entity. ### Extra entities @@ -21,4 +23,5 @@ No default entity. | switch.{DEVICEID}_power | switch | Power | ## Service -No services. \ No newline at end of file + +No services. diff --git a/doc/ED_hans.md b/doc/ED_hans.md index 2eeda605..a3a1d038 100644 --- a/doc/ED_hans.md +++ b/doc/ED_hans.md @@ -1,7 +1,9 @@ # 饮用水设备 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -21,4 +23,5 @@ | switch.{DEVICEID}_power | switch | Power | 电源开关 | ## 服务 -无服务 \ No newline at end of file + +无服务 diff --git a/doc/FA.md b/doc/FA.md index 6f7adc09..93ba2427 100644 --- a/doc/FA.md +++ b/doc/FA.md @@ -1,5 +1,7 @@ # Fan + ## Features + - Supports fan speed - Supports preset mode - Supports oscillation @@ -14,7 +16,9 @@ Set the levels of the fan device except "Off" (3 by default). ``` ## Entities + ### Default entity + | EntityID | Class | Description | |--------------------|-------|-------------| | fan.{DEVICEID}_fan | fan | Fan entity | @@ -63,6 +67,7 @@ Set the attribute of appliance. Service data: | value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | Example + ``` service: midea_ac_lan.set_attribute data: @@ -77,4 +82,4 @@ data: device_id: XXXXXXXXXXXX attribute: oscillation_angle value: "90" -``` \ No newline at end of file +``` diff --git a/doc/FA_hans.md b/doc/FA_hans.md index faf0cdb4..4e2bcd15 100644 --- a/doc/FA_hans.md +++ b/doc/FA_hans.md @@ -1,5 +1,7 @@ # 电风扇 + ## 特性 + - 支持风速调节 - 支持预设模式 - 支持水平摆头 @@ -14,7 +16,9 @@ ``` ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |--------------------|-----|------| | fan.{DEVICEID}_fan | fan | 风扇实体 | @@ -63,6 +67,7 @@ | value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | 示例 + ```yaml service: midea_ac_lan.set_attribute data: @@ -77,4 +82,4 @@ data: device_id: XXXXXXXXXXXX attribute: oscillation_angle value: "90" -``` \ No newline at end of file +``` diff --git a/doc/FB.md b/doc/FB.md index 222b6d9c..a09062fc 100644 --- a/doc/FB.md +++ b/doc/FB.md @@ -1,11 +1,14 @@ # Electric Heater ## Features + - Supports target temperature - Supports preset mode ## Entities + ### Default entity + | EntityID | Class | Description | |----------------------------|---------|----------------| | climate.{DEVICEID}_climate | climate | Climate entity | @@ -40,6 +43,7 @@ Set the attribute of appliance. Service data: | value | 1 - 10 | Example + ```yaml service: midea_ac_lan.set_attribute data: @@ -54,4 +58,4 @@ data: device_id: XXXXXXXXXXXX attribute: heating_level value: 9 -``` \ No newline at end of file +``` diff --git a/doc/FB_hans.md b/doc/FB_hans.md index 217dd5eb..a5a84463 100644 --- a/doc/FB_hans.md +++ b/doc/FB_hans.md @@ -1,11 +1,14 @@ # 电取暖器 ## Features + - 支持目标温度设定 - 支持预设模式设定 ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |----------------------------|---------|-------| | climate.{DEVICEID}_climate | climate | 恒温器实体 | @@ -40,6 +43,7 @@ | value | 1 - 10 | 示例 + ```yaml service: midea_ac_lan.set_attribute data: @@ -54,4 +58,4 @@ data: device_id: XXXXXXXXXXXX attribute: heating_level value: 9 -``` \ No newline at end of file +``` diff --git a/doc/FC.md b/doc/FC.md index e022934c..3ce9ca06 100644 --- a/doc/FC.md +++ b/doc/FC.md @@ -9,7 +9,9 @@ Set the high/low value of PM2.5 to automatically turn standby mode on or off. ``` ## Entities + ### Default entity + No default entity. ### Extra entities @@ -33,7 +35,6 @@ No default entity. ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -71,6 +72,7 @@ Set the attribute of appliance. Service data: | value | "Bright"
"Dim"
"Off" | Example + ```yaml service: midea_ac_lan.set_attribute data: @@ -85,4 +87,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Auto -``` \ No newline at end of file +``` diff --git a/doc/FC_hans.md b/doc/FC_hans.md index 67e85cbc..145909fb 100644 --- a/doc/FC_hans.md +++ b/doc/FC_hans.md @@ -9,7 +9,9 @@ ``` ## 生成实体 + ### 默认生成实体 + 无默认实体 ### 额外生成实体 @@ -84,4 +86,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Auto -``` \ No newline at end of file +``` diff --git a/doc/FD.md b/doc/FD.md index 58fa0149..0bfd3765 100644 --- a/doc/FD.md +++ b/doc/FD.md @@ -1,11 +1,15 @@ # Humidifier + ## Features + - Supports preset mode - Supports fan mode - Supports humidity setting ## Entities + ### Default entity + | EntityID | Class | Description | |----------------------------------|------------|-------------------| | humidifier.{DEVICEID}_humidifier | humidifier | Humidifier entity | @@ -24,7 +28,6 @@ ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -50,6 +53,7 @@ Set the attribute of appliance. Service data: | value | "Bright"
"Dim"
"Off" | Example + ```yaml service: midea_ac_lan.set_attribute data: @@ -64,4 +68,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Medium -``` \ No newline at end of file +``` diff --git a/doc/FD_hans.md b/doc/FD_hans.md index 56ea428f..a0c9e8d2 100644 --- a/doc/FD_hans.md +++ b/doc/FD_hans.md @@ -1,11 +1,15 @@ # 加湿器 + ## 特性 + - 支持运行模式 - 支持风扇模式设定 - 支持湿度设定 ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |----------------------------------|------------|-------| | humidifier.{DEVICEID}_humidifier | humidifier | 加湿器实体 | @@ -49,6 +53,7 @@ | value | "Bright"
"Dim"
"Off" | 示例 + ```yaml service: midea_ac_lan.set_attribute data: @@ -63,4 +68,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Medium -``` \ No newline at end of file +``` From 055ced0f72f8b956d9283777a4f0f302902237fc Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Mon, 13 May 2024 15:25:59 +0800 Subject: [PATCH 04/14] update based on super linter error --- .flake8 | 2 ++ .github/workflows/linter.yml | 1 + .github/workflows/release.yml | 23 +++++++++---------- README.md | 13 +++++++++-- README_hans.md | 6 ++++- custom_components/midea_ac_lan/__init__.py | 2 -- custom_components/midea_ac_lan/climate.py | 2 +- custom_components/midea_ac_lan/config_flow.py | 4 ++-- .../midea_ac_lan/midea/backports/enum.py | 2 +- .../midea_ac_lan/midea/core/device.py | 2 +- .../midea_ac_lan/midea/core/security.py | 2 +- .../midea_ac_lan/midea/devices/__init__.py | 6 +++-- .../midea_ac_lan/midea/devices/a1/message.py | 1 + .../midea_ac_lan/midea/devices/ac/message.py | 6 ++--- .../midea_ac_lan/midea/devices/b0/message.py | 3 +-- .../midea_ac_lan/midea/devices/b1/message.py | 3 +-- .../midea_ac_lan/midea/devices/b3/message.py | 1 - .../midea_ac_lan/midea/devices/b4/message.py | 3 +-- .../midea_ac_lan/midea/devices/c2/message.py | 1 + .../midea_ac_lan/midea/devices/c3/device.py | 4 +++- .../midea_ac_lan/midea/devices/c3/message.py | 2 +- .../midea_ac_lan/midea/devices/cd/message.py | 5 ++-- .../midea_ac_lan/midea/devices/cf/message.py | 1 - .../midea_ac_lan/midea/devices/da/device.py | 9 ++++---- .../midea_ac_lan/midea/devices/da/message.py | 2 +- .../midea_ac_lan/midea/devices/e3/device.py | 3 +-- .../midea_ac_lan/midea/devices/e8/message.py | 3 +-- .../midea_ac_lan/midea/devices/ea/message.py | 2 +- .../midea_ac_lan/midea/devices/ec/message.py | 2 +- .../midea_ac_lan/midea/devices/ed/device.py | 2 +- .../midea_ac_lan/midea/devices/x13/device.py | 3 +-- .../midea_ac_lan/midea/devices/x13/message.py | 2 +- .../midea_ac_lan/midea/devices/x26/message.py | 3 +-- .../midea_ac_lan/midea/devices/x40/message.py | 3 +-- .../midea_ac_lan/midea_entity.py | 2 +- custom_components/midea_ac_lan/number.py | 6 ++--- custom_components/midea_ac_lan/services.yaml | 2 +- doc/FA.md | 4 ++-- 38 files changed, 75 insertions(+), 68 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..e44b8108 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +ignore = E501 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index d2b00af8..30190df6 100755 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -35,3 +35,4 @@ jobs: env: # To report GitHub Actions status checks GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VALIDATE_GITLEAKS: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3d74650..155a956e 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,15 +26,15 @@ jobs: - name: Show Output Directory For Debug Purpose run: | apt update && apt install zip || exit 1 - echo “show runner hostname” + echo "show runner hostname" hostname - echo “show runner user” + echo "show runner user" whoami - echo “show runner pwd” + echo "show runner pwd" pwd - echo “show runner kernel” + echo "show runner kernel" uname -a - echo “show runner pwd file list” + echo "show runner pwd file list" ls ls -alht custom_components/midea_ac_lan/ || exit 1 echo "show manifest.json for debug" @@ -60,7 +60,6 @@ jobs: if: ${{ startsWith(github.ref, 'refs/tags/v') }} runs-on: ubuntu-latest steps: - - name: Download All Artifacts uses: actions/download-artifact@v4 with: @@ -73,18 +72,18 @@ jobs: - name: Show Working Directory For Debug Purpose run: | - echo “show runner hostname” + echo "show runner hostname" hostname - echo “show runner user” + echo "show runner user" whoami - echo “show runner pwd” + echo "show runner pwd" pwd - echo “show runner kernel” + echo "show runner kernel” uname -a - echo “show runner pwd file list” + echo "show runner pwd file list” ls ls -alht - echo “show runner artifacts” + echo "show runner artifacts” ls -alht artifacts || exit 0 echo "github.ref: ${{github.ref}} " diff --git a/README.md b/README.md index 40b5449a..0539c9ad 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,12 @@ Thanks also to [@NeoAcheron](https://github.com/NeoAcheron/midea-ac-py). ## Supported brands -![ariston](brands/ariston.png) ![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) +![ariston](brands/ariston.png) ![beverly](brands/beverly.png) ![bugu](brands/bugu.png) \ +![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) \ +![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) \ +![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) \ +![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) \ +![wahin](brands/wahin.png) And more. @@ -126,7 +131,11 @@ Set the IP address of device. You can reset this when your device IP is changed. ### Refresh interval Set the interval for actively refreshing the status of a single device (the unit is second) (30 by default and 0 means not refresh actively) -Mostly the status update of Midea devices relies on the active information notification of the device, in which condition the status update in HA still works normally even if the refresh interval is set to be “0”. This component will also actively query the device status at regular intervals, and the default time is 30 seconds. Some devices do not have active information notifications when their status changed, so synchronization with the status in HA will be slower. If you are very concerned about the synchronization speed of the status, you can try to set a shorter status refresh interval. +Mostly the status update of Midea devices relies on the active information notification of the device, \ +in which condition the status update in HA still works normally even if the refresh interval is set to be "0". \ +This component will also actively query the device status at regular intervals, and the default time is 30 seconds. \ +Some devices do not have active information notifications when their status changed, so synchronization with the status in HA will be slower. \ +If you are very concerned about the synchronization speed of the status, you can try to set a shorter status refresh interval. ***❗Note: shorter refresh interval may mean more power consumption*** diff --git a/README_hans.md b/README_hans.md index 54b335c6..5e1ae2bd 100644 --- a/README_hans.md +++ b/README_hans.md @@ -22,7 +22,11 @@ ## 已支持的品牌 -![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) +![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) \ +![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) \ +![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) \ +![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) \ +![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) 以及更多。 diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py index 330f5e15..31aed7c3 100644 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -9,9 +9,7 @@ CONF_SUBTYPE, CONF_REFRESH_INTERVAL, DEVICES, - EXTRA_SENSOR, EXTRA_SWITCH, - EXTRA_CONTROL, ALL_PLATFORM, ) from .midea_devices import MIDEA_DEVICES diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py index b5a6c9db..4f54f395 100644 --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -516,4 +516,4 @@ def set_hvac_mode(self, hvac_mode: str) -> None: self.turn_on() def set_preset_mode(self, preset_mode: str) -> None: - self._device.set_attribute(attr=FBAttributes.mode,value=preset_mode) + self._device.set_attribute(attr=FBAttributes.mode, value=preset_mode) diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py index 98c8b53b..fd3432c1 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -254,7 +254,7 @@ async def async_step_auto(self, user_input=None, error=None): self.found_device[CONF_SUBTYPE] = device_info.get("model_number") if device.get(CONF_PROTOCOL) == 3: if self.account[CONF_SERVER] == "美的美居": - _LOGGER.debug(f"Try to get the Token and the Key use the preset MSmartHome account") + _LOGGER.debug("Try to get the Token and the Key use the preset MSmartHome account") self.cloud = get_midea_cloud( "MSmartHome", self.session, @@ -426,7 +426,7 @@ async def async_step_init(self, user_input=None): sensors = {} switches = {} for attribute, attribute_config in MIDEA_DEVICES.get(self._device_type).get("entities").items(): - attribute_name = attribute if type(attribute) is str else attribute.value + attribute_name = attribute if isinstance(attribute, str) else attribute.value if attribute_config.get("type") in EXTRA_SENSOR: sensors[attribute_name] = attribute_config.get("name") elif attribute_config.get("type") in EXTRA_CONTROL and not attribute_config.get("default"): diff --git a/custom_components/midea_ac_lan/midea/backports/enum.py b/custom_components/midea_ac_lan/midea/backports/enum.py index 03f626fe..03b359ae 100644 --- a/custom_components/midea_ac_lan/midea/backports/enum.py +++ b/custom_components/midea_ac_lan/midea/backports/enum.py @@ -31,4 +31,4 @@ def _generate_next_value_( We may revisit this when it's very clear that Python 3.11's `StrEnum.auto()` behavior will no longer change. """ - raise TypeError("auto() is not supported by this implementation") \ No newline at end of file + raise TypeError("auto() is not supported by this implementation") diff --git a/custom_components/midea_ac_lan/midea/core/device.py b/custom_components/midea_ac_lan/midea/core/device.py index dc364673..e9fee776 100644 --- a/custom_components/midea_ac_lan/midea/core/device.py +++ b/custom_components/midea_ac_lan/midea/core/device.py @@ -254,7 +254,7 @@ def parse_message(self, msg): else: _LOGGER.debug(f"[{self._device_id}] Unidentified protocol") except Exception as e: - _LOGGER.error(f"[{self._device_id}] Error in process message, msg = {decrypted.hex()}") + _LOGGER.error(f"[{self._device_id}] Error in process message, msg = {e}") else: _LOGGER.warning( f"[{self._device_id}] Illegal payload, " diff --git a/custom_components/midea_ac_lan/midea/core/security.py b/custom_components/midea_ac_lan/midea/core/security.py index fc31bda2..ccf0e96f 100644 --- a/custom_components/midea_ac_lan/midea/core/security.py +++ b/custom_components/midea_ac_lan/midea/core/security.py @@ -173,7 +173,7 @@ def __init__(self): def aes_decrypt(self, raw): try: return unpad(AES.new(self.aes_key, AES.MODE_ECB).decrypt(bytearray(raw)), 16) - except ValueError as e: + except ValueError: return bytearray(0) def aes_encrypt(self, raw): diff --git a/custom_components/midea_ac_lan/midea/devices/__init__.py b/custom_components/midea_ac_lan/midea/devices/__init__.py index a8700311..fcc5e119 100644 --- a/custom_components/midea_ac_lan/midea/devices/__init__.py +++ b/custom_components/midea_ac_lan/midea/devices/__init__.py @@ -2,6 +2,7 @@ from importlib import import_module from types import ModuleType + async def async_device_selector( hass: HomeAssistant, name: str, @@ -22,13 +23,14 @@ async def async_device_selector( device_path = f".{'x%02x' % device_type}.device" else: device_path = f".{'%02x' % device_type}.device" - + modules: list[ModuleType] = [] + def _load_device_module() -> None: """Load all service modules.""" modules.append(import_module(device_path, __package__)) await hass.async_add_import_executor_job(_load_device_module) - + device = modules[0].MideaAppliance( name=name, device_id=device_id, diff --git a/custom_components/midea_ac_lan/midea/devices/a1/message.py b/custom_components/midea_ac_lan/midea/devices/a1/message.py index cf675483..1f5fc0e8 100644 --- a/custom_components/midea_ac_lan/midea/devices/a1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/a1/message.py @@ -8,6 +8,7 @@ NewProtocolMessageBody ) + class NewProtocolTags(IntEnum): light = 0x005B diff --git a/custom_components/midea_ac_lan/midea/devices/ac/message.py b/custom_components/midea_ac_lan/midea/devices/ac/message.py index 37b10b3f..f8118c3d 100644 --- a/custom_components/midea_ac_lan/midea/devices/ac/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ac/message.py @@ -207,7 +207,7 @@ def _subprotocol_body(self): prompt_tone = 0x01 if self.prompt_tone else 0 timer = 0x04 if (self.sn8_flag and self.timer) else 0 return bytearray([ - 0x02 | boost_mode | power | dry, aux_heating, sleep_mode, 0x00, + 0x02 | boost_mode | power | dry, aux_heating, sleep_mode, 0x00, 0x00, mode, target_temperature, fan_speed, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, @@ -256,9 +256,7 @@ def _body(self): # Byte 3, fan_speed fan_speed = self.fan_speed & 0x7f # Byte 7, swing_mode - swing_mode = 0x30 | \ - (0x0c if self.swing_vertical else 0) | \ - (0x03 if self.swing_horizontal else 0) + swing_mode = 0x30 | (0x0c if self.swing_vertical else 0) | (0x03 if self.swing_horizontal else 0) # Byte 8, turbo boost_mode = 0x20 if self.boost_mode else 0 # Byte 9 aux_heating eco_mode diff --git a/custom_components/midea_ac_lan/midea/devices/b0/message.py b/custom_components/midea_ac_lan/midea/devices/b0/message.py index 6503d4cc..1c4a9eb1 100644 --- a/custom_components/midea_ac_lan/midea/devices/b0/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b0/message.py @@ -7,7 +7,7 @@ class MessageB0Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): + def __init__(self, protocol_version, message_type, body_type): super().__init__( device_type=0xB0, protocol_version=protocol_version, @@ -82,4 +82,3 @@ def __init__(self, message): else: self.set_body(B0MessageBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/b1/message.py b/custom_components/midea_ac_lan/midea/devices/b1/message.py index bbf50abd..8a9a74c7 100644 --- a/custom_components/midea_ac_lan/midea/devices/b1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b1/message.py @@ -7,7 +7,7 @@ class MessageB1Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): + def __init__(self, protocol_version, message_type, body_type): super().__init__( device_type=0xB1, protocol_version=protocol_version, @@ -52,4 +52,3 @@ def __init__(self, message): if self.message_type in [MessageType.notify1, MessageType.query]: self.set_body(B1MessageBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/b3/message.py b/custom_components/midea_ac_lan/midea/devices/b3/message.py index a4c68ca5..8af1d9e6 100644 --- a/custom_components/midea_ac_lan/midea/devices/b3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b3/message.py @@ -138,4 +138,3 @@ def __init__(self, message): elif self.message_type == MessageType.set and self.body_type == 0x24: self.set_body(B3MessageBody21(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/b4/message.py b/custom_components/midea_ac_lan/midea/devices/b4/message.py index 489c0f14..91f55059 100644 --- a/custom_components/midea_ac_lan/midea/devices/b4/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b4/message.py @@ -38,7 +38,7 @@ def __init__(self, body): self.time_remaining = (0 if body[22] == 0xFF else body[22]) * 3600 + \ (0 if body[23] == 0xFF else body[23]) * 60 + \ (0 if body[24] == 0xFF else body[24]) - self.current_temperature = (body[25] << 8 ) + body[26] + self.current_temperature = (body[25] << 8) + body[26] if self.current_temperature == 0: self.current_temperature = (body[27] << 8) + body[28] self.status = body[31] @@ -55,4 +55,3 @@ def __init__(self, message): if self.body_type == 0x01: self.set_body(B4MessageBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/c2/message.py b/custom_components/midea_ac_lan/midea/devices/c2/message.py index 71c608b4..343fa1b3 100644 --- a/custom_components/midea_ac_lan/midea/devices/c2/message.py +++ b/custom_components/midea_ac_lan/midea/devices/c2/message.py @@ -59,6 +59,7 @@ def __init__(self, protocol_version): message_type=MessageType.set, body_type=0x00) self.power = False + @property def _body(self): if self.power: diff --git a/custom_components/midea_ac_lan/midea/devices/c3/device.py b/custom_components/midea_ac_lan/midea/devices/c3/device.py index a625866d..cf1679b3 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/device.py @@ -207,7 +207,9 @@ def make_message_set(self): return message def set_attribute(self, attr, value): - message= None + + message = None + if attr in [ DeviceAttributes.zone1_power, DeviceAttributes.zone2_power, diff --git a/custom_components/midea_ac_lan/midea/devices/c3/message.py b/custom_components/midea_ac_lan/midea/devices/c3/message.py index ddfb8cc6..577d8c20 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/message.py @@ -194,4 +194,4 @@ def __init__(self, message): self.set_body(C3MessageBody(super().body, data_offset=1)) elif self.message_type == MessageType.notify1 and self.body_type == 0x04: self.set_body(C3Notify1MessageBody(super().body, data_offset=1)) - self.set_attr() \ No newline at end of file + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cd/message.py b/custom_components/midea_ac_lan/midea/devices/cd/message.py index 450345e5..40c4ba0c 100644 --- a/custom_components/midea_ac_lan/midea/devices/cd/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cd/message.py @@ -21,7 +21,7 @@ def _body(self): class MessageQuery(MessageCDBase): - def __init__(self,protocol_version): + def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, @@ -33,7 +33,7 @@ def _body(self): class MessageSet(MessageCDBase): - def __init__(self,protocol_version): + def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, @@ -83,6 +83,7 @@ def __init__(self, body): if (body[28] & 0x20) > 0: self.mode = 3 + class CD02MessageBody(MessageBody): def __init__(self, body): super().__init__(body) diff --git a/custom_components/midea_ac_lan/midea/devices/cf/message.py b/custom_components/midea_ac_lan/midea/devices/cf/message.py index e05ecf3c..6c7105bd 100644 --- a/custom_components/midea_ac_lan/midea/devices/cf/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cf/message.py @@ -82,4 +82,3 @@ def __init__(self, message): elif self.message_type in [MessageType.notify1, MessageType.notify2]: self.set_body(CFMessageBody(super().body, data_offset=0)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/da/device.py b/custom_components/midea_ac_lan/midea/devices/da/device.py index 9323bdbd..41b3a624 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/device.py +++ b/custom_components/midea_ac_lan/midea/devices/da/device.py @@ -32,7 +32,7 @@ class DeviceAttributes(StrEnum): wash_strength = "wash_strength" softener = "softener" detergent = "detergent" - + class MideaDADevice(MiedaDevice): def __init__( @@ -93,10 +93,8 @@ def process_message(self, msg): "Stir", "Mute", "Bucket Self Clean", "Air Dry"] speed = ["-", "Low", "Medium", "High"] strength = ["-", "Week", "Medium", "Strong"] - detergent = ["No", "Less", "Medium", "More", "4", - "5", "6", "7", "8", "Insufficient"] - softener = ["No", "Intelligent", "Programed", "3", "4", - "5", "6", "7", "8", "Insufficient"] + detergent = ["No", "Less", "Medium", "More", "4", "5", "6", "7", "8", "Insufficient"] + softener = ["No", "Intelligent", "Programed", "3", "4", "5", "6", "7", "8", "Insufficient"] for status in self._attributes.keys(): if hasattr(message, str(status)): if status == DeviceAttributes.progress: @@ -137,5 +135,6 @@ def set_attribute(self, attr, value): message.washing_data = self._attributes[DeviceAttributes.washing_data] self.build_send(message) + class MideaAppliance(MideaDADevice): pass diff --git a/custom_components/midea_ac_lan/midea/devices/da/message.py b/custom_components/midea_ac_lan/midea/devices/da/message.py index 3dba050f..25b1dd81 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/message.py +++ b/custom_components/midea_ac_lan/midea/devices/da/message.py @@ -47,7 +47,7 @@ def _body(self): power, 0xFF ]) - + class MessageStart(MessageDABase): def __init__(self, protocol_version): super().__init__( diff --git a/custom_components/midea_ac_lan/midea/devices/e3/device.py b/custom_components/midea_ac_lan/midea/devices/e3/device.py index 7340a625..5ad753d2 100644 --- a/custom_components/midea_ac_lan/midea/devices/e3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e3/device.py @@ -82,8 +82,7 @@ def process_message(self, msg): new_status = {} for status in self._attributes.keys(): if hasattr(message, str(status)): - if self._precision_halves and status in [DeviceAttributes.current_temperature, - DeviceAttributes.target_temperature]: + if self._precision_halves and status in [DeviceAttributes.current_temperature, DeviceAttributes.target_temperature]: self._attributes[status] = getattr(message, str(status)) / 2 else: self._attributes[status] = getattr(message, str(status)) diff --git a/custom_components/midea_ac_lan/midea/devices/e8/message.py b/custom_components/midea_ac_lan/midea/devices/e8/message.py index 7ee345d5..9c45c688 100644 --- a/custom_components/midea_ac_lan/midea/devices/e8/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e8/message.py @@ -51,7 +51,6 @@ def __init__(self, message): if len(super().body) > 6: sub_cmd = super().body[6] if ((self.message_type == MessageType.set and sub_cmd in [0x02, 0x04, 0x06]) or - self.message_type in [MessageType.query, MessageType.notify1] and sub_cmd ==2): + self.message_type in [MessageType.query, MessageType.notify1] and sub_cmd == 2): self.set_body(E8MessageBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/ea/message.py b/custom_components/midea_ac_lan/midea/devices/ea/message.py index b8e09712..e6f147a1 100644 --- a/custom_components/midea_ac_lan/midea/devices/ea/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ea/message.py @@ -107,7 +107,7 @@ def __init__(self, message): elif self.message_type == MessageType.notify1 and super().body[5] == 0x3d: # 463 self.set_body(EABody1(super().body)) else: - if(self.message_type == MessageType.set and super().body[3] == 0x02) or \ + if (self.message_type == MessageType.set and super().body[3] == 0x02) or \ (self.message_type == MessageType.query and super().body[3] == 0x03) or \ (self.message_type == MessageType.notify1 and super().body[3] == 0x04): # 351 self.set_body(EABody3(super().body)) diff --git a/custom_components/midea_ac_lan/midea/devices/ec/message.py b/custom_components/midea_ac_lan/midea/devices/ec/message.py index fb337114..3e0c42f2 100644 --- a/custom_components/midea_ac_lan/midea/devices/ec/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ec/message.py @@ -71,7 +71,7 @@ def __init__(self, message): super().__init__(message) if self.message_type == MessageType.notify1 and super().body[3] == 0x01: self.set_body(ECBodyNew(super().body)) - elif(self.message_type == MessageType.set and super().body[3] == 0x02) or \ + elif (self.message_type == MessageType.set and super().body[3] == 0x02) or \ (self.message_type == MessageType.query and super().body[3] == 0x03) or \ (self.message_type == MessageType.notify1 and super().body[3] == 0x04) or \ (self.message_type == MessageType.notify1 and super().body[3] == 0x3d): diff --git a/custom_components/midea_ac_lan/midea/devices/ed/device.py b/custom_components/midea_ac_lan/midea/devices/ed/device.py index cd0ca7d7..0af734b1 100644 --- a/custom_components/midea_ac_lan/midea/devices/ed/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ed/device.py @@ -70,7 +70,7 @@ def __init__( self._device_class = 0 def _use_new_set(self): - return True # if (self.sub_type > 342 or self.sub_type == 340) else False + return True # if (self.sub_type > 342 or self.sub_type == 340) else False def build_query(self): return [ diff --git a/custom_components/midea_ac_lan/midea/devices/x13/device.py b/custom_components/midea_ac_lan/midea/devices/x13/device.py index 2a4e1e11..049c6afe 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/device.py @@ -69,8 +69,7 @@ def color_temp_range(self): return self._color_temp_range def kelvin_to_midea(self, kelvin): - return round((kelvin - self._color_temp_range[0]) / - (self._color_temp_range[1] - self._color_temp_range[0]) * 255) + return round((kelvin - self._color_temp_range[0]) / (self._color_temp_range[1] - self._color_temp_range[0]) * 255) def midea_to_kelvin(self, midea): return round((self._color_temp_range[1] - self._color_temp_range[0]) / 255 * midea) + \ diff --git a/custom_components/midea_ac_lan/midea/devices/x13/message.py b/custom_components/midea_ac_lan/midea/devices/x13/message.py index bf6bc09a..ce8e3699 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/message.py @@ -84,6 +84,7 @@ def __init__(self, body): super().__init__(body) self.control_success = body[1] > 0 + class Message13Response(MessageResponse): def __init__(self, message): super().__init__(message) @@ -92,4 +93,3 @@ def __init__(self, message): elif self.message_type == MessageType.set and self.body_type > 0x80: self.set_body(MessageMainLightResponseBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/x26/message.py b/custom_components/midea_ac_lan/midea/devices/x26/message.py index 268bf96e..f0afd96c 100644 --- a/custom_components/midea_ac_lan/midea/devices/x26/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x26/message.py @@ -107,7 +107,7 @@ def __init__(self, body): self.fields["LIGHT_INTENSITY_THRESHOLD"] = self.read_byte(body, 7) self.fields["RADAR_SENSITIVITY"] = self.read_byte(body, 8) heat_mode = self.read_byte(body, 9) > 0 - heat_temperature = self.read_byte(body, 10) + heat_temperature = self.read_byte(body, 10) self.fields["HEATING_SPEED"] = self.read_byte(body, 11) heat_direction = self.read_byte(body, 12) bath_mode = self.read_byte(body, 13) > 0 @@ -171,4 +171,3 @@ def __init__(self, message): if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: self.set_body(Message26Body(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/x40/message.py b/custom_components/midea_ac_lan/midea/devices/x40/message.py index a7afb1be..71baa781 100644 --- a/custom_components/midea_ac_lan/midea/devices/x40/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x40/message.py @@ -114,7 +114,7 @@ def __init__(self, body): self.fields["LIGHT_INTENSITY_THRESHOLD"] = body[7] self.fields["RADAR_SENSITIVITY"] = body[8] self.fields["HEATING_ENABLE"] = body[9] - self.fields["HEATING_TEMPERATURE"]= body[10] + self.fields["HEATING_TEMPERATURE"] = body[10] self.fields["HEATING_SPEED"] = body[11] self.fields["HEATING_DIRECTION"] = body[12] self.fields["BATH_ENABLE"] = body[13] > 0 @@ -160,4 +160,3 @@ def __init__(self, message): if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: self.set_body(Message40Body(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea_entity.py b/custom_components/midea_ac_lan/midea_entity.py index 9ae901d8..2970811d 100644 --- a/custom_components/midea_ac_lan/midea_entity.py +++ b/custom_components/midea_ac_lan/midea_entity.py @@ -15,7 +15,7 @@ def __init__(self, device, entity_key: str): self._unique_id = f"{DOMAIN}.{self._device.device_id}_{entity_key}" self.entity_id = self._unique_id self._device_name = self._device.name - + @property def device(self): return self._device diff --git a/custom_components/midea_ac_lan/number.py b/custom_components/midea_ac_lan/number.py index 34624507..360e7a5c 100644 --- a/custom_components/midea_ac_lan/number.py +++ b/custom_components/midea_ac_lan/number.py @@ -35,21 +35,21 @@ def __init__(self, device, entity_key: str): @property def native_min_value(self): - return self._min_value if (type(self._min_value) is int) else \ + return self._min_value if isinstance(self._min_value, int) else \ self._device.get_attribute(attr=self._min_value) \ if self._device.get_attribute(attr=self._min_value) else \ getattr(self._device, self._min_value) @property def native_max_value(self): - return self._max_value if (type(self._max_value) is int) else \ + return self._max_value if isinstance(self._max_value, int) else \ self._device.get_attribute(attr=self._max_value) \ if self._device.get_attribute(attr=self._max_value) else \ getattr(self._device, self._max_value) @property def native_step(self): - return self._step_value if (type(self._step_value) is int) else \ + return self._step_value if isinstance(self._step_value, int) else \ self._device.get_attribute(attr=self._step_value) \ if self._device.get_attribute(attr=self._step_value) else \ getattr(self._device, self._step_value) diff --git a/custom_components/midea_ac_lan/services.yaml b/custom_components/midea_ac_lan/services.yaml index 34ef2fc9..bfb99b49 100644 --- a/custom_components/midea_ac_lan/services.yaml +++ b/custom_components/midea_ac_lan/services.yaml @@ -14,4 +14,4 @@ send_command: cmd_type: example: 2 cmd_body: - example: "B0FF01370E0000A500" \ No newline at end of file + example: "B0FF01370E0000A500" diff --git a/doc/FA.md b/doc/FA.md index 93ba2427..a4e08289 100644 --- a/doc/FA.md +++ b/doc/FA.md @@ -68,7 +68,7 @@ Set the attribute of appliance. Service data: Example -``` +```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX @@ -76,7 +76,7 @@ data: value: true ``` -``` +```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX From a722dcb1a7356dc9022ebd73b5b74e9914068df5 Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Mon, 13 May 2024 16:18:00 +0800 Subject: [PATCH 05/14] update based on super linter error --- .flake8 | 2 - .github/ISSUE_TEMPLATE/2-issue_en.yml | 6 +- .github/linters/.flake8 | 3 + .github/linters/.python-lint | 4 + .github/linters/.yaml-lint.yml | 6 + .github/workflows/codeql.yml | 14 +- .github/workflows/linter.yml | 1 + .github/workflows/release.yml | 49 +- .github/workflows/validate.yml | 1 - custom_components/midea_ac_lan/__init__.py | 115 ++- .../midea_ac_lan/binary_sensor.py | 31 +- custom_components/midea_ac_lan/climate.py | 375 +++++++- custom_components/midea_ac_lan/config_flow.py | 482 ++++++---- custom_components/midea_ac_lan/const.py | 14 +- custom_components/midea_ac_lan/fan.py | 202 ++++- custom_components/midea_ac_lan/humidifier.py | 27 +- custom_components/midea_ac_lan/light.py | 34 +- custom_components/midea_ac_lan/lock.py | 20 +- .../midea_ac_lan/midea/backports/enum.py | 1 + .../midea_ac_lan/midea/core/cloud.py | 392 +++++---- .../midea_ac_lan/midea/core/crc8.py | 288 +++++- .../midea_ac_lan/midea/core/device.py | 102 ++- .../midea_ac_lan/midea/core/discover.py | 206 ++++- .../midea_ac_lan/midea/core/message.py | 77 +- .../midea_ac_lan/midea/core/packet_builder.py | 82 +- .../midea_ac_lan/midea/core/security.py | 54 +- .../midea_ac_lan/midea/devices/__init__.py | 8 +- .../midea_ac_lan/midea/devices/a1/device.py | 86 +- .../midea_ac_lan/midea/devices/a1/message.py | 104 ++- .../midea_ac_lan/midea/devices/ac/device.py | 125 +-- .../midea_ac_lan/midea/devices/ac/message.py | 391 ++++++--- .../midea_ac_lan/midea/devices/b0/device.py | 45 +- .../midea_ac_lan/midea/devices/b0/message.py | 23 +- .../midea_ac_lan/midea/devices/b1/device.py | 45 +- .../midea_ac_lan/midea/devices/b1/message.py | 20 +- .../midea_ac_lan/midea/devices/b3/device.py | 42 +- .../midea_ac_lan/midea/devices/b3/message.py | 93 +- .../midea_ac_lan/midea/devices/b4/device.py | 45 +- .../midea_ac_lan/midea/devices/b4/message.py | 26 +- .../midea_ac_lan/midea/devices/b6/device.py | 72 +- .../midea_ac_lan/midea/devices/b6/message.py | 37 +- .../midea_ac_lan/midea/devices/bf/device.py | 45 +- .../midea_ac_lan/midea/devices/bf/message.py | 33 +- .../midea_ac_lan/midea/devices/c2/device.py | 56 +- .../midea_ac_lan/midea/devices/c2/message.py | 56 +- .../midea_ac_lan/midea/devices/c3/device.py | 108 ++- .../midea_ac_lan/midea/devices/c3/message.py | 104 +-- .../midea_ac_lan/midea/devices/ca/device.py | 35 +- .../midea_ac_lan/midea/devices/ca/message.py | 35 +- .../midea_ac_lan/midea/devices/cc/device.py | 95 +- .../midea_ac_lan/midea/devices/cc/message.py | 75 +- .../midea_ac_lan/midea/devices/cd/device.py | 52 +- .../midea_ac_lan/midea/devices/cd/message.py | 34 +- .../midea_ac_lan/midea/devices/ce/device.py | 46 +- .../midea_ac_lan/midea/devices/ce/message.py | 39 +- .../midea_ac_lan/midea/devices/cf/device.py | 36 +- .../midea_ac_lan/midea/devices/cf/message.py | 34 +- .../midea_ac_lan/midea/devices/da/device.py | 83 +- .../midea_ac_lan/midea/devices/da/message.py | 51 +- .../midea_ac_lan/midea/devices/db/device.py | 50 +- .../midea_ac_lan/midea/devices/db/message.py | 66 +- .../midea_ac_lan/midea/devices/dc/device.py | 49 +- .../midea_ac_lan/midea/devices/dc/message.py | 35 +- .../midea_ac_lan/midea/devices/e1/device.py | 80 +- .../midea_ac_lan/midea/devices/e1/message.py | 45 +- .../midea_ac_lan/midea/devices/e2/device.py | 54 +- .../midea_ac_lan/midea/devices/e2/message.py | 64 +- .../midea_ac_lan/midea/devices/e3/device.py | 57 +- .../midea_ac_lan/midea/devices/e3/message.py | 98 ++- .../midea_ac_lan/midea/devices/e6/device.py | 46 +- .../midea_ac_lan/midea/devices/e6/message.py | 25 +- .../midea_ac_lan/midea/devices/e8/device.py | 45 +- .../midea_ac_lan/midea/devices/e8/message.py | 19 +- .../midea_ac_lan/midea/devices/ea/device.py | 160 +++- .../midea_ac_lan/midea/devices/ea/message.py | 34 +- .../midea_ac_lan/midea/devices/ec/device.py | 180 +++- .../midea_ac_lan/midea/devices/ec/message.py | 31 +- .../midea_ac_lan/midea/devices/ed/device.py | 48 +- .../midea_ac_lan/midea/devices/ed/message.py | 44 +- .../midea_ac_lan/midea/devices/fa/device.py | 155 ++-- .../midea_ac_lan/midea/devices/fa/message.py | 128 ++- .../midea_ac_lan/midea/devices/fb/device.py | 48 +- .../midea_ac_lan/midea/devices/fb/message.py | 85 +- .../midea_ac_lan/midea/devices/fc/device.py | 109 ++- .../midea_ac_lan/midea/devices/fc/message.py | 86 +- .../midea_ac_lan/midea/devices/fd/device.py | 100 ++- .../midea_ac_lan/midea/devices/fd/message.py | 96 +- .../midea_ac_lan/midea/devices/x13/device.py | 60 +- .../midea_ac_lan/midea/devices/x13/message.py | 25 +- .../midea_ac_lan/midea/devices/x26/device.py | 67 +- .../midea_ac_lan/midea/devices/x26/message.py | 112 +-- .../midea_ac_lan/midea/devices/x34/device.py | 80 +- .../midea_ac_lan/midea/devices/x34/message.py | 45 +- .../midea_ac_lan/midea/devices/x40/device.py | 59 +- .../midea_ac_lan/midea/devices/x40/message.py | 108 +-- .../midea_ac_lan/midea_devices.py | 820 ++++++++---------- .../midea_ac_lan/midea_entity.py | 19 +- custom_components/midea_ac_lan/number.py | 59 +- custom_components/midea_ac_lan/select.py | 20 +- custom_components/midea_ac_lan/sensor.py | 20 +- custom_components/midea_ac_lan/services.yaml | 1 + custom_components/midea_ac_lan/switch.py | 20 +- .../midea_ac_lan/water_heater.py | 72 +- scripts/run.sh | 4 +- 104 files changed, 5187 insertions(+), 3208 deletions(-) delete mode 100644 .flake8 create mode 100644 .github/linters/.flake8 create mode 100644 .github/linters/.python-lint create mode 100644 .github/linters/.yaml-lint.yml diff --git a/.flake8 b/.flake8 deleted file mode 100644 index e44b8108..00000000 --- a/.flake8 +++ /dev/null @@ -1,2 +0,0 @@ -[flake8] -ignore = E501 diff --git a/.github/ISSUE_TEMPLATE/2-issue_en.yml b/.github/ISSUE_TEMPLATE/2-issue_en.yml index dc43c48c..e90ecfd7 100644 --- a/.github/ISSUE_TEMPLATE/2-issue_en.yml +++ b/.github/ISSUE_TEMPLATE/2-issue_en.yml @@ -19,7 +19,8 @@ body: attributes: label: Device type and model placeholder: Air Conditioner 22012225 - description: Can be found in Settings -> Devices & Services -> Midea AC LAN -> Devices -> Device Info + description: Can be found in Settings -> Devices & Services -> \ + Midea AC LAN -> Devices -> Device Info - type: input validations: required: true @@ -35,4 +36,5 @@ body: - type: textarea attributes: label: The logs - description: Should enable the debug log of Midea AC LAN, and post or upload the log when the error occurs to here \ No newline at end of file + description: Should enable the debug log of Midea AC LAN, and post or \ + upload the log when the error occurs to here \ No newline at end of file diff --git a/.github/linters/.flake8 b/.github/linters/.flake8 new file mode 100644 index 00000000..bd92aba3 --- /dev/null +++ b/.github/linters/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 500 +extend-ignore = E203,E701 diff --git a/.github/linters/.python-lint b/.github/linters/.python-lint new file mode 100644 index 00000000..d911cb0f --- /dev/null +++ b/.github/linters/.python-lint @@ -0,0 +1,4 @@ +[FORMAT] +max-line-length=500 +[MESSAGES CONTROL] +disable=import-error, logging-fstring-interpolation diff --git a/.github/linters/.yaml-lint.yml b/.github/linters/.yaml-lint.yml new file mode 100644 index 00000000..7f05aa68 --- /dev/null +++ b/.github/linters/.yaml-lint.yml @@ -0,0 +1,6 @@ +--- +extends: default +rules: + line-length: + max: 500 + level: warning diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index babce74d..225d7229 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -3,9 +3,9 @@ name: "CodeQL" on: push: - branches: [ "master" ] + branches: ["master"] pull_request: - branches: [ "master" ] + branches: ["master"] # schedule: # - cron: "42 8 * * 6" @@ -23,22 +23,22 @@ jobs: strategy: fail-fast: false matrix: - language: [ python ] + language: [python] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 30190df6..224a97bd 100755 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -36,3 +36,4 @@ jobs: # To report GitHub Actions status checks GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_GITLEAKS: false + VALIDATE_JSCPD: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 155a956e..7962bcc3 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,32 +22,31 @@ jobs: with: fetch-depth: 0 clean: true - - - name: Show Output Directory For Debug Purpose + - name: Zip release file run: | apt update && apt install zip || exit 1 - echo "show runner hostname" + echo 'show runner hostname' hostname - echo "show runner user" + echo 'show runner user' whoami - echo "show runner pwd" + echo 'show runner pwd' pwd - echo "show runner kernel" + echo 'show runner kernel' uname -a - echo "show runner pwd file list" + echo 'show runner pwd file list' ls - ls -alht custom_components/midea_ac_lan/ || exit 1 - echo "show manifest.json for debug" - cat custom_components/midea_ac_lan/manifest.json || exit 1 - sudo mkdir -p /github/workspace/artifacts || exit 1 - sudo chown -R $(id -u):$(id -g) /github/workspace/artifacts || exit 1 - sudo chmod -R 755 /github/workspace/artifacts || exit 1 - cd custom_components/midea_ac_lan/ || exit 1 + ls -alht "custom_components/midea_ac_lan/" || exit 1 + echo 'show manifest.json for debug' + cat "custom_components/midea_ac_lan/manifest.json" || exit 1 + dst_dir="/github/workspace/artifacts" + sudo mkdir -p "$dst_dir" || exit 1 + sudo chown -R $(id -u):$(id -g) "$dst_dir" || exit 1 + sudo chmod -R 755 "$dst_dir" || exit 1 + cd "custom_components/midea_ac_lan/" || exit 1 zip -r ../midea_ac_lan.zip ./* || exit 1 - cp ../midea_ac_lan.zip /github/workspace/artifacts/midea_ac_lan.zip || exit 1 - ls -alht /github/workspace/artifacts/ || exit 1 + cp ../midea_ac_lan.zip "$dst_dir/midea_ac_lan.zip" || exit 1 + ls -alht "$dst_dir" || exit 1 shell: bash - - name: Upload Artifacts uses: actions/upload-artifact@v4 with: @@ -72,20 +71,22 @@ jobs: - name: Show Working Directory For Debug Purpose run: | - echo "show runner hostname" + echo 'show runner hostname' hostname - echo "show runner user" + echo 'show runner user' whoami - echo "show runner pwd" + echo 'show runner disk usage' + df -h + echo 'show runner pwd' pwd - echo "show runner kernel” + echo 'show runner kernel' uname -a - echo "show runner pwd file list” + echo 'show runner pwd file list' ls ls -alht - echo "show runner artifacts” + echo 'show runner artifacts' ls -alht artifacts || exit 0 - echo "github.ref: ${{github.ref}} " + echo "github.ref: ${{github.ref}}" - name: Upload images to Release Asset uses: softprops/action-gh-release@v2 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 00613ca0..cbb7c6da 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -2,7 +2,6 @@ name: HACS Validation on: pull_request: - push: # schedule: # - cron: "0 0 * * *" diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py index 31aed7c3..f3ac5a6f 100644 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -1,51 +1,54 @@ +""" +__init__.py +""" + import logging -import voluptuous as vol + import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from homeassistant.const import ( + CONF_CUSTOMIZE, + CONF_DEVICE_ID, + CONF_IP_ADDRESS, + CONF_NAME, + CONF_PORT, + CONF_PROTOCOL, + CONF_TOKEN, + CONF_TYPE, +) +from homeassistant.core import HomeAssistant + from .const import ( - DOMAIN, + ALL_PLATFORM, CONF_ACCOUNT, CONF_KEY, CONF_MODEL, - CONF_SUBTYPE, CONF_REFRESH_INTERVAL, + CONF_SUBTYPE, DEVICES, + DOMAIN, EXTRA_SWITCH, - ALL_PLATFORM, -) -from .midea_devices import MIDEA_DEVICES - -from homeassistant.core import HomeAssistant -from homeassistant.const import ( - CONF_NAME, - CONF_TOKEN, - CONF_IP_ADDRESS, - CONF_PORT, - CONF_PROTOCOL, - CONF_DEVICE_ID, - CONF_TYPE, - CONF_CUSTOMIZE, ) from .midea.devices import async_device_selector +from .midea_devices import MIDEA_DEVICES _LOGGER = logging.getLogger(__name__) async def update_listener(hass, config_entry): + """ + update_listener + """ for platform in ALL_PLATFORM: await hass.config_entries.async_forward_entry_unload(config_entry, platform) for platform in ALL_PLATFORM: - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - config_entry, platform)) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) device_id = config_entry.data.get(CONF_DEVICE_ID) - customize = config_entry.options.get( - CONF_CUSTOMIZE, "" - ) - ip_address = config_entry.options.get( - CONF_IP_ADDRESS, None - ) - refresh_interval = config_entry.options.get( - CONF_REFRESH_INTERVAL, None - ) + customize = config_entry.options.get(CONF_CUSTOMIZE, "") + ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) + refresh_interval = config_entry.options.get(CONF_REFRESH_INTERVAL, None) dev = hass.data[DOMAIN][DEVICES].get(device_id) if dev: dev.set_customize(customize) @@ -55,15 +58,24 @@ async def update_listener(hass, config_entry): dev.set_refresh_interval(refresh_interval) -async def async_setup(hass: HomeAssistant, hass_config: dict): +async def async_setup(hass: HomeAssistant): + """ + async_setup + """ hass.data.setdefault(DOMAIN, {}) attributes = [] for device_entities in MIDEA_DEVICES.values(): for attribute_name, attribute in device_entities.get("entities").items(): - if attribute.get("type") in EXTRA_SWITCH and attribute_name.value not in attributes: + if ( + attribute.get("type") in EXTRA_SWITCH + and attribute_name.value not in attributes + ): attributes.append(attribute_name.value) def service_set_attribute(service): + """ + service_set_attribute + """ device_id = service.data.get("device_id") attr = service.data.get("attribute") value = service.data.get("value") @@ -72,20 +84,34 @@ def service_set_attribute(service): if attr == "fan_speed" and value == "auto": value = 102 item = MIDEA_DEVICES.get(dev.device_type).get("entities").get(attr) - if (item and (item.get("type") in EXTRA_SWITCH) or - (dev.device_type == 0xAC and attr == "fan_speed" and value in range(0, 103))): + if ( + item + and (item.get("type") in EXTRA_SWITCH) + or ( + dev.device_type == 0xAC + and attr == "fan_speed" + and value in range(0, 103) + ) + ): dev.set_attribute(attr=attr, value=value) else: - _LOGGER.error(f"Appliance [{device_id}] has no attribute {attr} or value is invalid") + _LOGGER.error( + f"Appliance [{device_id}] has no attribute {attr} or value is invalid" + ) def service_send_command(service): + """ + service_send_command + """ device_id = service.data.get("device_id") cmd_type = service.data.get("cmd_type") cmd_body = service.data.get("cmd_body") try: cmd_body = bytearray.fromhex(cmd_body) except ValueError: - _LOGGER.error(f"Appliance [{device_id}] invalid cmd_body, a hexadecimal string required") + _LOGGER.error( + f"Appliance [{device_id}] invalid cmd_body, a hexadecimal string required" + ) return dev = hass.data[DOMAIN][DEVICES].get(device_id) if dev: @@ -99,9 +125,9 @@ def service_send_command(service): { vol.Required("device_id"): vol.Coerce(int), vol.Required("attribute"): vol.In(attributes), - vol.Required("value"): vol.Any(int, cv.boolean, str) + vol.Required("value"): vol.Any(int, cv.boolean, str), } - ) + ), ) hass.services.async_register( @@ -112,14 +138,17 @@ def service_send_command(service): { vol.Required("device_id"): vol.Coerce(int), vol.Required("cmd_type"): vol.In([2, 3]), - vol.Required("cmd_body"): str + vol.Required("cmd_body"): str, } - ) + ), ) return True async def async_setup_entry(hass: HomeAssistant, config_entry): + """ + async_setup_entry + """ device_type = config_entry.data.get(CONF_TYPE) if device_type == CONF_ACCOUNT: return True @@ -128,7 +157,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry): if name is None: name = f"{device_id}" if device_type is None: - device_type = 0xac + device_type = 0xAC token = config_entry.data.get(CONF_TOKEN) key = config_entry.data.get(CONF_KEY) ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) @@ -167,14 +196,18 @@ async def async_setup_entry(hass: HomeAssistant, config_entry): hass.data[DOMAIN][DEVICES] = {} hass.data[DOMAIN][DEVICES][device_id] = device for platform in ALL_PLATFORM: - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - config_entry, platform)) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) config_entry.add_update_listener(update_listener) return True return False async def async_unload_entry(hass: HomeAssistant, config_entry): + """ + async_unload_entry + """ device_type = config_entry.data.get(CONF_TYPE) if device_type == CONF_ACCOUNT: return True diff --git a/custom_components/midea_ac_lan/binary_sensor.py b/custom_components/midea_ac_lan/binary_sensor.py index a449970d..e5db6364 100644 --- a/custom_components/midea_ac_lan/binary_sensor.py +++ b/custom_components/midea_ac_lan/binary_sensor.py @@ -1,19 +1,22 @@ +""" +binary_sensor.py +""" + from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import Platform, CONF_DEVICE_ID, CONF_SENSORS -from .const import ( - DOMAIN, - DEVICES -) -from .midea_entity import MideaEntity +from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform + +from .const import DEVICES, DOMAIN from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity async def async_setup_entry(hass, config_entry, async_add_entities): + """ + async_setup_entry + """ device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_sensors = config_entry.options.get( - CONF_SENSORS, [] - ) + extra_sensors = config_entry.options.get(CONF_SENSORS, []) binary_sensors = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.BINARY_SENSOR and entity_key in extra_sensors: @@ -23,10 +26,20 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class MideaSensor(MideaEntity, BinarySensorEntity): + """ + MideaSensor + """ + @property def device_class(self): + """ + device_class + """ return self._config.get("device_class") @property def is_on(self): + """ + is_on + """ return self._device.get_attribute(self._entity_key) diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py index 4f54f395..d982701d 100644 --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -1,12 +1,11 @@ +import logging + from homeassistant.components.climate import ( ATTR_HVAC_MODE, - ClimateEntity, - ClimateEntityFeature, FAN_AUTO, FAN_HIGH, FAN_LOW, FAN_MEDIUM, - HVACMode, PRESET_AWAY, PRESET_BOOST, PRESET_COMFORT, @@ -18,23 +17,23 @@ SWING_OFF, SWING_ON, SWING_VERTICAL, + ClimateEntity, + ClimateEntityFeature, + HVACMode, ) from homeassistant.const import ( + ATTR_TEMPERATURE, + CONF_DEVICE_ID, + CONF_SWITCHES, MAJOR_VERSION, MINOR_VERSION, + PRECISION_HALVES, + PRECISION_WHOLE, Platform, UnitOfTemperature, - PRECISION_WHOLE, - PRECISION_HALVES, - ATTR_TEMPERATURE, - CONF_DEVICE_ID, - CONF_SWITCHES, ) -from .const import ( - DOMAIN, - DEVICES, -) +from .const import DEVICES, DOMAIN from .midea.devices.ac.device import DeviceAttributes as ACAttributes from .midea.devices.c3.device import DeviceAttributes as C3Attributes from .midea.devices.cc.device import DeviceAttributes as CCAttributes @@ -43,7 +42,6 @@ from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity -import logging _LOGGER = logging.getLogger(__name__) @@ -55,14 +53,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): + """ + async_setup_entry + """ device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get( - CONF_SWITCHES, [] - ) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.CLIMATE and (config.get("default") or entity_key in extra_switches): + if config["type"] == Platform.CLIMATE and ( + config.get("default") or entity_key in extra_switches + ): if device.device_type == 0xAC: devs.append(MideaACClimate(device, entity_key)) elif device.device_type == 0xCC: @@ -77,20 +78,31 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class MideaClimate(MideaEntity, ClimateEntity): + """ + MideaClimate + """ # https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded - _enable_turn_on_off_backwards_compatibility: bool = False # maybe remove after 2025.1 + _enable_turn_on_off_backwards_compatibility: bool = ( + False # maybe remove after 2025.1 + ) def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) @property def supported_features(self): + """ + supported_features + """ features = ( - ClimateEntityFeature.TARGET_TEMPERATURE | - ClimateEntityFeature.FAN_MODE | - ClimateEntityFeature.PRESET_MODE | - ClimateEntityFeature.SWING_MODE + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.SWING_MODE ) if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON @@ -98,38 +110,65 @@ def supported_features(self): @property def min_temp(self): + """ + min_temp + """ return TEMPERATURE_MIN @property def max_temp(self): + """ + max_temp + """ return TEMPERATURE_MAX @property def temperature_unit(self): + """ + temperature_unit + """ return UnitOfTemperature.CELSIUS @property def target_temperature_low(self): + """ + target_temperature_low + """ return TEMPERATURE_MIN @property def target_temperature_high(self): + """ + target_temperature_high + """ return TEMPERATURE_MAX @property def hvac_modes(self): + """ + hvac_modes + """ return self._modes @property def swing_modes(self): + """ + swing_modes + """ return self._swing_modes @property def is_on(self) -> bool: + """ + is_on + """ return self.hvac_mode != HVACMode.OFF @property def hvac_mode(self) -> str: + """ + hvac_mode + """ if self._device.get_attribute("power"): return self._modes[self._device.get_attribute("mode")] else: @@ -137,18 +176,30 @@ def hvac_mode(self) -> str: @property def target_temperature(self): + """ + target_temperature + """ return self._device.get_attribute("target_temperature") @property def current_temperature(self): + """ + current_temperature + """ return self._device.get_attribute("indoor_temperature") @property def preset_modes(self): + """ + preset_modes + """ return self._preset_modes @property def preset_mode(self): + """ + preset_mode + """ if self._device.get_attribute("comfort_mode"): mode = PRESET_COMFORT elif self._device.get_attribute("eco_mode"): @@ -165,15 +216,27 @@ def preset_mode(self): @property def extra_state_attributes(self) -> dict: + """ + extra_state_attributes + """ return self._device.attributes def turn_on(self): + """ + turn_on + """ self._device.set_attribute(attr="power", value=True) def turn_off(self): + """ + turn_off + """ self._device.set_attribute(attr="power", value=False) def set_temperature(self, **kwargs) -> None: + """ + set_temperature + """ if ATTR_TEMPERATURE not in kwargs: return temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 @@ -184,11 +247,15 @@ def set_temperature(self, **kwargs) -> None: try: mode = self._modes.index(hvac_mode.lower()) if hvac_mode else None self._device.set_target_temperature( - target_temperature=temperature, mode=mode) + target_temperature=temperature, mode=mode + ) except ValueError as e: _LOGGER.error(f"set_temperature {e}, kwargs = {kwargs}") def set_hvac_mode(self, hvac_mode: str) -> None: + """ + set_hvac_mode + """ hvac_mode = hvac_mode.lower() if hvac_mode == HVACMode.OFF: self.turn_off() @@ -196,6 +263,9 @@ def set_hvac_mode(self, hvac_mode: str) -> None: self._device.set_attribute(attr="mode", value=self._modes.index(hvac_mode)) def set_preset_mode(self, preset_mode: str) -> None: + """ + set_preset_mode + """ old_mode = self.preset_mode preset_mode = preset_mode.lower() if preset_mode == PRESET_AWAY: @@ -220,38 +290,70 @@ def set_preset_mode(self, preset_mode: str) -> None: self._device.set_attribute(attr="boost_mode", value=False) def update_state(self, status): + """ + update_state + """ try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) class MideaACClimate(MideaClimate): + """ + MideaACClimate + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) - self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.DRY, HVACMode.HEAT, HVACMode.FAN_ONLY] + self._modes = [ + HVACMode.OFF, + HVACMode.AUTO, + HVACMode.COOL, + HVACMode.DRY, + HVACMode.HEAT, + HVACMode.FAN_ONLY, + ] self._fan_speeds = { FAN_SILENT.capitalize(): 20, FAN_LOW.capitalize(): 40, FAN_MEDIUM.capitalize(): 60, FAN_HIGH.capitalize(): 80, FAN_FULL_SPEED.capitalize(): 100, - FAN_AUTO.capitalize(): 102 + FAN_AUTO.capitalize(): 102, } self._swing_modes = [ SWING_OFF.capitalize(), SWING_VERTICAL.capitalize(), SWING_HORIZONTAL.capitalize(), - SWING_BOTH.capitalize() + SWING_BOTH.capitalize(), + ] + self._preset_modes = [ + PRESET_NONE, + PRESET_COMFORT, + PRESET_ECO, + PRESET_BOOST, + PRESET_SLEEP, + PRESET_AWAY, ] - self._preset_modes = [PRESET_NONE, PRESET_COMFORT, PRESET_ECO, PRESET_BOOST, PRESET_SLEEP, PRESET_AWAY] @property def fan_modes(self): + """ + fan_modes + """ return list(self._fan_speeds.keys()) @property def fan_mode(self) -> str: + """ + fan_mode + """ fan_speed = self._device.get_attribute(ACAttributes.fan_speed) if fan_speed > 100: return FAN_AUTO.capitalize() @@ -268,73 +370,136 @@ def fan_mode(self) -> str: @property def target_temperature_step(self): - return PRECISION_WHOLE if self._device.temperature_step == 1 else PRECISION_HALVES + """ + target_temperature_step + """ + return ( + PRECISION_WHOLE if self._device.temperature_step == 1 else PRECISION_HALVES + ) @property def swing_mode(self): - swing_mode = (1 if self._device.get_attribute(ACAttributes.swing_vertical) else 0) + \ - (2 if self._device.get_attribute(ACAttributes.swing_horizontal) else 0) + """ + swing_mode + """ + swing_mode = ( + 1 if self._device.get_attribute(ACAttributes.swing_vertical) else 0 + ) + (2 if self._device.get_attribute(ACAttributes.swing_horizontal) else 0) return self._swing_modes[swing_mode] @property def outdoor_temperature(self): + """ + outdoor_temperature + """ return self._device.get_attribute(ACAttributes.outdoor_temperature) def set_fan_mode(self, fan_mode: str) -> None: + """ + set_fan_mode + """ fan_speed = self._fan_speeds.get(fan_mode.capitalize()) if fan_speed: self._device.set_attribute(attr=ACAttributes.fan_speed, value=fan_speed) def set_swing_mode(self, swing_mode: str) -> None: + """ + set_swing_mode + """ swing = self._swing_modes.index(swing_mode.capitalize()) swing_vertical = swing & 1 > 0 swing_horizontal = swing & 2 > 0 - self._device.set_swing(swing_vertical=swing_vertical, swing_horizontal=swing_horizontal) + self._device.set_swing( + swing_vertical=swing_vertical, swing_horizontal=swing_horizontal + ) class MideaCCClimate(MideaClimate): + """ + MideaCCClimate + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) - self._modes = [HVACMode.OFF, HVACMode.FAN_ONLY, HVACMode.DRY, HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO] - self._swing_modes = [ - SWING_OFF.capitalize(), - SWING_ON.capitalize() + self._modes = [ + HVACMode.OFF, + HVACMode.FAN_ONLY, + HVACMode.DRY, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.AUTO, ] + self._swing_modes = [SWING_OFF.capitalize(), SWING_ON.capitalize()] self._preset_modes = [PRESET_NONE, PRESET_SLEEP, PRESET_ECO] @property def fan_modes(self): + """ + fan_modes + """ return self._device.fan_modes @property def fan_mode(self) -> str: + """ + fan_mode + """ return self._device.get_attribute(CCAttributes.fan_speed) @property def target_temperature_step(self): + """ + target_temperature_step + """ return self._device.get_attribute(CCAttributes.temperature_precision) @property def swing_mode(self): - return SWING_ON.capitalize() if self._device.get_attribute(CCAttributes.swing) else SWING_OFF.capitalize() + """ + swing_mode + """ + return ( + SWING_ON.capitalize() + if self._device.get_attribute(CCAttributes.swing) + else SWING_OFF.capitalize() + ) def set_fan_mode(self, fan_mode: str) -> None: + """ + set_fan_mode + """ self._device.set_attribute(attr=CCAttributes.fan_speed, value=fan_mode) def set_swing_mode(self, swing_mode: str) -> None: + """ + set_swing_mode + """ self._device.set_attribute( attr=CCAttributes.swing, - value=swing_mode.capitalize() == SWING_ON.capitalize() + value=swing_mode.capitalize() == SWING_ON.capitalize(), ) class MideaCFClimate(MideaClimate): + """ + MideaCFClimate + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] @property def supported_features(self): + """ + supported_features + """ features = ClimateEntityFeature.TARGET_TEMPERATURE if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON @@ -342,36 +507,61 @@ def supported_features(self): @property def target_temperature_step(self): + """ + target_temperature_step + """ return PRECISION_WHOLE @property def min_temp(self): + """ + min_temp + """ return self._device.get_attribute(CFAttributes.min_temperature) @property def max_temp(self): + """ + max_temp + """ return self._device.get_attribute(CFAttributes.max_temperature) @property def target_temperature_low(self): + """ + target_temperature_low + """ return self._device.get_attribute(CFAttributes.min_temperature) @property def target_temperature_high(self): + """ + target_temperature_high + """ return self._device.get_attribute(CFAttributes.max_temperature) @property def current_temperature(self): + """ + current_temperature + """ return self._device.get_attribute(CFAttributes.current_temperature) class MideaC3Climate(MideaClimate): + """ + MideaC3Climate + """ + _powers = [ C3Attributes.zone1_power, C3Attributes.zone2_power, ] def __init__(self, device, entity_key, zone): + """ + __init__ + """ super().__init__(device, entity_key) self._zone = zone self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] @@ -379,6 +569,9 @@ def __init__(self, device, entity_key, zone): @property def supported_features(self): + """ + supported_features + """ features = ClimateEntityFeature.TARGET_TEMPERATURE if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON @@ -386,33 +579,60 @@ def supported_features(self): @property def target_temperature_step(self): - return PRECISION_WHOLE if \ - self._device.get_attribute(C3Attributes.zone_temp_type)[self._zone] else PRECISION_HALVES + """ + target_temperature_step + """ + return ( + PRECISION_WHOLE + if self._device.get_attribute(C3Attributes.zone_temp_type)[self._zone] + else PRECISION_HALVES + ) @property def min_temp(self): + """ + min_temp + """ return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] @property def max_temp(self): + """ + max_temp + """ return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] @property def target_temperature_low(self): + """ + target_temperature_low + """ return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] @property def target_temperature_high(self): + """ + target_temperature_high + """ return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] def turn_on(self): + """ + turn_on + """ self._device.set_attribute(attr=self._power_attr, value=True) def turn_off(self): + """ + turn_off + """ self._device.set_attribute(attr=self._power_attr, value=False) @property def hvac_mode(self) -> str: + """ + hvac_mode + """ if self._device.get_attribute(self._power_attr): return self._modes[self._device.get_attribute(C3Attributes.mode)] else: @@ -420,13 +640,22 @@ def hvac_mode(self) -> str: @property def target_temperature(self): + """ + target_temperature + """ return self._device.get_attribute(C3Attributes.target_temperature)[self._zone] @property def current_temperature(self): + """ + current_temperature + """ return None def set_temperature(self, **kwargs) -> None: + """ + set_temperature + """ if ATTR_TEMPERATURE not in kwargs: return temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 @@ -437,11 +666,15 @@ def set_temperature(self, **kwargs) -> None: try: mode = self._modes.index(hvac_mode.lower()) if hvac_mode else None self._device.set_target_temperature( - zone=self._zone, target_temperature=temperature, mode=mode) + zone=self._zone, target_temperature=temperature, mode=mode + ) except ValueError as e: _LOGGER.error(f"set_temperature {e}, kwargs = {kwargs}") def set_hvac_mode(self, hvac_mode: str) -> None: + """ + set_hvac_mode + """ hvac_mode = hvac_mode.lower() if hvac_mode == HVACMode.OFF: self.turn_off() @@ -450,55 +683,101 @@ def set_hvac_mode(self, hvac_mode: str) -> None: class MideaFBClimate(MideaClimate): + """ + MideaFBClimate + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) self._modes = [HVACMode.OFF, HVACMode.HEAT] self._preset_modes = self._device.modes @property def supported_features(self): - features = ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + """ + supported_features + """ + features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON return features @property def target_temperature_step(self): + """ + target_temperature_step + """ return PRECISION_WHOLE @property def preset_modes(self): + """ + preset_modes + """ return self._preset_modes @property def preset_mode(self): + """ + preset_mode + """ return self._device.get_attribute(attr=FBAttributes.mode) @property def min_temp(self): + """ + min_temp + """ return 5 @property def max_temp(self): + """ + max_temp + """ return 35 @property def target_temperature_low(self): + """ + target_temperature_low + """ return 5 @property def target_temperature_high(self): + """ + target_temperature_high + """ return 35 @property def hvac_mode(self) -> str: - return HVACMode.HEAT if self._device.get_attribute(attr=FBAttributes.power) else HVACMode.OFF + """ + hvac_mode + """ + return ( + HVACMode.HEAT + if self._device.get_attribute(attr=FBAttributes.power) + else HVACMode.OFF + ) @property def current_temperature(self): + """ + current_temperature + """ return self._device.get_attribute(FBAttributes.current_temperature) def set_temperature(self, **kwargs) -> None: + """ + set_temperature + """ if ATTR_TEMPERATURE not in kwargs: return temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 @@ -506,9 +785,14 @@ def set_temperature(self, **kwargs) -> None: if hvac_mode == HVACMode.OFF: self.turn_off() else: - self._device.set_attribute(attr=FBAttributes.target_temperature, value=temperature) + self._device.set_attribute( + attr=FBAttributes.target_temperature, value=temperature + ) def set_hvac_mode(self, hvac_mode: str) -> None: + """ + set_hvac_mode + """ hvac_mode = hvac_mode.lower() if hvac_mode == HVACMode.OFF: self.turn_off() @@ -516,4 +800,7 @@ def set_hvac_mode(self, hvac_mode: str) -> None: self.turn_on() def set_preset_mode(self, preset_mode: str) -> None: + """ + set_preset_mode + """ self._device.set_attribute(attr=FBAttributes.mode, value=preset_mode) diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py index fd3432c1..29cdc967 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -1,48 +1,61 @@ -import voluptuous as vol +""" +config_flow.py +""" + import os + +import voluptuous as vol + try: from homeassistant.helpers.json import save_json except ImportError: from homeassistant.util.json import save_json -from homeassistant.util.json import load_json + import logging -from .const import ( - DOMAIN, - EXTRA_SENSOR, - EXTRA_CONTROL, - CONF_ACCOUNT, - CONF_SERVER, - CONF_KEY, - CONF_MODEL, - CONF_SUBTYPE, - CONF_REFRESH_INTERVAL -) + +import homeassistant.helpers.config_validation as cv from homeassistant import config_entries -from homeassistant.core import callback from homeassistant.const import ( - CONF_NAME, + CONF_CUSTOMIZE, CONF_DEVICE, - CONF_TOKEN, CONF_DEVICE_ID, - CONF_TYPE, CONF_IP_ADDRESS, - CONF_PROTOCOL, + CONF_NAME, + CONF_PASSWORD, CONF_PORT, - CONF_SWITCHES, + CONF_PROTOCOL, CONF_SENSORS, - CONF_CUSTOMIZE, - CONF_PASSWORD, + CONF_SWITCHES, + CONF_TOKEN, + CONF_TYPE, ) -import homeassistant.helpers.config_validation as cv +from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_create_clientsession -from .midea.core.discover import discover +from homeassistant.util.json import load_json + +from .const import ( + CONF_ACCOUNT, + CONF_KEY, + CONF_MODEL, + CONF_REFRESH_INTERVAL, + CONF_SERVER, + CONF_SUBTYPE, + DOMAIN, + EXTRA_CONTROL, + EXTRA_SENSOR, +) from .midea.core.cloud import get_midea_cloud from .midea.core.device import MiedaDevice +from .midea.core.discover import discover from .midea_devices import MIDEA_DEVICES _LOGGER = logging.getLogger(__name__) -ADD_WAY = {"discovery": "Discover automatically", "manually": "Configure manually", "list": "List all appliances only"} +ADD_WAY = { + "discovery": "Discover automatically", + "manually": "Configure manually", + "list": "List all appliances only", +} PROTOCOLS = {1: "V1", 2: "V2", 3: "V3"} STORAGE_PATH = f".storage/{DOMAIN}" @@ -57,11 +70,15 @@ PRESET_ACCOUNT = [ 39182118275972017797890111985649342047468653967530949796945843010512, 29406100301096535908214728322278519471982973450672552249652548883645, - 39182118275972017797890111985649342050088014265865102175083010656997 + 39182118275972017797890111985649342050088014265865102175083010656997, ] class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """ + ConfigFlow + """ + available_device = [] devices = {} found_device = {} @@ -78,48 +95,85 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): supports[item[0]] = item[1] def _save_device_config(self, data: dict): + """ + _save_device_config + """ os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True) - record_file = self.hass.config.path(f"{STORAGE_PATH}/{data[CONF_DEVICE_ID]}.json") + record_file = self.hass.config.path( + f"{STORAGE_PATH}/{data[CONF_DEVICE_ID]}.json" + ) save_json(record_file, data) def _load_device_config(self, device_id): + """ + _load_device_config + """ record_file = self.hass.config.path(f"{STORAGE_PATH}/{device_id}.json") json_data = load_json(record_file, default={}) return json_data def _save_account(self, account: dict): + """ + _save_account + """ os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True) record_file = self.hass.config.path(f"{STORAGE_PATH}/account.json") - account[CONF_PASSWORD] = format((int(account[CONF_ACCOUNT].encode("utf-8").hex(), 16) ^ - int(account[CONF_PASSWORD].encode("utf-8").hex(), 16)), 'x') + account[CONF_PASSWORD] = format( + ( + int(account[CONF_ACCOUNT].encode("utf-8").hex(), 16) + ^ int(account[CONF_PASSWORD].encode("utf-8").hex(), 16) + ), + "x", + ) save_json(record_file, account) def _load_account(self): + """ + _load_account + """ record_file = self.hass.config.path(f"{STORAGE_PATH}/account.json") json_data = load_json(record_file, default={}) if CONF_ACCOUNT in json_data.keys(): - json_data[CONF_PASSWORD] = bytes.fromhex(format(( - int(json_data[CONF_PASSWORD], 16) ^ - int(json_data[CONF_ACCOUNT].encode("utf-8").hex(), 16)), 'X') - ).decode('UTF-8') + json_data[CONF_PASSWORD] = bytes.fromhex( + format( + ( + int(json_data[CONF_PASSWORD], 16) + ^ int(json_data[CONF_ACCOUNT].encode("utf-8").hex(), 16) + ), + "X", + ) + ).decode("UTF-8") return json_data @staticmethod def _check_storage_device(device: dict, storage_device: dict): + """ + _check_storage_device + """ if storage_device.get(CONF_SUBTYPE) is None: return False - if (device.get(CONF_PROTOCOL) == 3 and - (storage_device.get(CONF_TOKEN) is None or storage_device.get(CONF_KEY) is None)): + if device.get(CONF_PROTOCOL) == 3 and ( + storage_device.get(CONF_TOKEN) is None + or storage_device.get(CONF_KEY) is None + ): return False return True def _already_configured(self, device_id, ip_address): + """ + _already_configured + """ for entry in self._async_current_entries(): - if device_id == entry.data.get(CONF_DEVICE_ID) or ip_address == entry.data.get(CONF_IP_ADDRESS): + if device_id == entry.data.get( + CONF_DEVICE_ID + ) or ip_address == entry.data.get(CONF_IP_ADDRESS): return True return False async def async_step_user(self, user_input=None, error=None): + """ + async_step_user + """ if user_input is not None: if user_input["action"] == "discovery": return await self.async_step_discovery() @@ -130,13 +184,16 @@ async def async_step_user(self, user_input=None, error=None): return await self.async_step_list() return self.async_show_form( step_id="user", - data_schema=vol.Schema({ - vol.Required("action", default="discovery"): vol.In(ADD_WAY) - }), - errors={"base": error} if error else None + data_schema=vol.Schema( + {vol.Required("action", default="discovery"): vol.In(ADD_WAY)} + ), + errors={"base": error} if error else None, ) async def async_step_login(self, user_input=None, error=None): + """ + async_step_login + """ if user_input is not None: if self.session is None: self.session = async_create_clientsession(self.hass) @@ -145,13 +202,13 @@ async def async_step_login(self, user_input=None, error=None): session=self.session, cloud_name=SERVERS[user_input[CONF_SERVER]], account=user_input[CONF_ACCOUNT], - password=user_input[CONF_PASSWORD] + password=user_input[CONF_PASSWORD], ) if await self.cloud.login(): self.account = { CONF_ACCOUNT: user_input[CONF_ACCOUNT], CONF_PASSWORD: user_input[CONF_PASSWORD], - CONF_SERVER: SERVERS[user_input[CONF_SERVER]] + CONF_SERVER: SERVERS[user_input[CONF_SERVER]], } self._save_account(self.account) return await self.async_step_auto() @@ -159,32 +216,44 @@ async def async_step_login(self, user_input=None, error=None): return await self.async_step_login(error="login_failed") return self.async_show_form( step_id="login", - data_schema=vol.Schema({ - vol.Required(CONF_ACCOUNT): str, - vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_SERVER, default=1): vol.In(SERVERS) - }), - errors={"base": error} if error else None + data_schema=vol.Schema( + { + vol.Required(CONF_ACCOUNT): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_SERVER, default=1): vol.In(SERVERS), + } + ), + errors={"base": error} if error else None, ) async def async_step_list(self, user_input=None, error=None): + """ + async_step_list + """ all_devices = discover() if len(all_devices) > 0: - table = "Appliance code|Type|IP address|SN|Supported\n:--:|:--:|:--:|:--:|:--:" + table = ( + "Appliance code|Type|IP address|SN|Supported\n:--:|:--:|:--:|:--:|:--:" + ) for device_id, device in all_devices.items(): supported = device.get(CONF_TYPE) in self.supports.keys() - table += f"\n{device_id}|{'%02X' % device.get(CONF_TYPE)}|{device.get(CONF_IP_ADDRESS)}|" \ - f"{device.get('sn')}|" \ - f"{'YES' if supported else 'NO'}" + table += ( + f"\n{device_id}|{'%02X' % device.get(CONF_TYPE)}|{device.get(CONF_IP_ADDRESS)}|" + f"{device.get('sn')}|" + f"{'YES' if supported else 'NO'}" + ) else: table = "Not found" return self.async_show_form( step_id="list", description_placeholders={"table": table}, - errors={"base": error} if error else None + errors={"base": error} if error else None, ) async def async_step_discovery(self, user_input=None, error=None): + """ + async_step_discovery + """ if user_input is not None: if user_input[CONF_IP_ADDRESS].lower() == "auto": ip_address = None @@ -194,21 +263,25 @@ async def async_step_discovery(self, user_input=None, error=None): self.available_device = {} for device_id, device in self.devices.items(): if not self._already_configured(device_id, device.get(CONF_IP_ADDRESS)): - self.available_device[device_id] = \ + self.available_device[device_id] = ( f"{device_id} ({self.supports.get(device.get(CONF_TYPE))})" + ) if len(self.available_device) > 0: return await self.async_step_auto() else: return await self.async_step_discovery(error="no_devices") return self.async_show_form( step_id="discovery", - data_schema=vol.Schema({ - vol.Required(CONF_IP_ADDRESS, default="auto"): str - }), - errors={"base": error} if error else None + data_schema=vol.Schema( + {vol.Required(CONF_IP_ADDRESS, default="auto"): str} + ), + errors={"base": error} if error else None, ) async def async_step_auto(self, user_input=None, error=None): + """ + async_step_auto + """ if user_input is not None: device_id = user_input[CONF_DEVICE] device = self.devices.get(device_id) @@ -224,9 +297,11 @@ async def async_step_auto(self, user_input=None, error=None): CONF_NAME: storage_device.get(CONF_NAME), CONF_SUBTYPE: storage_device.get(CONF_SUBTYPE), CONF_TOKEN: storage_device.get(CONF_TOKEN), - CONF_KEY: storage_device.get(CONF_KEY) + CONF_KEY: storage_device.get(CONF_KEY), } - _LOGGER.debug(f"Loaded configuration for device {device_id} from storage") + _LOGGER.debug( + f"Loaded configuration for device {device_id} from storage" + ) return await self.async_step_manually() else: if CONF_ACCOUNT not in self.account.keys(): @@ -237,8 +312,11 @@ async def async_step_auto(self, user_input=None, error=None): self.session = async_create_clientsession(self.hass) if self.cloud is None: self.cloud = get_midea_cloud( - self.account[CONF_SERVER], self.session, self.account[CONF_ACCOUNT], - self.account[CONF_PASSWORD]) + self.account[CONF_SERVER], + self.session, + self.account[CONF_ACCOUNT], + self.account[CONF_PASSWORD], + ) if not await self.cloud.login(): return await self.async_step_login() self.found_device = { @@ -254,12 +332,19 @@ async def async_step_auto(self, user_input=None, error=None): self.found_device[CONF_SUBTYPE] = device_info.get("model_number") if device.get(CONF_PROTOCOL) == 3: if self.account[CONF_SERVER] == "美的美居": - _LOGGER.debug("Try to get the Token and the Key use the preset MSmartHome account") + _LOGGER.debug( + "Try to get the Token and the Key use the preset MSmartHome account" + ) self.cloud = get_midea_cloud( "MSmartHome", self.session, - bytes.fromhex(format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[1]), 'X')).decode('ASCII'), - bytes.fromhex(format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[2]), 'X')).decode('ASCII')) + bytes.fromhex( + format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[1]), "X") + ).decode("ASCII"), + bytes.fromhex( + format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[2]), "X") + ).decode("ASCII"), + ) if not await self.cloud.login(): return await self.async_step_auto(error="preset_account") keys = await self.cloud.get_keys(user_input[CONF_DEVICE]) @@ -275,7 +360,7 @@ async def async_step_auto(self, user_input=None, error=None): protocol=3, model=device.get(CONF_MODEL), subtype=0, - attributes={} + attributes={}, ) if dm.connect(refresh_status=False): dm.close_socket() @@ -288,14 +373,20 @@ async def async_step_auto(self, user_input=None, error=None): return self.async_show_form( step_id="auto", - data_schema=vol.Schema({ - vol.Required(CONF_DEVICE, default=list(self.available_device.keys())[0]): - vol.In(self.available_device), - }), - errors={"base": error} if error else None + data_schema=vol.Schema( + { + vol.Required( + CONF_DEVICE, default=list(self.available_device.keys())[0] + ): vol.In(self.available_device), + } + ), + errors={"base": error} if error else None, ) async def async_step_manually(self, user_input=None, error=None): + """ + async_step_manually + """ if user_input is not None: self.found_device = { CONF_DEVICE_ID: user_input[CONF_DEVICE_ID], @@ -305,14 +396,16 @@ async def async_step_manually(self, user_input=None, error=None): CONF_PORT: user_input[CONF_PORT], CONF_MODEL: user_input[CONF_MODEL], CONF_TOKEN: user_input[CONF_TOKEN], - CONF_KEY: user_input[CONF_KEY] + CONF_KEY: user_input[CONF_KEY], } try: bytearray.fromhex(user_input[CONF_TOKEN]) bytearray.fromhex(user_input[CONF_KEY]) except ValueError: return await self.async_step_manually(error="invalid_token") - if user_input[CONF_PROTOCOL] == 3 and (len(user_input[CONF_TOKEN]) == 0 or len(user_input[CONF_KEY]) == 0): + if user_input[CONF_PROTOCOL] == 3 and ( + len(user_input[CONF_TOKEN]) == 0 or len(user_input[CONF_KEY]) == 0 + ): return await self.async_step_manually(error="invalid_token") dm = MiedaDevice( name="", @@ -325,7 +418,7 @@ async def async_step_manually(self, user_input=None, error=None): protocol=user_input[CONF_PROTOCOL], model=user_input[CONF_MODEL], subtype=0, - attributes={} + attributes={}, ) if dm.connect(refresh_status=False): dm.close_socket() @@ -340,75 +433,114 @@ async def async_step_manually(self, user_input=None, error=None): CONF_SUBTYPE: user_input[CONF_SUBTYPE], CONF_TOKEN: user_input[CONF_TOKEN], CONF_KEY: user_input[CONF_KEY], - } + } self._save_device_config(data) return self.async_create_entry( - title=f"{user_input[CONF_NAME]}", - data=data + title=f"{user_input[CONF_NAME]}", data=data ) else: return await self.async_step_manually(error="config_incorrect") return self.async_show_form( step_id="manually", - data_schema=vol.Schema({ - vol.Required( - CONF_NAME, - default=(self.found_device.get(CONF_NAME) - if self.found_device.get(CONF_NAME) - else self.supports.get(self.found_device.get(CONF_TYPE))) - ): str, - vol.Required( - CONF_DEVICE_ID, - default=self.found_device.get(CONF_DEVICE_ID) - ): int, - vol.Required( - CONF_TYPE, - default=self.found_device.get(CONF_TYPE) if self.found_device.get(CONF_TYPE) else 0xac - ): vol.In(self.supports), - vol.Required( - CONF_IP_ADDRESS, - default=self.found_device.get(CONF_IP_ADDRESS) - ): str, - vol.Required( - CONF_PORT, - default=self.found_device.get(CONF_PORT) if self.found_device.get(CONF_PORT) else 6444 - ): int, - vol.Required( - CONF_PROTOCOL, - default=self.found_device.get(CONF_PROTOCOL) if self.found_device.get(CONF_PROTOCOL) else 3 - ): vol.In(PROTOCOLS), - vol.Required( - CONF_MODEL, - default=self.found_device.get(CONF_MODEL) if self.found_device.get(CONF_MODEL) else "Unknown" - ): str, - vol.Required( - CONF_SUBTYPE, - default=self.found_device.get(CONF_SUBTYPE) if self.found_device.get(CONF_SUBTYPE) else 0 - ): int, - vol.Optional( - CONF_TOKEN, - default=self.found_device.get(CONF_TOKEN) if self.found_device.get(CONF_TOKEN) else "" - ): str, - vol.Optional( - CONF_KEY, - default=self.found_device.get(CONF_KEY) if self.found_device.get(CONF_KEY) else "" - ): str, - }), - errors={"base": error} if error else None + data_schema=vol.Schema( + { + vol.Required( + CONF_NAME, + default=( + self.found_device.get(CONF_NAME) + if self.found_device.get(CONF_NAME) + else self.supports.get(self.found_device.get(CONF_TYPE)) + ), + ): str, + vol.Required( + CONF_DEVICE_ID, default=self.found_device.get(CONF_DEVICE_ID) + ): int, + vol.Required( + CONF_TYPE, + default=( + self.found_device.get(CONF_TYPE) + if self.found_device.get(CONF_TYPE) + else 0xAC + ), + ): vol.In(self.supports), + vol.Required( + CONF_IP_ADDRESS, default=self.found_device.get(CONF_IP_ADDRESS) + ): str, + vol.Required( + CONF_PORT, + default=( + self.found_device.get(CONF_PORT) + if self.found_device.get(CONF_PORT) + else 6444 + ), + ): int, + vol.Required( + CONF_PROTOCOL, + default=( + self.found_device.get(CONF_PROTOCOL) + if self.found_device.get(CONF_PROTOCOL) + else 3 + ), + ): vol.In(PROTOCOLS), + vol.Required( + CONF_MODEL, + default=( + self.found_device.get(CONF_MODEL) + if self.found_device.get(CONF_MODEL) + else "Unknown" + ), + ): str, + vol.Required( + CONF_SUBTYPE, + default=( + self.found_device.get(CONF_SUBTYPE) + if self.found_device.get(CONF_SUBTYPE) + else 0 + ), + ): int, + vol.Optional( + CONF_TOKEN, + default=( + self.found_device.get(CONF_TOKEN) + if self.found_device.get(CONF_TOKEN) + else "" + ), + ): str, + vol.Optional( + CONF_KEY, + default=( + self.found_device.get(CONF_KEY) + if self.found_device.get(CONF_KEY) + else "" + ), + ): str, + } + ), + errors={"base": error} if error else None, ) @staticmethod @callback def async_get_options_flow(config_entry): + """ + async_get_options_flow + """ return OptionsFlowHandler(config_entry) class OptionsFlowHandler(config_entries.OptionsFlow): + """ + OptionsFlowHandler + """ + def __init__(self, config_entry: config_entries.ConfigEntry): + """ + __init__ + """ self._config_entry = config_entry self._device_type = config_entry.data.get(CONF_TYPE) if self._device_type is None: - self._device_type = 0xac + self._device_type = 0xAC if CONF_SENSORS in self._config_entry.options: for key in self._config_entry.options[CONF_SENSORS]: if key not in MIDEA_DEVICES[self._device_type]["entities"]: @@ -419,72 +551,70 @@ def __init__(self, config_entry: config_entries.ConfigEntry): self._config_entry.options[CONF_SWITCHES].remove(key) async def async_step_init(self, user_input=None): + """ + async_step_init + """ if self._device_type == CONF_ACCOUNT: return self.async_abort(reason="account_option") if user_input is not None: return self.async_create_entry(title="", data=user_input) sensors = {} switches = {} - for attribute, attribute_config in MIDEA_DEVICES.get(self._device_type).get("entities").items(): - attribute_name = attribute if isinstance(attribute, str) else attribute.value + for attribute, attribute_config in ( + MIDEA_DEVICES.get(self._device_type).get("entities").items() + ): + attribute_name = ( + attribute if isinstance(attribute, str) else attribute.value + ) if attribute_config.get("type") in EXTRA_SENSOR: sensors[attribute_name] = attribute_config.get("name") - elif attribute_config.get("type") in EXTRA_CONTROL and not attribute_config.get("default"): + elif attribute_config.get( + "type" + ) in EXTRA_CONTROL and not attribute_config.get("default"): switches[attribute_name] = attribute_config.get("name") - ip_address = self._config_entry.options.get( - CONF_IP_ADDRESS, None - ) + ip_address = self._config_entry.options.get(CONF_IP_ADDRESS, None) if ip_address is None: - ip_address = self._config_entry.data.get( - CONF_IP_ADDRESS, None - ) - refresh_interval = self._config_entry.options.get( - CONF_REFRESH_INTERVAL, 30 + ip_address = self._config_entry.data.get(CONF_IP_ADDRESS, None) + refresh_interval = self._config_entry.options.get(CONF_REFRESH_INTERVAL, 30) + extra_sensors = list( + set(sensors.keys()) & set(self._config_entry.options.get(CONF_SENSORS, [])) ) - extra_sensors = list(set(sensors.keys()) & set(self._config_entry.options.get( - CONF_SENSORS, [] - ))) - extra_switches = list(set(switches.keys()) & set(self._config_entry.options.get( - CONF_SWITCHES, [] - ))) - customize = self._config_entry.options.get( - CONF_CUSTOMIZE, "" + extra_switches = list( + set(switches.keys()) + & set(self._config_entry.options.get(CONF_SWITCHES, [])) + ) + customize = self._config_entry.options.get(CONF_CUSTOMIZE, "") + data_schema = vol.Schema( + { + vol.Required(CONF_IP_ADDRESS, default=ip_address): str, + vol.Required(CONF_REFRESH_INTERVAL, default=refresh_interval): int, + } ) - data_schema = vol.Schema({ - vol.Required( - CONF_IP_ADDRESS, - default=ip_address - ): str, - vol.Required( - CONF_REFRESH_INTERVAL, - default=refresh_interval - ): int - }) if len(sensors) > 0: - data_schema = data_schema.extend({ - vol.Required( - CONF_SENSORS, - default=extra_sensors, - ): - cv.multi_select(sensors) - }) + data_schema = data_schema.extend( + { + vol.Required( + CONF_SENSORS, + default=extra_sensors, + ): cv.multi_select(sensors) + } + ) if len(switches) > 0: - data_schema = data_schema.extend({ - vol.Required( - CONF_SWITCHES, - default=extra_switches, - ): - cv.multi_select(switches) - }) - data_schema = data_schema.extend({ - vol.Optional( - CONF_CUSTOMIZE, - default=customize, - ): - str - }) - - return self.async_show_form( - step_id="init", - data_schema=data_schema + data_schema = data_schema.extend( + { + vol.Required( + CONF_SWITCHES, + default=extra_switches, + ): cv.multi_select(switches) + } + ) + data_schema = data_schema.extend( + { + vol.Optional( + CONF_CUSTOMIZE, + default=customize, + ): str + } ) + + return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/custom_components/midea_ac_lan/const.py b/custom_components/midea_ac_lan/const.py index e3897533..bbebdc8d 100644 --- a/custom_components/midea_ac_lan/const.py +++ b/custom_components/midea_ac_lan/const.py @@ -1,4 +1,9 @@ +""" +const.py +""" + from homeassistant.const import Platform + DOMAIN = "midea_ac_lan" COMPONENT = "component" DEVICES = "devices" @@ -10,6 +15,11 @@ CONF_REFRESH_INTERVAL = "refresh_interval" EXTRA_SENSOR = [Platform.SENSOR, Platform.BINARY_SENSOR] EXTRA_SWITCH = [Platform.SWITCH, Platform.LOCK, Platform.SELECT, Platform.NUMBER] -EXTRA_CONTROL = [Platform.CLIMATE, Platform.WATER_HEATER, Platform.FAN, Platform.HUMIDIFIER, Platform.LIGHT] + \ - EXTRA_SWITCH +EXTRA_CONTROL = [ + Platform.CLIMATE, + Platform.WATER_HEATER, + Platform.FAN, + Platform.HUMIDIFIER, + Platform.LIGHT, +] + EXTRA_SWITCH ALL_PLATFORM = EXTRA_SENSOR + EXTRA_CONTROL diff --git a/custom_components/midea_ac_lan/fan.py b/custom_components/midea_ac_lan/fan.py index 7fda6b32..853c8abe 100644 --- a/custom_components/midea_ac_lan/fan.py +++ b/custom_components/midea_ac_lan/fan.py @@ -1,36 +1,41 @@ +""" +fan.py +""" + +import logging from typing import Any from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.const import ( - Platform, CONF_DEVICE_ID, CONF_SWITCHES, + STATE_OFF, STATE_ON, - STATE_OFF -) -from .const import ( - DOMAIN, - DEVICES, + Platform, ) + +from .const import DEVICES, DOMAIN from .midea.devices.ac.device import DeviceAttributes as ACAttributes from .midea.devices.ce.device import DeviceAttributes as CEAttributes from .midea.devices.x40.device import DeviceAttributes as X40Attributes from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity -import logging _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): + """ + async_setup_entry + """ device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get( - CONF_SWITCHES, [] - ) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.FAN and (config.get("default") or entity_key in extra_switches): + if config["type"] == Platform.FAN and ( + config.get("default") or entity_key in extra_switches + ): if device.device_type == 0xFA: devs.append(MideaFAFan(device, entity_key)) elif device.device_type == 0xB6: @@ -45,7 +50,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class MideaFan(MideaEntity, FanEntity): + """ + MideaFan + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) def turn_on( @@ -54,6 +66,9 @@ def turn_on( preset_mode: str | None = None, **kwargs: Any, ) -> None: + """ + turn_on + """ if percentage: fan_speed = int(percentage / self.percentage_step + 0.5) else: @@ -62,141 +77,286 @@ def turn_on( @property def preset_modes(self): - return self._device.preset_modes if hasattr(self._device, "preset_modes") else None + """ + preset_modes + """ + return ( + self._device.preset_modes if hasattr(self._device, "preset_modes") else None + ) @property def is_on(self) -> bool: + """ + is_on + """ return self._device.get_attribute("power") @property def oscillating(self): + """ + oscillating + """ return self._device.get_attribute("oscillate") @property def preset_mode(self): + """ + preset_mode + """ return self._device.get_attribute("mode") @property def fan_speed(self): + """ + fan_speed + """ return self._device.get_attribute("fan_speed") def turn_off(self): + """ + turn_off + """ self._device.set_attribute(attr="power", value=False) def toggle(self): + """ + toggle + """ toggle = not self.is_on self._device.set_attribute(attr="power", value=toggle) def oscillate(self, oscillating: bool): + """ + oscillate + """ self._device.set_attribute(attr="oscillate", value=oscillating) def set_preset_mode(self, preset_mode: str): + """ + set_preset_mode + """ self._device.set_attribute(attr="mode", value=preset_mode.capitalize()) @property def percentage(self): + """ + percentage + """ return round(self.fan_speed * self.percentage_step) def set_percentage(self, percentage: int): + """ + set_percentage + """ fan_speed = round(percentage / self.percentage_step) self._device.set_attribute(attr="fan_speed", value=fan_speed) async def async_set_percentage(self, percentage: int): + """ + async_set_percentage + """ if percentage == 0: await self.async_turn_off() else: await self.hass.async_add_executor_job(self.set_percentage, percentage) def update_state(self, status): + """ + update_state + """ try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) class MideaFAFan(MideaFan): + """ + MideaFAFan + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) - self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE | FanEntityFeature.PRESET_MODE + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED + | FanEntityFeature.OSCILLATE + | FanEntityFeature.PRESET_MODE + ) self._attr_speed_count = self._device.speed_count class MideaB6Fan(MideaFan): + """ + MideaB6Fan + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) - self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + ) self._attr_speed_count = self._device.speed_count class MideaACFreshAirFan(MideaFan): + """ + MideaACFreshAirFan + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) - self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + ) self._attr_speed_count = 100 @property def preset_modes(self): + """ + preset_modes + """ return self._device.fresh_air_fan_speeds @property def state(self): - return STATE_ON if self._device.get_attribute(ACAttributes.fresh_air_power) else STATE_OFF + """ + state + """ + return ( + STATE_ON + if self._device.get_attribute(ACAttributes.fresh_air_power) + else STATE_OFF + ) @property def is_on(self) -> bool: + """ + is_on + """ return self.state == STATE_ON @property def fan_speed(self): + """ + fan_speed + """ return self._device.get_attribute(ACAttributes.fresh_air_fan_speed) def turn_on(self, percentage, preset_mode, **kwargs): + """ + turn_on + """ self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=True) def turn_off(self): + """ + turn_off + """ self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=False) def toggle(self): + """ + toggle + """ toggle = not self.is_on self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=toggle) def set_percentage(self, percentage: int): + """ + set_percentage + """ fan_speed = int(percentage / self.percentage_step + 0.5) - self._device.set_attribute(attr=ACAttributes.fresh_air_fan_speed, value=fan_speed) + self._device.set_attribute( + attr=ACAttributes.fresh_air_fan_speed, value=fan_speed + ) def set_preset_mode(self, preset_mode: str): + """ + set_preset_mode + """ self._device.set_attribute(attr=ACAttributes.fresh_air_mode, value=preset_mode) @property def preset_mode(self): + """ + preset_mode + """ return self._device.get_attribute(attr=ACAttributes.fresh_air_mode) class MideaCEFan(MideaFan): + """ + MideaCEFan + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) - self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + ) self._attr_speed_count = self._device.speed_count def turn_on(self, percentage, preset_mode, **kwargs): + """ + turn_on + """ self._device.set_attribute(attr=CEAttributes.power, value=True) async def async_set_percentage(self, percentage: int): + """ + async_set_percentage + """ await self.hass.async_add_executor_job(self.set_percentage, percentage) class Midea40Fan(MideaFan): + """ + Midea40Fan + """ + def __init__(self, device, entity_key): + """ + __init__ + """ super().__init__(device, entity_key) - self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE + ) self._attr_speed_count = 2 @property def state(self): - return STATE_ON if self._device.get_attribute(attr=X40Attributes.fan_speed) > 0 else STATE_OFF + """ + state + """ + return ( + STATE_ON + if self._device.get_attribute(attr=X40Attributes.fan_speed) > 0 + else STATE_OFF + ) def turn_on(self, percentage, preset_mode, **kwargs): + """ + turn_on + """ self._device.set_attribute(attr=X40Attributes.fan_speed, value=1) def turn_off(self): + """ + turn_off + """ self._device.set_attribute(attr=X40Attributes.fan_speed, value=0) diff --git a/custom_components/midea_ac_lan/humidifier.py b/custom_components/midea_ac_lan/humidifier.py index ae506b91..35988137 100644 --- a/custom_components/midea_ac_lan/humidifier.py +++ b/custom_components/midea_ac_lan/humidifier.py @@ -1,33 +1,28 @@ +import logging + from homeassistant.components.humidifier import ( HumidifierDeviceClass, HumidifierEntity, HumidifierEntityFeature, ) -from homeassistant.const import ( - Platform, - CONF_DEVICE_ID, - CONF_SWITCHES, -) -from .const import ( - DOMAIN, - DEVICES, -) +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform + +from .const import DEVICES, DOMAIN from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity -import logging _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get( - CONF_SWITCHES, [] - ) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.HUMIDIFIER and (config.get("default") or entity_key in extra_switches): + if config["type"] == Platform.HUMIDIFIER and ( + config.get("default") or entity_key in extra_switches + ): if device.device_type == 0xA1: devs.append(MideaA1Humidifier(device, entity_key)) if device.device_type == 0xFD: @@ -79,7 +74,9 @@ def update_state(self, status): try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) class MideaA1Humidifier(MideaHumidifier): diff --git a/custom_components/midea_ac_lan/light.py b/custom_components/midea_ac_lan/light.py index 5834574d..91747b87 100644 --- a/custom_components/midea_ac_lan/light.py +++ b/custom_components/midea_ac_lan/light.py @@ -5,25 +5,19 @@ ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, - LightEntity, - LightEntityFeature, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, + LightEntity, + LightEntityFeature, ) -from homeassistant.const import ( - Platform, - CONF_DEVICE_ID, - CONF_SWITCHES, -) -from .const import ( - DOMAIN, - DEVICES -) +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform + +from .const import DEVICES, DOMAIN from .midea.devices.x13.device import DeviceAttributes as X13Attributes -from .midea_entity import MideaEntity from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity _LOGGER = logging.getLogger(__name__) @@ -31,12 +25,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get( - CONF_SWITCHES, [] - ) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.LIGHT and (config.get("default") or entity_key in extra_switches): + if config["type"] == Platform.LIGHT and ( + config.get("default") or entity_key in extra_switches + ): devs.append(MideaLight(device, entity_key)) async_add_entities(devs) @@ -110,7 +104,9 @@ def turn_on(self, **kwargs: Any): if key == ATTR_BRIGHTNESS: self._device.set_attribute(attr=X13Attributes.brightness, value=value) if key == ATTR_COLOR_TEMP: - self._device.set_attribute(attr=X13Attributes.color_temperature, value=round(1000000 / value)) + self._device.set_attribute( + attr=X13Attributes.color_temperature, value=round(1000000 / value) + ) if key == ATTR_EFFECT: self._device.set_attribute(attr=X13Attributes.effect, value=value) @@ -121,4 +117,6 @@ def update_state(self, status): try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) diff --git a/custom_components/midea_ac_lan/lock.py b/custom_components/midea_ac_lan/lock.py index 91592496..e846ece3 100644 --- a/custom_components/midea_ac_lan/lock.py +++ b/custom_components/midea_ac_lan/lock.py @@ -1,23 +1,15 @@ -from .midea_entity import MideaEntity -from .midea_devices import MIDEA_DEVICES from homeassistant.components.lock import LockEntity -from homeassistant.const import ( - Platform, - CONF_DEVICE_ID, - CONF_SWITCHES -) -from .const import ( - DOMAIN, - DEVICES, -) +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get( - CONF_SWITCHES, [] - ) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) locks = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.LOCK and entity_key in extra_switches: diff --git a/custom_components/midea_ac_lan/midea/backports/enum.py b/custom_components/midea_ac_lan/midea/backports/enum.py index 03b359ae..6a6c1b0c 100644 --- a/custom_components/midea_ac_lan/midea/backports/enum.py +++ b/custom_components/midea_ac_lan/midea/backports/enum.py @@ -1,4 +1,5 @@ """Enum backports from standard lib.""" + from __future__ import annotations from enum import Enum diff --git a/custom_components/midea_ac_lan/midea/core/cloud.py b/custom_components/midea_ac_lan/midea/core/cloud.py index fe29427a..f686fabb 100644 --- a/custom_components/midea_ac_lan/midea/core/cloud.py +++ b/custom_components/midea_ac_lan/midea/core/cloud.py @@ -1,12 +1,19 @@ -import logging -import time +import base64 import datetime import json -import base64 +import logging +import time +from secrets import token_hex from threading import Lock + from aiohttp import ClientSession -from secrets import token_hex -from .security import CloudSecurity, MeijuCloudSecurity, MSmartCloudSecurity, MideaAirSecurity + +from .security import ( + CloudSecurity, + MeijuCloudSecurity, + MideaAirSecurity, + MSmartCloudSecurity, +) _LOGGER = logging.getLogger(__name__) @@ -16,16 +23,22 @@ "app_id": "900", "app_key": "46579c15", "login_key": "ad0ee21d48a64bf49f4fb583ab76e799", - "iot_key": bytes.fromhex(format(9795516279659324117647275084689641883661667, 'x')).decode(), - "hmac_key": bytes.fromhex(format(117390035944627627450677220413733956185864939010425, 'x')).decode(), + "iot_key": bytes.fromhex( + format(9795516279659324117647275084689641883661667, "x") + ).decode(), + "hmac_key": bytes.fromhex( + format(117390035944627627450677220413733956185864939010425, "x") + ).decode(), "api_url": "https://mp-prod.smartmidea.net/mas/v5/app/proxy?alias=", }, "MSmartHome": { "class_name": "MSmartHomeCloud", "app_id": "1010", "app_key": "ac21b9f9cbfe4ca5a88562ef25e2b768", - "iot_key": bytes.fromhex(format(7882822598523843940, 'x')).decode(), - "hmac_key": bytes.fromhex(format(117390035944627627450677220413733956185864939010425, 'x')).decode(), + "iot_key": bytes.fromhex(format(7882822598523843940, "x")).decode(), + "hmac_key": bytes.fromhex( + format(117390035944627627450677220413733956185864939010425, "x") + ).decode(), "api_url": "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=", }, "Midea Air": { @@ -45,28 +58,28 @@ "app_id": "1005", "app_key": "434a209a5ce141c3b726de067835d7f0", "api_url": "https://mapp.appsmb.com", - } + }, } default_keys = { 99: { "token": "ee755a84a115703768bcc7c6c13d3d629aa416f1e2fd798beb9f78cbb1381d09" - "1cc245d7b063aad2a900e5b498fbd936c811f5d504b2e656d4f33b3bbc6d1da3", - "key": "ed37bd31558a4b039aaf4e7a7a59aa7a75fd9101682045f69baf45d28380ae5c" + "1cc245d7b063aad2a900e5b498fbd936c811f5d504b2e656d4f33b3bbc6d1da3", + "key": "ed37bd31558a4b039aaf4e7a7a59aa7a75fd9101682045f69baf45d28380ae5c", } } class MideaCloud: def __init__( - self, - session: ClientSession, - security: CloudSecurity, - app_id: str, - app_key: str, - account: str, - password: str, - api_url: str + self, + session: ClientSession, + security: CloudSecurity, + app_id: str, + app_key: str, + account: str, + password: str, + api_url: str, ): self._device_id = CloudSecurity.get_deviceid(account) self._session = session @@ -87,38 +100,36 @@ def _make_general_data(self): async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: header = header or {} if not data.get("reqId"): - data.update({ - "reqId": token_hex(16) - }) + data.update({"reqId": token_hex(16)}) if not data.get("stamp"): - data.update({ - "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S") - }) + data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) random = str(int(time.time())) url = self._api_url + endpoint dump_data = json.dumps(data) sign = self._security.sign("", dump_data, random) - header.update({ - "content-type": "application/json; charset=utf-8", - "secretVersion": "1", - "sign": sign, - "random": random, - }) + header.update( + { + "content-type": "application/json; charset=utf-8", + "secretVersion": "1", + "sign": sign, + "random": random, + } + ) if self._uid is not None: - header.update({ - "uid": self._uid - }) + header.update({"uid": self._uid}) if self._access_token is not None: - header.update({ - "accessToken": self._access_token - }) + header.update({"accessToken": self._access_token}) response: dict = {"code": -1} for i in range(0, 3): try: with self._api_lock: - r = await self._session.request("POST", url, headers=header, data=dump_data, timeout=10) + r = await self._session.request( + "POST", url, headers=header, data=dump_data, timeout=10 + ) raw = await r.read() - _LOGGER.debug(f"Midea cloud API url: {url}, data: {data}, response: {raw}") + _LOGGER.debug( + f"Midea cloud API url: {url}, data: {data}, response: {raw}" + ) response = json.loads(raw) break except Exception as e: @@ -129,12 +140,9 @@ async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | N async def _get_login_id(self) -> str | None: data = self._make_general_data() - data.update({ - "loginAccount": f"{self._account}" - }) + data.update({"loginAccount": f"{self._account}"}) if response := await self._api_request( - endpoint="/v1/user/login/id/get", - data=data + endpoint="/v1/user/login/id/get", data=data ): return response.get("loginId") return None @@ -147,19 +155,16 @@ async def get_keys(self, appliance_id: int): for method in [1, 2]: udp_id = self._security.get_udp_id(appliance_id, method) data = self._make_general_data() - data.update({ - "udpid": udp_id - }) + data.update({"udpid": udp_id}) response = await self._api_request( - endpoint="/v1/iot/secure/getToken", - data=data + endpoint="/v1/iot/secure/getToken", data=data ) if response and "tokenlist" in response: for token in response["tokenlist"]: if token["udpId"] == udp_id: result[method] = { "token": token["token"].lower(), - "key": token["key"].lower() + "key": token["key"].lower(), } result.update(default_keys) return result @@ -177,22 +182,23 @@ async def get_device_info(self, device_id: int): return None async def download_lua( - self, path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", + self, + path: str, + device_type: int, + sn: str, + model_number: str | None, + manufacturer_code: str = "0000", ): raise NotImplementedError() class MeijuCloud(MideaCloud): def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, + self, + cloud_name: str, + session: ClientSession, + account: str, + password: str, ): super().__init__( session=session, @@ -205,7 +211,7 @@ def __init__( app_key=clouds[cloud_name]["app_key"], account=account, password=password, - api_url=clouds[cloud_name]["api_url"] + api_url=clouds[cloud_name]["api_url"], ) async def login(self) -> bool: @@ -216,30 +222,31 @@ async def login(self) -> bool: "iotData": { "clientType": 1, "deviceId": self._device_id, - "iampwd": self._security.encrypt_iam_password(self._login_id, self._password), + "iampwd": self._security.encrypt_iam_password( + self._login_id, self._password + ), "iotAppId": self._app_id, "loginAccount": self._account, - "password": self._security.encrypt_password(self._login_id, self._password), + "password": self._security.encrypt_password( + self._login_id, self._password + ), "reqId": token_hex(16), - "stamp": stamp + "stamp": stamp, }, "data": { "appKey": self._app_key, "deviceId": self._device_id, - "platform": 2 + "platform": 2, }, "timestamp": stamp, - "stamp": stamp + "stamp": stamp, } if response := await self._api_request( - endpoint="/mj/user/login", - data=data + endpoint="/mj/user/login", data=data ): self._access_token = response["mdata"]["accessToken"] self._security.set_aes_keys( - self._security.aes_decrypt_with_fixed_key( - response["key"] - ), None + self._security.aes_decrypt_with_fixed_key(response["key"]), None ) return True @@ -247,24 +254,18 @@ async def login(self) -> bool: async def list_home(self): if response := await self._api_request( - endpoint="/v1/homegroup/list/get", - data={} + endpoint="/v1/homegroup/list/get", data={} ): homes = {} for home in response["homeList"]: - homes.update({ - int(home["homegroupId"]): home["name"] - }) + homes.update({int(home["homegroupId"]): home["name"]}) return homes return None async def list_appliances(self, home_id) -> dict | None: - data = { - "homegroupId": home_id - } + data = {"homegroupId": home_id} if response := await self._api_request( - endpoint="/v1/appliance/home/list/get", - data=data + endpoint="/v1/appliance/home/list/get", data=data ): appliances = {} for home in response.get("homeList") or []: @@ -277,28 +278,37 @@ async def list_appliances(self, home_id) -> dict | None: device_info = { "name": appliance.get("name"), "type": int(appliance.get("type"), 16), - "sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "", + "sn": ( + self._security.aes_decrypt(appliance.get("sn")) + if appliance.get("sn") + else "" + ), "sn8": appliance.get("sn8", "00000000"), "model_number": model_number, - "manufacturer_code": appliance.get("enterpriseCode", "0000"), + "manufacturer_code": appliance.get( + "enterpriseCode", "0000" + ), "model": appliance.get("productModel"), "online": appliance.get("onlineStatus") == "1", } - if device_info.get("sn8") is None or len(device_info.get("sn8")) == 0: + if ( + device_info.get("sn8") is None + or len(device_info.get("sn8")) == 0 + ): device_info["sn8"] = "00000000" - if device_info.get("model") is None or len(device_info.get("model")) == 0: + if ( + device_info.get("model") is None + or len(device_info.get("model")) == 0 + ): device_info["model"] = device_info["sn8"] appliances[int(appliance["applianceCode"])] = device_info return appliances return None async def get_device_info(self, device_id: int): - data = { - "applianceCode": device_id - } + data = {"applianceCode": device_id} if response := await self._api_request( - endpoint="/v1/appliance/info/get", - data=data + endpoint="/v1/appliance/info/get", data=data ): try: model_number = int(response.get("modelNumber", 0)) @@ -307,7 +317,11 @@ async def get_device_info(self, device_id: int): device_info = { "name": response.get("name"), "type": int(response.get("type"), 16), - "sn": self._security.aes_decrypt(response.get("sn")) if response.get("sn") else "", + "sn": ( + self._security.aes_decrypt(response.get("sn")) + if response.get("sn") + else "" + ), "sn8": response.get("sn8", "00000000"), "model_number": model_number, "manufacturer_code": response.get("enterpriseCode", "0000"), @@ -322,30 +336,32 @@ async def get_device_info(self, device_id: int): return None async def download_lua( - self, path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", + self, + path: str, + device_type: int, + sn: str, + model_number: str | None, + manufacturer_code: str = "0000", ): data = { "applianceSn": sn, "applianceType": "0x%02X" % device_type, "applianceMFCode": manufacturer_code, - 'version': "0", - "iotAppId": self._app_id + "version": "0", + "iotAppId": self._app_id, } fnm = None if response := await self._api_request( - endpoint="/v1/appliance/protocol/lua/luaGet", - data=data + endpoint="/v1/appliance/protocol/lua/luaGet", data=data ): res = await self._session.get(response["url"]) if res.status == 200: lua = await res.text() if lua: - stream = ('local bit = require "bit"\n' + - self._security.aes_decrypt_with_fixed_key(lua)) + stream = ( + 'local bit = require "bit"\n' + + self._security.aes_decrypt_with_fixed_key(lua) + ) stream = stream.replace("\r\n", "\n") fnm = f"{path}/{response['fileName']}" with open(fnm, "w") as fp: @@ -355,11 +371,11 @@ async def download_lua( class MSmartHomeCloud(MideaCloud): def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, + self, + cloud_name: str, + session: ClientSession, + account: str, + password: str, ): super().__init__( session=session, @@ -372,7 +388,7 @@ def __init__( app_key=clouds[cloud_name]["app_key"], account=account, password=password, - api_url=clouds[cloud_name]["api_url"] + api_url=clouds[cloud_name]["api_url"], ) self._auth_base = base64.b64encode( f"{self._app_key}:{clouds['MSmartHome']['iot_key']}".encode("ascii") @@ -389,27 +405,22 @@ def _make_general_data(self): "reqId": token_hex(16), "uid": self._uid, "clientType": "1", - "appId": self._app_id + "appId": self._app_id, } async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: header = header or {} - header.update({ - "x-recipe-app": self._app_id, - "authorization": f"Basic {self._auth_base}" - }) + header.update( + {"x-recipe-app": self._app_id, "authorization": f"Basic {self._auth_base}"} + ) return await super()._api_request(endpoint, data, header) async def _re_route(self): data = self._make_general_data() - data.update({ - "userType": "0", - "userName": f"{self._account}" - }) + data.update({"userType": "0", "userName": f"{self._account}"}) if response := await self._api_request( - endpoint="/v1/multicloud/platform/user/route", - data=data + endpoint="/v1/multicloud/platform/user/route", data=data ): if api_url := response.get("masUrl"): self._api_url = api_url @@ -421,36 +432,42 @@ async def login(self) -> bool: iot_data = self._make_general_data() iot_data.pop("uid") stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - iot_data.update({ - "iampwd": self._security.encrypt_iam_password(self._login_id, self._password), - "loginAccount": self._account, - "password": self._security.encrypt_password(self._login_id, self._password), - "stamp": stamp - }) + iot_data.update( + { + "iampwd": self._security.encrypt_iam_password( + self._login_id, self._password + ), + "loginAccount": self._account, + "password": self._security.encrypt_password( + self._login_id, self._password + ), + "stamp": stamp, + } + ) data = { "iotData": iot_data, "data": { "appKey": self._app_key, "deviceId": self._device_id, - "platform": "2" + "platform": "2", }, - "stamp": stamp + "stamp": stamp, } if response := await self._api_request( - endpoint="/mj/user/login", - data=data + endpoint="/mj/user/login", data=data ): self._uid = response["uid"] self._access_token = response["mdata"]["accessToken"] - self._security.set_aes_keys(response["accessToken"], response["randomData"]) + self._security.set_aes_keys( + response["accessToken"], response["randomData"] + ) return True return False async def list_appliances(self, home_id) -> dict | None: data = self._make_general_data() if response := await self._api_request( - endpoint="/v1/appliance/user/list/get", - data=data + endpoint="/v1/appliance/user/list/get", data=data ): appliances = {} for appliance in response["list"]: @@ -461,21 +478,28 @@ async def list_appliances(self, home_id) -> dict | None: device_info = { "name": appliance.get("name"), "type": int(appliance.get("type"), 16), - "sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "", + "sn": ( + self._security.aes_decrypt(appliance.get("sn")) + if appliance.get("sn") + else "" + ), "sn8": "", "model_number": model_number, "manufacturer_code": appliance.get("enterpriseCode", "0000"), "model": "", "online": appliance.get("onlineStatus") == "1", } - device_info["sn8"] = device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" + device_info["sn8"] = ( + device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" + ) device_info["model"] = device_info.get("sn8") appliances[int(appliance["id"])] = device_info return appliances return None async def download_lua( - self, path: str, + self, + path: str, device_type: int, sn: str, model_number: str | None, @@ -490,21 +514,24 @@ async def download_lua( "applianceMFCode": manufacturer_code, "applianceType": "0x%02X" % device_type, "modelNumber": model_number, - "applianceSn": self._security.aes_encrypt_with_fixed_key(sn.encode("ascii")).hex(), + "applianceSn": self._security.aes_encrypt_with_fixed_key( + sn.encode("ascii") + ).hex(), "version": "0", - "encryptedType ": "2" + "encryptedType ": "2", } fnm = None if response := await self._api_request( - endpoint="/v2/luaEncryption/luaGet", - data=data + endpoint="/v2/luaEncryption/luaGet", data=data ): res = await self._session.get(response["url"]) if res.status == 200: lua = await res.text() if lua: - stream = ('local bit = require "bit"\n' + - self._security.aes_decrypt_with_fixed_key(lua)) + stream = ( + 'local bit = require "bit"\n' + + self._security.aes_decrypt_with_fixed_key(lua) + ) stream = stream.replace("\r\n", "\n") fnm = f"{path}/{response['fileName']}" with open(fnm, "w") as fp: @@ -514,22 +541,20 @@ async def download_lua( class MideaAirCloud(MideaCloud): def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, + self, + cloud_name: str, + session: ClientSession, + account: str, + password: str, ): super().__init__( session=session, - security=MideaAirSecurity( - login_key=clouds[cloud_name]["app_key"] - ), + security=MideaAirSecurity(login_key=clouds[cloud_name]["app_key"]), app_id=clouds[cloud_name]["app_id"], app_key=clouds[cloud_name]["app_key"], account=account, password=password, - api_url=clouds[cloud_name]["api_url"] + api_url=clouds[cloud_name]["api_url"], ) self._session_id = None @@ -541,45 +566,37 @@ def _make_general_data(self): "deviceId": self._device_id, "reqId": token_hex(16), "clientType": "1", - "appId": self._app_id + "appId": self._app_id, } if self._session_id is not None: - data.update({ - "sessionId": self._session_id - }) + data.update({"sessionId": self._session_id}) return data async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: header = header or {} if not data.get("reqId"): - data.update({ - "reqId": token_hex(16) - }) + data.update({"reqId": token_hex(16)}) if not data.get("stamp"): - data.update({ - "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S") - }) + data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) url = self._api_url + endpoint sign = self._security.sign(url, data, "") - data.update({ - "sign": sign - }) + data.update({"sign": sign}) if self._uid is not None: - header.update({ - "uid": self._uid - }) + header.update({"uid": self._uid}) if self._access_token is not None: - header.update({ - "accessToken": self._access_token - }) + header.update({"accessToken": self._access_token}) response: dict = {"code": -1} for i in range(0, 3): try: with self._api_lock: - r = await self._session.request("POST", url, headers=header, data=data, timeout=10) + r = await self._session.request( + "POST", url, headers=header, data=data, timeout=10 + ) raw = await r.read() - _LOGGER.debug(f"Midea cloud API url: {url}, data: {data}, response: {raw}") + _LOGGER.debug( + f"Midea cloud API url: {url}, data: {data}, response: {raw}" + ) response = json.loads(raw) break except Exception as e: @@ -592,13 +609,16 @@ async def login(self) -> bool: if login_id := await self._get_login_id(): self._login_id = login_id data = self._make_general_data() - data.update({ - "loginAccount": self._account, - "password": self._security.encrypt_password(self._login_id, self._password), - }) + data.update( + { + "loginAccount": self._account, + "password": self._security.encrypt_password( + self._login_id, self._password + ), + } + ) if response := await self._api_request( - endpoint="/v1/user/login", - data=data + endpoint="/v1/user/login", data=data ): self._access_token = response["accessToken"] self._uid = response["userId"] @@ -609,8 +629,7 @@ async def login(self) -> bool: async def list_appliances(self, home_id) -> dict | None: data = self._make_general_data() if response := await self._api_request( - endpoint="/v1/appliance/user/list/get", - data=data + endpoint="/v1/appliance/user/list/get", data=data ): appliances = {} for appliance in response["list"]: @@ -628,20 +647,21 @@ async def list_appliances(self, home_id) -> dict | None: "model": "", "online": appliance.get("onlineStatus") == "1", } - device_info["sn8"] = device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" + device_info["sn8"] = ( + device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" + ) device_info["model"] = device_info.get("sn8") appliances[int(appliance["id"])] = device_info return appliances return None -def get_midea_cloud(cloud_name: str, session: ClientSession, account: str, password: str) -> MideaCloud | None: +def get_midea_cloud( + cloud_name: str, session: ClientSession, account: str, password: str +) -> MideaCloud | None: cloud = None if cloud_name in clouds.keys(): cloud = globals()[clouds[cloud_name]["class_name"]]( - cloud_name=cloud_name, - session=session, - account=account, - password=password + cloud_name=cloud_name, session=session, account=account, password=password ) return cloud diff --git a/custom_components/midea_ac_lan/midea/core/crc8.py b/custom_components/midea_ac_lan/midea/core/crc8.py index 24fa56c2..3d56f2d2 100644 --- a/custom_components/midea_ac_lan/midea/core/crc8.py +++ b/custom_components/midea_ac_lan/midea/core/crc8.py @@ -1,36 +1,260 @@ crc8_854_table = [ - 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, - 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, - 0x9D, 0xC3, 0x21, 0x7F, 0xFC, 0xA2, 0x40, 0x1E, - 0x5F, 0x01, 0xE3, 0xBD, 0x3E, 0x60, 0x82, 0xDC, - 0x23, 0x7D, 0x9F, 0xC1, 0x42, 0x1C, 0xFE, 0xA0, - 0xE1, 0xBF, 0x5D, 0x03, 0x80, 0xDE, 0x3C, 0x62, - 0xBE, 0xE0, 0x02, 0x5C, 0xDF, 0x81, 0x63, 0x3D, - 0x7C, 0x22, 0xC0, 0x9E, 0x1D, 0x43, 0xA1, 0xFF, - 0x46, 0x18, 0xFA, 0xA4, 0x27, 0x79, 0x9B, 0xC5, - 0x84, 0xDA, 0x38, 0x66, 0xE5, 0xBB, 0x59, 0x07, - 0xDB, 0x85, 0x67, 0x39, 0xBA, 0xE4, 0x06, 0x58, - 0x19, 0x47, 0xA5, 0xFB, 0x78, 0x26, 0xC4, 0x9A, - 0x65, 0x3B, 0xD9, 0x87, 0x04, 0x5A, 0xB8, 0xE6, - 0xA7, 0xF9, 0x1B, 0x45, 0xC6, 0x98, 0x7A, 0x24, - 0xF8, 0xA6, 0x44, 0x1A, 0x99, 0xC7, 0x25, 0x7B, - 0x3A, 0x64, 0x86, 0xD8, 0x5B, 0x05, 0xE7, 0xB9, - 0x8C, 0xD2, 0x30, 0x6E, 0xED, 0xB3, 0x51, 0x0F, - 0x4E, 0x10, 0xF2, 0xAC, 0x2F, 0x71, 0x93, 0xCD, - 0x11, 0x4F, 0xAD, 0xF3, 0x70, 0x2E, 0xCC, 0x92, - 0xD3, 0x8D, 0x6F, 0x31, 0xB2, 0xEC, 0x0E, 0x50, - 0xAF, 0xF1, 0x13, 0x4D, 0xCE, 0x90, 0x72, 0x2C, - 0x6D, 0x33, 0xD1, 0x8F, 0x0C, 0x52, 0xB0, 0xEE, - 0x32, 0x6C, 0x8E, 0xD0, 0x53, 0x0D, 0xEF, 0xB1, - 0xF0, 0xAE, 0x4C, 0x12, 0x91, 0xCF, 0x2D, 0x73, - 0xCA, 0x94, 0x76, 0x28, 0xAB, 0xF5, 0x17, 0x49, - 0x08, 0x56, 0xB4, 0xEA, 0x69, 0x37, 0xD5, 0x8B, - 0x57, 0x09, 0xEB, 0xB5, 0x36, 0x68, 0x8A, 0xD4, - 0x95, 0xCB, 0x29, 0x77, 0xF4, 0xAA, 0x48, 0x16, - 0xE9, 0xB7, 0x55, 0x0B, 0x88, 0xD6, 0x34, 0x6A, - 0x2B, 0x75, 0x97, 0xC9, 0x4A, 0x14, 0xF6, 0xA8, - 0x74, 0x2A, 0xC8, 0x96, 0x15, 0x4B, 0xA9, 0xF7, - 0xB6, 0xE8, 0x0A, 0x54, 0xD7, 0x89, 0x6B, 0x35 + 0x00, + 0x5E, + 0xBC, + 0xE2, + 0x61, + 0x3F, + 0xDD, + 0x83, + 0xC2, + 0x9C, + 0x7E, + 0x20, + 0xA3, + 0xFD, + 0x1F, + 0x41, + 0x9D, + 0xC3, + 0x21, + 0x7F, + 0xFC, + 0xA2, + 0x40, + 0x1E, + 0x5F, + 0x01, + 0xE3, + 0xBD, + 0x3E, + 0x60, + 0x82, + 0xDC, + 0x23, + 0x7D, + 0x9F, + 0xC1, + 0x42, + 0x1C, + 0xFE, + 0xA0, + 0xE1, + 0xBF, + 0x5D, + 0x03, + 0x80, + 0xDE, + 0x3C, + 0x62, + 0xBE, + 0xE0, + 0x02, + 0x5C, + 0xDF, + 0x81, + 0x63, + 0x3D, + 0x7C, + 0x22, + 0xC0, + 0x9E, + 0x1D, + 0x43, + 0xA1, + 0xFF, + 0x46, + 0x18, + 0xFA, + 0xA4, + 0x27, + 0x79, + 0x9B, + 0xC5, + 0x84, + 0xDA, + 0x38, + 0x66, + 0xE5, + 0xBB, + 0x59, + 0x07, + 0xDB, + 0x85, + 0x67, + 0x39, + 0xBA, + 0xE4, + 0x06, + 0x58, + 0x19, + 0x47, + 0xA5, + 0xFB, + 0x78, + 0x26, + 0xC4, + 0x9A, + 0x65, + 0x3B, + 0xD9, + 0x87, + 0x04, + 0x5A, + 0xB8, + 0xE6, + 0xA7, + 0xF9, + 0x1B, + 0x45, + 0xC6, + 0x98, + 0x7A, + 0x24, + 0xF8, + 0xA6, + 0x44, + 0x1A, + 0x99, + 0xC7, + 0x25, + 0x7B, + 0x3A, + 0x64, + 0x86, + 0xD8, + 0x5B, + 0x05, + 0xE7, + 0xB9, + 0x8C, + 0xD2, + 0x30, + 0x6E, + 0xED, + 0xB3, + 0x51, + 0x0F, + 0x4E, + 0x10, + 0xF2, + 0xAC, + 0x2F, + 0x71, + 0x93, + 0xCD, + 0x11, + 0x4F, + 0xAD, + 0xF3, + 0x70, + 0x2E, + 0xCC, + 0x92, + 0xD3, + 0x8D, + 0x6F, + 0x31, + 0xB2, + 0xEC, + 0x0E, + 0x50, + 0xAF, + 0xF1, + 0x13, + 0x4D, + 0xCE, + 0x90, + 0x72, + 0x2C, + 0x6D, + 0x33, + 0xD1, + 0x8F, + 0x0C, + 0x52, + 0xB0, + 0xEE, + 0x32, + 0x6C, + 0x8E, + 0xD0, + 0x53, + 0x0D, + 0xEF, + 0xB1, + 0xF0, + 0xAE, + 0x4C, + 0x12, + 0x91, + 0xCF, + 0x2D, + 0x73, + 0xCA, + 0x94, + 0x76, + 0x28, + 0xAB, + 0xF5, + 0x17, + 0x49, + 0x08, + 0x56, + 0xB4, + 0xEA, + 0x69, + 0x37, + 0xD5, + 0x8B, + 0x57, + 0x09, + 0xEB, + 0xB5, + 0x36, + 0x68, + 0x8A, + 0xD4, + 0x95, + 0xCB, + 0x29, + 0x77, + 0xF4, + 0xAA, + 0x48, + 0x16, + 0xE9, + 0xB7, + 0x55, + 0x0B, + 0x88, + 0xD6, + 0x34, + 0x6A, + 0x2B, + 0x75, + 0x97, + 0xC9, + 0x4A, + 0x14, + 0xF6, + 0xA8, + 0x74, + 0x2A, + 0xC8, + 0x96, + 0x15, + 0x4B, + 0xA9, + 0xF7, + 0xB6, + 0xE8, + 0x0A, + 0x54, + 0xD7, + 0x89, + 0x6B, + 0x35, ] diff --git a/custom_components/midea_ac_lan/midea/core/device.py b/custom_components/midea_ac_lan/midea/core/device.py index e9fee776..85b2aa71 100644 --- a/custom_components/midea_ac_lan/midea/core/device.py +++ b/custom_components/midea_ac_lan/midea/core/device.py @@ -1,20 +1,27 @@ import threading + try: from enum import StrEnum except ImportError: from ..backports.enum import StrEnum + +import logging +import socket +import time from enum import IntEnum -from .security import LocalSecurity, MSGTYPE_HANDSHAKE_REQUEST, MSGTYPE_ENCRYPTED_REQUEST -from .packet_builder import PacketBuilder + from .message import ( - MessageType, - MessageQuestCustom, + MessageApplianceResponse, MessageQueryAppliance, - MessageApplianceResponse + MessageQuestCustom, + MessageType, +) +from .packet_builder import PacketBuilder +from .security import ( + MSGTYPE_ENCRYPTED_REQUEST, + MSGTYPE_HANDSHAKE_REQUEST, + LocalSecurity, ) -import socket -import logging -import time _LOGGER = logging.getLogger(__name__) @@ -42,18 +49,20 @@ class ParseMessageResult(IntEnum): class MiedaDevice(threading.Thread): - def __init__(self, - name: str, - device_id: int, - device_type: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - attributes: dict): + def __init__( + self, + name: str, + device_id: int, + device_type: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + attributes: dict, + ): threading.Thread.__init__(self) self._attributes = attributes if attributes else {} self._socket = None @@ -122,7 +131,9 @@ def connect(self, refresh_status=True): try: self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.settimeout(10) - _LOGGER.debug(f"[{self._device_id}] Connecting to {self._ip_address}:{self._port}") + _LOGGER.debug( + f"[{self._device_id}] Connecting to {self._ip_address}:{self._port}" + ) self._socket.connect((self._ip_address, self._port)) _LOGGER.debug(f"[{self._device_id}] Connected") if self._protocol == 3: @@ -143,20 +154,21 @@ def connect(self, refresh_status=True): except RefreshFailed: _LOGGER.debug(f"[{self._device_id}] Refresh status is timed out") except Exception as e: - _LOGGER.error(f"[{self._device_id}] Unknown error: {e.__traceback__.tb_frame.f_globals['__file__']}, " - f"{e.__traceback__.tb_lineno}, {repr(e)}") + _LOGGER.error( + f"[{self._device_id}] Unknown error: {e.__traceback__.tb_frame.f_globals['__file__']}, " + f"{e.__traceback__.tb_lineno}, {repr(e)}" + ) self.enable_device(False) return False def authenticate(self): - request = self._security.encode_8370( - self._token, MSGTYPE_HANDSHAKE_REQUEST) + request = self._security.encode_8370(self._token, MSGTYPE_HANDSHAKE_REQUEST) _LOGGER.debug(f"[{self._device_id}] Handshaking") self._socket.send(request) response = self._socket.recv(512) if len(response) < 20: raise AuthException() - response = response[8: 72] + response = response[8:72] self._security.tcp_key(response, self._key) def send_message(self, data): @@ -169,7 +181,9 @@ def send_message_v2(self, data): if self._socket is not None: self._socket.send(data) else: - _LOGGER.debug(f"[{self._device_id}] Send failure, device disconnected, data: {data.hex()}") + _LOGGER.debug( + f"[{self._device_id}] Send failure, device disconnected, data: {data.hex()}" + ) def send_message_v3(self, data, msg_type=MSGTYPE_ENCRYPTED_REQUEST): data = self._security.encode_8370(data, msg_type) @@ -205,8 +219,10 @@ def refresh_status(self, wait_response=False): except socket.timeout: error_count += 1 self._unsupported_protocol.append(cmd.__class__.__name__) - _LOGGER.debug(f"[{self._device_id}] Does not supports " - f"the protocol {cmd.__class__.__name__}, ignored") + _LOGGER.debug( + f"[{self._device_id}] Does not supports " + f"the protocol {cmd.__class__.__name__}, ignored" + ) except ResponseException: error_count += 1 else: @@ -220,7 +236,9 @@ def pre_process_message(self, msg): self._appliance_query = False _LOGGER.debug(f"[{self.device_id}] Received: {message}") self._protocol_version = message.protocol_version - _LOGGER.debug(f"[{self._device_id}] Device protocol version: {self._protocol_version}") + _LOGGER.debug( + f"[{self._device_id}] Device protocol version: {self._protocol_version}" + ) return False return True @@ -252,9 +270,13 @@ def parse_message(self, msg): if len(status) > 0: self.update_all(status) else: - _LOGGER.debug(f"[{self._device_id}] Unidentified protocol") + _LOGGER.debug( + f"[{self._device_id}] Unidentified protocol" + ) except Exception as e: - _LOGGER.error(f"[{self._device_id}] Error in process message, msg = {e}") + _LOGGER.error( + f"[{self._device_id}] Error in process message, msg = {e}" + ) else: _LOGGER.warning( f"[{self._device_id}] Illegal payload, " @@ -278,12 +300,16 @@ def process_message(self, msg): raise NotImplementedError def send_command(self, cmd_type, cmd_body: bytearray): - cmd = MessageQuestCustom(self._device_type, self._protocol_version, cmd_type, cmd_body) + cmd = MessageQuestCustom( + self._device_type, self._protocol_version, cmd_type, cmd_body + ) try: self.build_send(cmd) except socket.error as e: - _LOGGER.debug(f"[{self._device_id}] Interface send_command failure, {repr(e)}, " - f"cmd_type: {cmd_type}, cmd_body: {cmd_body.hex()}") + _LOGGER.debug( + f"[{self._device_id}] Interface send_command failure, {repr(e)}, " + f"cmd_type: {cmd_type}, cmd_body: {cmd_body.hex()}" + ) def send_heartbeat(self): msg = PacketBuilder(self._device_id, bytearray([0x00])).finalize(msg_type=0) @@ -373,8 +399,10 @@ def run(self): self.close_socket() break except Exception as e: - _LOGGER.error(f"[{self._device_id}] Unknown error :{e.__traceback__.tb_frame.f_globals['__file__']}, " - f"{e.__traceback__.tb_lineno}, {repr(e)}") + _LOGGER.error( + f"[{self._device_id}] Unknown error :{e.__traceback__.tb_frame.f_globals['__file__']}, " + f"{e.__traceback__.tb_lineno}, {repr(e)}" + ) self.close_socket() break diff --git a/custom_components/midea_ac_lan/midea/core/discover.py b/custom_components/midea_ac_lan/midea/core/discover.py index d3f808a7..52619809 100644 --- a/custom_components/midea_ac_lan/midea/core/discover.py +++ b/custom_components/midea_ac_lan/midea/core/discover.py @@ -1,8 +1,11 @@ import logging import socket -import ifaddr from ipaddress import IPv4Network + +import ifaddr + from .security import LocalSecurity + try: import xml.etree.cElementTree as ET except ImportError: @@ -10,27 +13,143 @@ _LOGGER = logging.getLogger(__name__) -BROADCAST_MSG = bytearray([ - 0x5a, 0x5a, 0x01, 0x11, 0x48, 0x00, 0x92, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7f, 0x75, 0xbd, 0x6b, 0x3e, 0x4f, 0x8b, 0x76, - 0x2e, 0x84, 0x9c, 0x6e, 0x57, 0x8d, 0x65, 0x90, - 0x03, 0x6e, 0x9d, 0x43, 0x42, 0xa5, 0x0f, 0x1f, - 0x56, 0x9e, 0xb8, 0xec, 0x91, 0x8e, 0x92, 0xe5 -]) - -DEVICE_INFO_MSG = bytearray([ - 0x5a, 0x5a, 0x15, 0x00, 0x00, 0x38, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x33, 0x05, - 0x13, 0x06, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xca, 0x8d, 0x9b, 0xf9, 0xa0, 0x30, 0x1a, 0xe3, - 0xb7, 0xe4, 0x2d, 0x53, 0x49, 0x47, 0x62, 0xbe -]) +BROADCAST_MSG = bytearray( + [ + 0x5A, + 0x5A, + 0x01, + 0x11, + 0x48, + 0x00, + 0x92, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7F, + 0x75, + 0xBD, + 0x6B, + 0x3E, + 0x4F, + 0x8B, + 0x76, + 0x2E, + 0x84, + 0x9C, + 0x6E, + 0x57, + 0x8D, + 0x65, + 0x90, + 0x03, + 0x6E, + 0x9D, + 0x43, + 0x42, + 0xA5, + 0x0F, + 0x1F, + 0x56, + 0x9E, + 0xB8, + 0xEC, + 0x91, + 0x8E, + 0x92, + 0xE5, + ] +) + +DEVICE_INFO_MSG = bytearray( + [ + 0x5A, + 0x5A, + 0x15, + 0x00, + 0x00, + 0x38, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x27, + 0x33, + 0x05, + 0x13, + 0x06, + 0x14, + 0x14, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0xE8, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xCA, + 0x8D, + 0x9B, + 0xF9, + 0xA0, + 0x30, + 0x1A, + 0xE3, + 0xB7, + 0xE4, + 0x2D, + 0x53, + 0x49, + 0x47, + 0x62, + 0xBE, + ] +) def discover(discover_type=None, ip_address=None): @@ -57,7 +176,9 @@ def discover(discover_type=None, ip_address=None): data, addr = sock.recvfrom(512) ip = addr[0] _LOGGER.debug(f"Received response from {addr}: {data.hex()}") - if len(data) >= 104 and (data[:2].hex() == "5a5a" or data[8:10].hex() == "5a5a"): + if len(data) >= 104 and ( + data[:2].hex() == "5a5a" or data[8:10].hex() == "5a5a" + ): if data[:2].hex() == "5a5a": protocol = 2 elif data[:2].hex() == "8370": @@ -66,25 +187,29 @@ def discover(discover_type=None, ip_address=None): data = data[8:-16] else: continue - device_id = int.from_bytes(bytearray.fromhex(data[20:26].hex()), "little") + device_id = int.from_bytes( + bytearray.fromhex(data[20:26].hex()), "little" + ) if device_id in found_devices: continue encrypt_data = data[40:-16] reply = security.aes_decrypt(encrypt_data) _LOGGER.debug(f"Declassified reply: {reply.hex()}") - ssid = reply[41:41 + reply[40]].decode("utf-8") + ssid = reply[41 : 41 + reply[40]].decode("utf-8") device_type = ssid.split("_")[1] port = bytes2port(reply[4:8]) model = reply[17:25].decode("utf-8") sn = reply[8:40].decode("utf-8") elif data[:6].hex() == "3c3f786d6c20": protocol = 1 - root = ET.fromstring(data.decode( - encoding="utf-8", errors="replace")) + root = ET.fromstring(data.decode(encoding="utf-8", errors="replace")) child = root.find("body/device") m = child.attrib - port, sn, device_type = int(m["port"]), m["apc_sn"], str( - hex(int(m["apc_type"])))[2:] + port, sn, device_type = ( + int(m["port"]), + m["apc_sn"], + str(hex(int(m["apc_type"])))[2:], + ) response = get_device_info(ip, int(port)) device_id = get_id_from_response(response) if len(sn) == 32: @@ -102,7 +227,7 @@ def discover(discover_type=None, ip_address=None): "port": port, "model": model, "sn": sn, - "protocol": protocol + "protocol": protocol, } if len(discover_type) == 0 or device.get("type") in discover_type: found_devices[device_id] = device @@ -148,13 +273,16 @@ def get_device_info(device_ip, device_port: int): sock.settimeout(8) device_address = (device_ip, device_port) sock.connect(device_address) - _LOGGER.debug(f"Sending to {device_ip}:{device_port} {DEVICE_INFO_MSG.hex()}") + _LOGGER.debug( + f"Sending to {device_ip}:{device_port} {DEVICE_INFO_MSG.hex()}" + ) sock.sendall(DEVICE_INFO_MSG) response = sock.recv(512) except socket.timeout: - _LOGGER.warning(f"Connect the device {device_ip}:{device_port} timed out for 8s. " - f"Don't care about a small amount of this. if many maybe not support." - ) + _LOGGER.warning( + f"Connect the device {device_ip}:{device_port} timed out for 8s. " + f"Don't care about a small amount of this. if many maybe not support." + ) except socket.error: _LOGGER.warning(f"Can't connect to Device {device_ip}:{device_port}") return response @@ -166,8 +294,14 @@ def enum_all_broadcast(): for adapter in adapters: for ip in adapter.ips: if ip.is_IPv4 and ip.network_prefix < 32: - local_network = IPv4Network(f"{ip.ip}/{ip.network_prefix}", strict=False) - if local_network.is_private and not local_network.is_loopback and not local_network.is_link_local: + local_network = IPv4Network( + f"{ip.ip}/{ip.network_prefix}", strict=False + ) + if ( + local_network.is_private + and not local_network.is_loopback + and not local_network.is_link_local + ): addr = str(local_network.broadcast_address) if addr not in nets: nets.append(addr) diff --git a/custom_components/midea_ac_lan/midea/core/message.py b/custom_components/midea_ac_lan/midea/core/message.py index 5e38a6d8..8a6222f9 100644 --- a/custom_components/midea_ac_lan/midea/core/message.py +++ b/custom_components/midea_ac_lan/midea/core/message.py @@ -18,12 +18,12 @@ class MessageCheckSumError(Exception): class MessageType(IntEnum): - set = 0x02, - query = 0x03, - notify1 = 0x04, - notify2 = 0x05, - exception = 0x06, - exception2 = 0x0A, + set = (0x02,) + query = (0x03,) + notify1 = (0x04,) + notify2 = (0x05,) + exception = (0x06,) + exception2 = (0x0A,) query_appliance = 0xA0 @@ -38,7 +38,7 @@ def __init__(self): @staticmethod def checksum(data): - return (~ sum(data) + 1) & 0xff + return (~sum(data) + 1) & 0xFF @property def header(self): @@ -85,7 +85,9 @@ def __str__(self) -> str: "header": self.header.hex(), "body": self.body.hex(), "message type": "%02x" % self._message_type, - "body type": ("%02x" % self._body_type) if self._body_type is not None else "None" + "body type": ( + ("%02x" % self._body_type) if self._body_type is not None else "None" + ), } return str(output) @@ -101,26 +103,29 @@ def __init__(self, device_type, protocol_version, message_type, body_type): @property def header(self): length = self.HEADER_LENGTH + len(self.body) - return bytearray([ - # flag - 0xAA, - # length - length, - # device type - self.device_type, - # frame checksum - 0x00, # self._device_type ^ length, - # unused - 0x00, 0x00, - # frame ID - 0x00, - # frame protocol version - 0x00, - # device protocol version - self.protocol_version, - # frame type - self.message_type - ]) + return bytearray( + [ + # flag + 0xAA, + # length + length, + # device type + self.device_type, + # frame checksum + 0x00, # self._device_type ^ length, + # unused + 0x00, + 0x00, + # frame ID + 0x00, + # frame protocol version + 0x00, + # device protocol version + self.protocol_version, + # frame type + self.message_type, + ] + ) @property def _body(self): @@ -147,7 +152,8 @@ def __init__(self, device_type, protocol_version, cmd_type, cmd_body): device_type=device_type, protocol_version=protocol_version, message_type=cmd_type, - body_type=None) + body_type=None, + ) self._cmd_body = cmd_body @property @@ -165,7 +171,8 @@ def __init__(self, device_type): device_type=device_type, protocol_version=0, message_type=MessageType.query_appliance, - body_type=None) + body_type=None, + ) @property def _body(self): @@ -196,7 +203,7 @@ def read_byte(body, byte, default_value=0): class NewProtocolMessageBody(MessageBody): def __init__(self, body, bt): super().__init__(body) - if bt == 0xb5: + if bt == 0xB5: self._pack_len = 4 else: self._pack_len = 5 @@ -220,9 +227,9 @@ def parse(self): pos += 1 length = self.data[pos + 2] if length > 0: - value = self.data[pos + 3: pos + 3 + length] + value = self.data[pos + 3 : pos + 3 + length] result[param] = value - pos += (3 + length) + pos += 3 + length except IndexError: # Some device used non-standard new-protocol(美的乐享三代中央空调?) _LOGGER.debug(f"Non-standard new-protocol {self.data.hex()}") @@ -234,11 +241,11 @@ def __init__(self, message): super().__init__() if message is None or len(message) < self.HEADER_LENGTH + 1: raise MessageLenError - self._header = message[:self.HEADER_LENGTH] + self._header = message[: self.HEADER_LENGTH] self.protocol_version = self._header[-2] self.message_type = self._header[-1] self.device_type = self._header[2] - body = message[self.HEADER_LENGTH: -1] + body = message[self.HEADER_LENGTH : -1] self._body = MessageBody(body) self.body_type = self._body.body_type diff --git a/custom_components/midea_ac_lan/midea/core/packet_builder.py b/custom_components/midea_ac_lan/midea/core/packet_builder.py index 3470cd05..49b9770c 100644 --- a/custom_components/midea_ac_lan/midea/core/packet_builder.py +++ b/custom_components/midea_ac_lan/midea/core/packet_builder.py @@ -1,6 +1,7 @@ -from .security import LocalSecurity import datetime +from .security import LocalSecurity + class PacketBuilder: def __init__(self, device_id: int, command): @@ -8,24 +9,58 @@ def __init__(self, device_id: int, command): self.security = LocalSecurity() # aa20ac00000000000003418100ff03ff000200000000000000000000000006f274 # Init the packet with the header data. - self.packet = bytearray([ - # 2 bytes - StaicHeader - 0x5a, 0x5a, - # 2 bytes - mMessageType - 0x01, 0x11, - # 2 bytes - PacketLenght - 0x00, 0x00, - # 2 bytes - 0x20, 0x00, - # 4 bytes - MessageId - 0x00, 0x00, 0x00, 0x00, - # 8 bytes - Date&Time - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - # 6 bytes - mDeviceID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - # 12 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - ]) + self.packet = bytearray( + [ + # 2 bytes - StaicHeader + 0x5A, + 0x5A, + # 2 bytes - mMessageType + 0x01, + 0x11, + # 2 bytes - PacketLenght + 0x00, + 0x00, + # 2 bytes + 0x20, + 0x00, + # 4 bytes - MessageId + 0x00, + 0x00, + 0x00, + 0x00, + # 8 bytes - Date&Time + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + # 6 bytes - mDeviceID + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + # 12 bytes + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) self.packet[12:20] = self.packet_time() self.packet[20:28] = device_id.to_bytes(8, "little") self.command = command @@ -33,7 +68,7 @@ def __init__(self, device_id: int, command): def finalize(self, msg_type=1): if msg_type != 1: self.packet[3] = 0x10 - self.packet[6] = 0x7b + self.packet[6] = 0x7B else: self.packet.extend(self.security.aes_encrypt(self.command)) # PacketLenght @@ -47,14 +82,13 @@ def encode32(self, data: bytearray): @staticmethod def checksum(data): - return (~ sum(data) + 1) & 0xff + return (~sum(data) + 1) & 0xFF @staticmethod def packet_time(): - t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[ - :16] + t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:16] b = bytearray() for i in range(0, len(t), 2): - d = int(t[i:i+2]) + d = int(t[i : i + 2]) b.insert(0, d) return b diff --git a/custom_components/midea_ac_lan/midea/core/security.py b/custom_components/midea_ac_lan/midea/core/security.py index ccf0e96f..6b055a11 100644 --- a/custom_components/midea_ac_lan/midea/core/security.py +++ b/custom_components/midea_ac_lan/midea/core/security.py @@ -1,12 +1,12 @@ -from Crypto.Cipher import AES -from Crypto.Util.Padding import pad, unpad -from Crypto.Util.strxor import strxor -from Crypto.Random import get_random_bytes -from urllib.parse import unquote_plus, urlencode, urlparse +import hmac from hashlib import md5, sha256 from typing import Any -import hmac +from urllib.parse import unquote_plus, urlencode, urlparse +from Crypto.Cipher import AES +from Crypto.Random import get_random_bytes +from Crypto.Util.Padding import pad, unpad +from Crypto.Util.strxor import strxor MSGTYPE_HANDSHAKE_REQUEST = 0x0 MSGTYPE_HANDSHAKE_RESPONSE = 0x1 @@ -21,8 +21,8 @@ def __init__(self, login_key, iot_key, hmac_key, fixed_key=None, fixed_iv=None): self._hmac_key = hmac_key self._aes_key = None self._aes_iv = None - self._fixed_key = format(fixed_key, 'x').encode("ascii") if fixed_key else None - self._fixed_iv = format(fixed_iv, 'x').encode("ascii") if fixed_iv else None + self._fixed_key = format(fixed_key, "x").encode("ascii") if fixed_key else None + self._fixed_iv = format(fixed_iv, "x").encode("ascii") if fixed_iv else None def sign(self, url: str, data: Any, random: str) -> str: msg = self._iot_key @@ -59,7 +59,7 @@ def get_udp_id(appliance_id, method=0): data = bytearray(sha256(bytes_id).digest()) for i in range(0, 16): data[i] ^= data[i + 16] - return data[0: 16].hex() + return data[0:16].hex() def set_aes_keys(self, key, iv): if isinstance(key, str): @@ -103,15 +103,18 @@ def aes_decrypt(self, data, key=None, iv=None): if isinstance(data, str): data = bytes.fromhex(data) if aes_iv is None: # ECB - return unpad(AES.new(aes_key, AES.MODE_ECB).decrypt(data), len(aes_key)).decode() + return unpad( + AES.new(aes_key, AES.MODE_ECB).decrypt(data), len(aes_key) + ).decode() else: # CBC - return unpad(AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).decrypt(data), len(aes_key)).decode() + return unpad( + AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).decrypt(data), len(aes_key) + ).decode() class MeijuCloudSecurity(CloudSecurity): def __init__(self, login_key, iot_key, hmac_key): - super().__init__(login_key, iot_key, hmac_key, - 10864842703515613082) + super().__init__(login_key, iot_key, hmac_key, 10864842703515613082) def encrypt_iam_password(self, login_id, data) -> str: md = md5() @@ -123,9 +126,9 @@ def encrypt_iam_password(self, login_id, data) -> str: class MSmartCloudSecurity(CloudSecurity): def __init__(self, login_key, iot_key, hmac_key): - super().__init__(login_key, iot_key, hmac_key, - 13101328926877700970, - 16429062708050928556) + super().__init__( + login_key, iot_key, hmac_key, 13101328926877700970, 16429062708050928556 + ) def encrypt_iam_password(self, login_id, data) -> str: md = md5() @@ -141,8 +144,8 @@ def set_aes_keys(self, encrypted_key, encrypted_iv): key_digest = sha256(self._login_key.encode("ascii")).hexdigest() tmp_key = key_digest[:16].encode("ascii") tmp_iv = key_digest[16:32].encode("ascii") - self._aes_key = self.aes_decrypt(encrypted_key, tmp_key, tmp_iv).encode('ascii') - self._aes_iv = self.aes_decrypt(encrypted_iv, tmp_key, tmp_iv).encode('ascii') + self._aes_key = self.aes_decrypt(encrypted_key, tmp_key, tmp_iv).encode("ascii") + self._aes_iv = self.aes_decrypt(encrypted_iv, tmp_key, tmp_iv).encode("ascii") class MideaAirSecurity(CloudSecurity): @@ -161,10 +164,13 @@ def __init__(self): self.blockSize = 16 self.iv = b"\0" * 16 self.aes_key = bytes.fromhex( - format(141661095494369103254425781617665632877, 'x') + format(141661095494369103254425781617665632877, "x") ) self.salt = bytes.fromhex( - format(233912452794221312800602098970898185176935770387238278451789080441632479840061417076563, 'x') + format( + 233912452794221312800602098970898185176935770387238278451789080441632479840061417076563, + "x", + ) ) self._tcp_key = None self._request_count = 0 @@ -172,7 +178,9 @@ def __init__(self): def aes_decrypt(self, raw): try: - return unpad(AES.new(self.aes_key, AES.MODE_ECB).decrypt(bytearray(raw)), 16) + return unpad( + AES.new(self.aes_key, AES.MODE_ECB).decrypt(bytearray(raw)), 16 + ) except ValueError: return bytearray(0) @@ -208,7 +216,7 @@ def encode_8370(self, data, msgtype): size, padding = len(data), 0 if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): if (size + 2) % 16 != 0: - padding = 16 - (size + 2 & 0xf) + padding = 16 - (size + 2 & 0xF) size += padding + 32 data += get_random_bytes(padding) header += size.to_bytes(2, "big") @@ -238,7 +246,7 @@ def decode_8370(self, data): if header[4] != 0x20: raise Exception("missing byte 4") padding = header[5] >> 4 - msgtype = header[5] & 0xf + msgtype = header[5] & 0xF data = data[6:] if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): sign = data[-32:] diff --git a/custom_components/midea_ac_lan/midea/devices/__init__.py b/custom_components/midea_ac_lan/midea/devices/__init__.py index fcc5e119..2eddd391 100644 --- a/custom_components/midea_ac_lan/midea/devices/__init__.py +++ b/custom_components/midea_ac_lan/midea/devices/__init__.py @@ -1,7 +1,8 @@ -from homeassistant.core import HomeAssistant from importlib import import_module from types import ModuleType +from homeassistant.core import HomeAssistant + async def async_device_selector( hass: HomeAssistant, @@ -15,7 +16,7 @@ async def async_device_selector( protocol: int, model: str, subtype: int, - customize: str + customize: str, ): try: @@ -29,6 +30,7 @@ async def async_device_selector( def _load_device_module() -> None: """Load all service modules.""" modules.append(import_module(device_path, __package__)) + await hass.async_add_import_executor_job(_load_device_module) device = modules[0].MideaAppliance( @@ -41,7 +43,7 @@ def _load_device_module() -> None: protocol=protocol, model=model, subtype=subtype, - customize=customize + customize=customize, ) except ModuleNotFoundError: device = None diff --git a/custom_components/midea_ac_lan/midea/devices/a1/device.py b/custom_components/midea_ac_lan/midea/devices/a1/device.py index 91b83938..7b3f9474 100644 --- a/custom_components/midea_ac_lan/midea/devices/a1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/a1/device.py @@ -1,13 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageA1Response, - MessageSet -) + +from .message import MessageA1Response, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -30,28 +29,29 @@ class DeviceAttributes(StrEnum): class MideaA1Device(MiedaDevice): - _modes = [ - "Manual", "Continuous", "Auto", "Clothes-Dry", "Shoes-Dry" - ] + _modes = ["Manual", "Continuous", "Auto", "Clothes-Dry", "Shoes-Dry"] _speeds = { - 1: "Lowest", 40: "Low", 60: "Medium", 80: "High", 102: "Auto", 127: "Off" + 1: "Lowest", + 40: "Low", + 60: "Medium", + 80: "High", + 102: "Auto", + 127: "Off", } - _water_level_sets = [ - "25", "50", "75", "100" - ] + _water_level_sets = ["25", "50", "75", "100"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -77,8 +77,9 @@ def __init__( DeviceAttributes.water_level_set: 50, DeviceAttributes.tank_full: None, DeviceAttributes.current_humidity: None, - DeviceAttributes.current_temperature: None - }) + DeviceAttributes.current_temperature: None, + }, + ) @property def modes(self): @@ -93,9 +94,7 @@ def water_level_sets(self): return MideaA1Device._water_level_sets def build_query(self): - return [ - MessageQuery(self._protocol_version) - ] + return [MessageQuery(self._protocol_version)] def process_message(self, msg): message = MessageA1Response(msg) @@ -119,9 +118,13 @@ def process_message(self, msg): self._attributes[status] = str(value) else: self._attributes[status] = value - tank_full = (self._attributes[DeviceAttributes.tank] >= - int(self._attributes[DeviceAttributes.water_level_set])) - if self._attributes[DeviceAttributes.tank_full] is None or self._attributes[DeviceAttributes.tank_full] != tank_full: + tank_full = self._attributes[DeviceAttributes.tank] >= int( + self._attributes[DeviceAttributes.water_level_set] + ) + if ( + self._attributes[DeviceAttributes.tank_full] is None + or self._attributes[DeviceAttributes.tank_full] != tank_full + ): self._attributes[DeviceAttributes.tank_full] = tank_full new_status[str(DeviceAttributes.tank_full)] = tank_full new_status[str(status)] = self._attributes[status] @@ -133,17 +136,26 @@ def make_message_set(self): message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] message.child_lock = self._attributes[DeviceAttributes.child_lock] if self._attributes[DeviceAttributes.mode] in MideaA1Device._modes: - message.mode = MideaA1Device._modes.index(self._attributes[DeviceAttributes.mode]) + 1 + message.mode = ( + MideaA1Device._modes.index(self._attributes[DeviceAttributes.mode]) + 1 + ) else: message.mode = 1 - message.fan_speed = 40 if self._attributes[DeviceAttributes.fan_speed] is None else \ - list(MideaA1Device._speeds.keys())[list(MideaA1Device._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - )] + message.fan_speed = ( + 40 + if self._attributes[DeviceAttributes.fan_speed] is None + else list(MideaA1Device._speeds.keys())[ + list(MideaA1Device._speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + ) + ] + ) message.target_humidity = self._attributes[DeviceAttributes.target_humidity] message.swing = self._attributes[DeviceAttributes.swing] message.anion = self._attributes[DeviceAttributes.anion] - message.water_level_set = int(self._attributes[DeviceAttributes.water_level_set]) + message.water_level_set = int( + self._attributes[DeviceAttributes.water_level_set] + ) return message def set_attribute(self, attr, value): diff --git a/custom_components/midea_ac_lan/midea/devices/a1/message.py b/custom_components/midea_ac_lan/midea/devices/a1/message.py index 1f5fc0e8..00958165 100644 --- a/custom_components/midea_ac_lan/midea/devices/a1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/a1/message.py @@ -1,11 +1,12 @@ from enum import IntEnum + from ...core.crc8 import calculate from ...core.message import ( - MessageType, + MessageBody, MessageRequest, MessageResponse, - MessageBody, - NewProtocolMessageBody + MessageType, + NewProtocolMessageBody, ) @@ -44,17 +45,34 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41) + body_type=0x41, + ) @property def _body(self): - return bytearray([ - 0x81, 0x00, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00 - ]) + return bytearray( + [ + 0x81, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class MessageNewProtocolQuery(MessageA1Base): @@ -62,13 +80,12 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0xB1) + body_type=0xB1, + ) @property def _body(self): - query_params = [ - NewProtocolTags.light - ] + query_params = [NewProtocolTags.light] _body = bytearray([len(query_params)]) for param in query_params: _body.extend([param & 0xFF, param >> 8]) @@ -80,7 +97,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x48) + body_type=0x48, + ) self.power = False self.prompt_tone = False self.mode = 1 @@ -110,20 +128,30 @@ def _body(self): swing = 0x08 if self.swing else 0x00 # byte 13 water_level_set water_level_set = self.water_level_set - return bytearray([ - power | prompt_tone | 0x02, - mode, - fan_speed, - 0x00, 0x00, 0x00, - target_humidity, - child_lock, - anion, - swing, - 0x00, 0x00, - water_level_set, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00 - ]) + return bytearray( + [ + power | prompt_tone | 0x02, + mode, + fan_speed, + 0x00, + 0x00, + 0x00, + target_humidity, + child_lock, + anion, + swing, + 0x00, + 0x00, + water_level_set, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class MessageNewProtocolSet(MessageA1Base): @@ -131,7 +159,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0xB0) + body_type=0xB0, + ) self.light = None @property @@ -143,8 +172,9 @@ def _body(self): payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.light, - value=bytearray([0x01 if self.light else 0x00]) - )) + value=bytearray([0x01 if self.light else 0x00]), + ) + ) payload[0] = pack_count return payload @@ -172,13 +202,17 @@ def __init__(self, body, bt): super().__init__(body, bt) params = self.parse() if NewProtocolTags.light in params: - self.light = (params[NewProtocolTags.light][0] > 0) + self.light = params[NewProtocolTags.light][0] > 0 class MessageA1Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1]: + if self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify1, + ]: if self.body_type in [0xB0, 0xB1, 0xB5]: self.set_body(A1NewProtocolMessageBody(super().body, self.body_type)) else: diff --git a/custom_components/midea_ac_lan/midea/devices/ac/device.py b/custom_components/midea_ac_lan/midea/devices/ac/device.py index 1effb0a5..98025841 100644 --- a/custom_components/midea_ac_lan/midea/devices/ac/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ac/device.py @@ -1,20 +1,23 @@ -import logging import json +import logging + from .message import ( - MessageQuery, - MessageToggleDisplay, - MessageNewProtocolQuery, MessageACResponse, MessageGeneralSet, + MessageNewProtocolQuery, MessageNewProtocolSet, MessagePowerQuery, + MessageQuery, MessageSubProtocolQuery, - MessageSubProtocolSet + MessageSubProtocolSet, + MessageToggleDisplay, ) + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -58,22 +61,27 @@ class DeviceAttributes(StrEnum): class MideaACDevice(MiedaDevice): _fresh_air_fan_speeds = { - 0: "Off", 20: "Silent", 40: "Low", 60: "Medium", 80: "High", 100: "Full" + 0: "Off", + 20: "Silent", + 40: "Low", + 60: "Medium", + 80: "High", + 100: "Full", } _fresh_air_fan_speeds_rev = dict(reversed(_fresh_air_fan_speeds.items())) def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -120,7 +128,8 @@ def __init__( DeviceAttributes.fresh_air_mode: None, DeviceAttributes.fresh_air_1: None, DeviceAttributes.fresh_air_2: None, - }) + }, + ) self._fresh_air_version = None self._default_temperature_step = 0.5 self._temperature_step = None @@ -144,12 +153,12 @@ def build_query(self): return [ MessageSubProtocolQuery(self._protocol_version, 0x10), MessageSubProtocolQuery(self._protocol_version, 0x11), - MessageSubProtocolQuery(self._protocol_version, 0x30) + MessageSubProtocolQuery(self._protocol_version, 0x30), ] return [ MessageQuery(self._protocol_version), MessageNewProtocolQuery(self._protocol_version), - MessagePowerQuery(self._protocol_version) + MessagePowerQuery(self._protocol_version), ] def process_message(self, msg): @@ -179,9 +188,13 @@ def process_message(self, msg): self._attributes[DeviceAttributes.fresh_air_mode] = v else: self._attributes[DeviceAttributes.fresh_air_mode] = "Off" - new_status[DeviceAttributes.fresh_air_mode.value] = self._attributes[DeviceAttributes.fresh_air_mode] - if not self._attributes[DeviceAttributes.power] or \ - (DeviceAttributes.swing_vertical in new_status and self._attributes[DeviceAttributes.swing_vertical]): + new_status[DeviceAttributes.fresh_air_mode.value] = self._attributes[ + DeviceAttributes.fresh_air_mode + ] + if not self._attributes[DeviceAttributes.power] or ( + DeviceAttributes.swing_vertical in new_status + and self._attributes[DeviceAttributes.swing_vertical] + ): self._attributes[DeviceAttributes.indirect_wind] = False new_status[DeviceAttributes.indirect_wind.value] = False if not self._attributes[DeviceAttributes.power]: @@ -198,7 +211,9 @@ def make_message_set(self): message.power = self._attributes[DeviceAttributes.power] message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[DeviceAttributes.target_temperature] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] message.fan_speed = self._attributes[DeviceAttributes.fan_speed] message.swing_vertical = self._attributes[DeviceAttributes.swing_vertical] message.swing_horizontal = self._attributes[DeviceAttributes.swing_horizontal] @@ -220,7 +235,9 @@ def make_subptotocol_message_set(self): message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] message.aux_heating = self._attributes[DeviceAttributes.aux_heating] message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[DeviceAttributes.target_temperature] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] message.fan_speed = self._attributes[DeviceAttributes.fan_speed] message.boost_mode = self._attributes[DeviceAttributes.boost_mode] message.dry = self._attributes[DeviceAttributes.dry] @@ -240,13 +257,15 @@ def make_message_uniq_set(self): def set_attribute(self, attr, value): # if nat a sensor message = None - if attr not in [DeviceAttributes.indoor_temperature, - DeviceAttributes.outdoor_temperature, - DeviceAttributes.indoor_humidity, - DeviceAttributes.full_dust, - DeviceAttributes.total_energy_consumption, - DeviceAttributes.current_energy_consumption, - DeviceAttributes.realtime_power]: + if attr not in [ + DeviceAttributes.indoor_temperature, + DeviceAttributes.outdoor_temperature, + DeviceAttributes.indoor_humidity, + DeviceAttributes.full_dust, + DeviceAttributes.total_energy_consumption, + DeviceAttributes.current_energy_consumption, + DeviceAttributes.realtime_power, + ]: if attr == DeviceAttributes.prompt_tone: self._attributes[DeviceAttributes.prompt_tone] = value self.update_all({DeviceAttributes.prompt_tone.value: value}) @@ -254,9 +273,9 @@ def set_attribute(self, attr, value): message = MessageToggleDisplay(self._protocol_version) message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] elif attr in [ - DeviceAttributes.indirect_wind, - DeviceAttributes.breezeless, - DeviceAttributes.screen_display_alternate + DeviceAttributes.indirect_wind, + DeviceAttributes.breezeless, + DeviceAttributes.screen_display_alternate, ]: message = MessageNewProtocolSet(self._protocol_version) setattr(message, str(attr), value) @@ -267,38 +286,42 @@ def set_attribute(self, attr, value): setattr( message, str(self._fresh_air_version), - [value, self._attributes[DeviceAttributes.fresh_air_fan_speed]] + [value, self._attributes[DeviceAttributes.fresh_air_fan_speed]], ) elif attr == DeviceAttributes.fresh_air_mode: if value in MideaACDevice._fresh_air_fan_speeds.values(): speed = list(MideaACDevice._fresh_air_fan_speeds.keys())[ list(MideaACDevice._fresh_air_fan_speeds.values()).index(value) ] - fresh_air = [True, speed] if speed > 0 else \ - [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]] - message = MessageNewProtocolSet(self._protocol_version) - setattr( - message, - str(self._fresh_air_version), - fresh_air + fresh_air = ( + [True, speed] + if speed > 0 + else [ + False, + self._attributes[DeviceAttributes.fresh_air_fan_speed], + ] ) + message = MessageNewProtocolSet(self._protocol_version) + setattr(message, str(self._fresh_air_version), fresh_air) elif not value: message = MessageNewProtocolSet(self._protocol_version) setattr( message, str(self._fresh_air_version), - [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]] + [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]], ) elif attr == DeviceAttributes.fresh_air_fan_speed: if self._fresh_air_version is not None: message = MessageNewProtocolSet(self._protocol_version) - fresh_air = [True, value] if value > 0 else \ - [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]] - setattr( - message, - str(self._fresh_air_version), - fresh_air + fresh_air = ( + [True, value] + if value > 0 + else [ + False, + self._attributes[DeviceAttributes.fresh_air_fan_speed], + ] ) + setattr(message, str(self._fresh_air_version), fresh_air) elif attr in self._attributes.keys(): message = self.make_message_uniq_set() if attr in [ @@ -306,7 +329,7 @@ def set_attribute(self, attr, value): DeviceAttributes.sleep_mode, DeviceAttributes.frost_protect, DeviceAttributes.comfort_mode, - DeviceAttributes.eco_mode + DeviceAttributes.eco_mode, ]: message.boost_mode = False message.sleep_mode = False diff --git a/custom_components/midea_ac_lan/midea/devices/ac/message.py b/custom_components/midea_ac_lan/midea/devices/ac/message.py index f8118c3d..c6c26f6d 100644 --- a/custom_components/midea_ac_lan/midea/devices/ac/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ac/message.py @@ -1,12 +1,13 @@ from enum import IntEnum + +from ...core.crc8 import calculate from ...core.message import ( - MessageType, + MessageBody, MessageRequest, MessageResponse, - MessageBody, - NewProtocolMessageBody + MessageType, + NewProtocolMessageBody, ) -from ...core.crc8 import calculate BB_AC_MODES = [0, 3, 1, 2, 4, 5] @@ -18,7 +19,7 @@ class NewProtocolTags(IntEnum): prompt_tone = 0x001A indirect_wind = 0x0042 fresh_air_1 = 0x0233 - fresh_air_2 = 0x004b + fresh_air_2 = 0x004B class MessageACBase(MessageRequest): @@ -29,7 +30,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xAC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) MessageACBase._message_serial += 1 if MessageACBase._message_serial >= 254: @@ -52,17 +53,34 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41) + body_type=0x41, + ) @property def _body(self): - return bytearray([ - 0x81, 0x00, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, - ]) + return bytearray( + [ + 0x81, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class MessagePowerQuery(MessageACBase): @@ -70,13 +88,12 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41) + body_type=0x41, + ) @property def _body(self): - return bytearray([ - 0x21, 0x01, 0x44, 0x00, 0x01 - ]) + return bytearray([0x21, 0x01, 0x44, 0x00, 0x01]) @property def body(self): @@ -90,20 +107,36 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41) + body_type=0x41, + ) self.prompt_tone = False @property def _body(self): prompt_tone = 0x40 if self.prompt_tone else 0 - return bytearray([ - 0x02 | prompt_tone, - 0x00, 0xFF, 0x02, - 0x00, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00 - ]) + return bytearray( + [ + 0x02 | prompt_tone, + 0x00, + 0xFF, + 0x02, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class MessageNewProtocolQuery(MessageACBase): @@ -111,7 +144,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0xB1) + body_type=0xB1, + ) @property def _body(self): @@ -121,7 +155,7 @@ def _body(self): NewProtocolTags.indoor_humidity, NewProtocolTags.screen_display, NewProtocolTags.fresh_air_1, - NewProtocolTags.fresh_air_2 + NewProtocolTags.fresh_air_2, ] _body = bytearray([len(query_params)]) @@ -133,9 +167,8 @@ def _body(self): class MessageSubProtocol(MessageACBase): def __init__(self, protocol_version, message_type, subprotocol_query_type): super().__init__( - protocol_version=protocol_version, - message_type=message_type, - body_type=0xAA) + protocol_version=protocol_version, message_type=message_type, body_type=0xAA + ) self._subprotocol_query_type = subprotocol_query_type @property @@ -152,10 +185,17 @@ def body(self): @property def _body(self): _subprotocol_body = self._subprotocol_body - _body = bytearray([ - 6 + 2 + (len(_subprotocol_body) if _subprotocol_body is not None else 0), - 0x00, 0xFF, 0xFF, self._subprotocol_query_type - ]) + _body = bytearray( + [ + 6 + + 2 + + (len(_subprotocol_body) if _subprotocol_body is not None else 0), + 0x00, + 0xFF, + 0xFF, + self._subprotocol_query_type, + ] + ) if _subprotocol_body is not None: _body.extend(_subprotocol_body) return _body @@ -166,7 +206,8 @@ def __init__(self, protocol_version, subprotocol_query_type): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - subprotocol_query_type=subprotocol_query_type) + subprotocol_query_type=subprotocol_query_type, + ) class MessageSubProtocolSet(MessageSubProtocol): @@ -174,7 +215,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - subprotocol_query_type=0x20) + subprotocol_query_type=0x20, + ) self.power = False self.mode = 0 self.target_temperature = 20.0 @@ -206,18 +248,47 @@ def _subprotocol_body(self): prompt_tone = 0x01 if self.prompt_tone else 0 timer = 0x04 if (self.sn8_flag and self.timer) else 0 - return bytearray([ - 0x02 | boost_mode | power | dry, aux_heating, sleep_mode, 0x00, - 0x00, mode, target_temperature, fan_speed, - 0x32, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, - 0x01, 0x00, 0x01, water_model_temperature_set, - prompt_tone, target_temperature, 0x32, 0x66, - 0x00, eco | timer, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x08 - ]) + return bytearray( + [ + 0x02 | boost_mode | power | dry, + aux_heating, + sleep_mode, + 0x00, + 0x00, + mode, + target_temperature, + fan_speed, + 0x32, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x01, + 0x00, + 0x01, + water_model_temperature_set, + prompt_tone, + target_temperature, + 0x32, + 0x66, + 0x00, + eco | timer, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + ] + ) class MessageGeneralSet(MessageACBase): @@ -225,7 +296,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x40) + body_type=0x40, + ) self.power = False self.prompt_tone = True self.mode = 0 @@ -250,13 +322,18 @@ def _body(self): power = 0x01 if self.power else 0 prompt_tone = 0x40 if self.prompt_tone else 0 # Byte2, mode target_temperature - mode = (self.mode << 5) & 0xe0 - target_temperature = (int(self.target_temperature) & 0xf) | \ - (0x10 if int(round(self.target_temperature * 2)) % 2 != 0 else 0) + mode = (self.mode << 5) & 0xE0 + target_temperature = (int(self.target_temperature) & 0xF) | ( + 0x10 if int(round(self.target_temperature * 2)) % 2 != 0 else 0 + ) # Byte 3, fan_speed - fan_speed = self.fan_speed & 0x7f + fan_speed = self.fan_speed & 0x7F # Byte 7, swing_mode - swing_mode = 0x30 | (0x0c if self.swing_vertical else 0) | (0x03 if self.swing_horizontal else 0) + swing_mode = ( + 0x30 + | (0x0C if self.swing_vertical else 0) + | (0x03 if self.swing_horizontal else 0) + ) # Byte 8, turbo boost_mode = 0x20 if self.boost_mode else 0 # Byte 9 aux_heating eco_mode @@ -275,22 +352,32 @@ def _body(self): # Byte 22 comfort_mode comfort_mode = 0x01 if self.comfort_mode else 0 - return bytearray([ - power | prompt_tone, - mode | target_temperature, - fan_speed, - 0x00, 0x00, 0x00, - swing_mode, - boost_mode, - smart_eye | dry | aux_heating | eco_mode, - temp_fahrenheit | sleep_mode | boost_mode_1, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, - natural_wind, - 0x00, 0x00, 0x00, - frost_protect, - comfort_mode - ]) + return bytearray( + [ + power | prompt_tone, + mode | target_temperature, + fan_speed, + 0x00, + 0x00, + 0x00, + swing_mode, + boost_mode, + smart_eye | dry | aux_heating | eco_mode, + temp_fahrenheit | sleep_mode | boost_mode_1, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + natural_wind, + 0x00, + 0x00, + 0x00, + frost_protect, + comfort_mode, + ] + ) class MessageNewProtocolSet(MessageACBase): @@ -298,7 +385,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0xB0) + body_type=0xB0, + ) self.indirect_wind = None self.prompt_tone = None self.breezeless = None @@ -315,29 +403,33 @@ def _body(self): payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.breezeless, - value=bytearray([0x01 if self.breezeless else 0x00]) - )) + value=bytearray([0x01 if self.breezeless else 0x00]), + ) + ) if self.indirect_wind is not None: pack_count += 1 payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.indirect_wind, - value=bytearray([0x02 if self.indirect_wind else 0x01]) - )) + value=bytearray([0x02 if self.indirect_wind else 0x01]), + ) + ) if self.prompt_tone is not None: pack_count += 1 payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.prompt_tone, - value=bytearray([0x01 if self.prompt_tone else 0x00]) - )) + value=bytearray([0x01 if self.prompt_tone else 0x00]), + ) + ) if self.screen_display_alternate is not None: pack_count += 1 payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.screen_display, - value=bytearray([0x64 if self.screen_display_alternate else 0x00]) - )) + value=bytearray([0x64 if self.screen_display_alternate else 0x00]), + ) + ) if self.fresh_air_1 is not None and len(self.fresh_air_1) == 2: pack_count += 1 fresh_air_power = 2 if self.fresh_air_1[0] > 0 else 1 @@ -345,13 +437,22 @@ def _body(self): payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.fresh_air_1, - value=bytearray([ - fresh_air_power, - fresh_air_fan_speed, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - ]) - )) + value=bytearray( + [ + fresh_air_power, + fresh_air_fan_speed, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ), + ) + ) if self.fresh_air_2 is not None and len(self.fresh_air_2) == 2: pack_count += 1 fresh_air_power = 1 if self.fresh_air_2[0] > 0 else 0 @@ -359,12 +460,9 @@ def _body(self): payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.fresh_air_2, - value=bytearray([ - fresh_air_power, - fresh_air_fan_speed, - 0xFF - ]) - )) + value=bytearray([fresh_air_power, fresh_air_fan_speed, 0xFF]), + ) + ) payload[0] = pack_count return payload @@ -373,9 +471,11 @@ class XA0MessageBody(MessageBody): def __init__(self, body): super().__init__(body) self.power = (body[1] & 0x1) > 0 - self.target_temperature = ((body[1] & 0x3E) >> 1) - 4 + 16.0 + (0.5 if body[1] & 0x40 > 0 else 0.0) - self.mode = (body[2] & 0xe0) >> 5 - self.fan_speed = body[3] & 0x7f + self.target_temperature = ( + ((body[1] & 0x3E) >> 1) - 4 + 16.0 + (0.5 if body[1] & 0x40 > 0 else 0.0) + ) + self.mode = (body[2] & 0xE0) >> 5 + self.fan_speed = body[3] & 0x7F self.swing_vertical = (body[7] & 0xC) > 0 self.swing_horizontal = (body[7] & 0x3) > 0 self.boost_mode = ((body[8] & 0x20) > 0) or ((body[10] & 0x2) > 0) @@ -416,13 +516,15 @@ def __init__(self, body, bt): super().__init__(body, bt) params = self.parse() if NewProtocolTags.indirect_wind in params: - self.indirect_wind = (params[NewProtocolTags.indirect_wind][0] == 0x02) + self.indirect_wind = params[NewProtocolTags.indirect_wind][0] == 0x02 if NewProtocolTags.indoor_humidity in params: self.indoor_humidity = params[NewProtocolTags.indoor_humidity][0] if NewProtocolTags.breezeless in params: - self.breezeless = (params[NewProtocolTags.breezeless][0] == 1) + self.breezeless = params[NewProtocolTags.breezeless][0] == 1 if NewProtocolTags.screen_display in params: - self.screen_display_alternate = (params[NewProtocolTags.screen_display][0] > 0) + self.screen_display_alternate = ( + params[NewProtocolTags.screen_display][0] > 0 + ) self.screen_display_new = True if NewProtocolTags.fresh_air_1 in params: self.fresh_air_1 = True @@ -440,8 +542,10 @@ class XC0MessageBody(MessageBody): def __init__(self, body): super().__init__(body) self.power = (body[1] & 0x1) > 0 - self.mode = (body[2] & 0xe0) >> 5 - self.target_temperature = (body[2] & 0x0F) + 16.0 + (0.5 if body[0x02] & 0x10 > 0 else 0.0) + self.mode = (body[2] & 0xE0) >> 5 + self.target_temperature = ( + (body[2] & 0x0F) + 16.0 + (0.5 if body[0x02] & 0x10 > 0 else 0.0) + ) self.fan_speed = body[3] & 0x7F self.swing_vertical = (body[7] & 0x0C) > 0 self.swing_horizontal = (body[7] & 0x03) > 0 @@ -480,16 +584,13 @@ def __init__(self, body, analysis_method=3): super().__init__(body) if body[3] == 0x44: self.total_energy_consumption = XC1MessageBody.parse_consumption( - analysis_method, - body[4], body[5], body[6], body[7] + analysis_method, body[4], body[5], body[6], body[7] ) self.current_energy_consumption = XC1MessageBody.parse_consumption( - analysis_method, - body[12], body[13], body[14], body[15] + analysis_method, body[12], body[13], body[14], body[15] ) self.realtime_power = XC1MessageBody.parse_power( - analysis_method, - body[16], body[17], body[18] + analysis_method, body[16], body[17], body[18] ) elif body[3] == 0x40: pass @@ -501,9 +602,14 @@ def parse_value(byte): @staticmethod def parse_power(analysis_method, byte1, byte2, byte3): if analysis_method == 1: - return float(XC1MessageBody.parse_value(byte1) * 10000 + - XC1MessageBody.parse_value(byte2) * 100 + - XC1MessageBody.parse_value(byte3)) / 10 + return ( + float( + XC1MessageBody.parse_value(byte1) * 10000 + + XC1MessageBody.parse_value(byte2) * 100 + + XC1MessageBody.parse_value(byte3) + ) + / 10 + ) elif analysis_method == 2: return float((byte1 << 16) + (byte2 << 8) + byte3) / 10 else: @@ -512,10 +618,15 @@ def parse_power(analysis_method, byte1, byte2, byte3): @staticmethod def parse_consumption(analysis_method, byte1, byte2, byte3, byte4): if analysis_method == 1: - return float(XC1MessageBody.parse_value(byte1) * 1000000 + - XC1MessageBody.parse_value(byte2) * 10000 + - XC1MessageBody.parse_value(byte3) * 100 + - XC1MessageBody.parse_value(byte4)) / 100 + return ( + float( + XC1MessageBody.parse_value(byte1) * 1000000 + + XC1MessageBody.parse_value(byte2) * 10000 + + XC1MessageBody.parse_value(byte3) * 100 + + XC1MessageBody.parse_value(byte4) + ) + / 100 + ) elif analysis_method == 2: return float((byte1 << 32) + (byte2 << 16) + (byte3 << 8) + byte4) / 10 else: @@ -541,22 +652,40 @@ def __init__(self, body): self.mode = 0 self.target_temperature = (subprotocol_body[6] - 30) / 2 self.fan_speed = subprotocol_body[7] - self.timer = (subprotocol_body[25] & 0x04) > 0 if subprotocol_body_len > 27 else False - self.eco_mode = (subprotocol_body[25] & 0x40) > 0 if subprotocol_body_len > 27 else False + self.timer = ( + (subprotocol_body[25] & 0x04) > 0 + if subprotocol_body_len > 27 + else False + ) + self.eco_mode = ( + (subprotocol_body[25] & 0x40) > 0 + if subprotocol_body_len > 27 + else False + ) elif data_type == 0x10: if subprotocol_body[8] & 0x80 == 0x80: - self.indoor_temperature = (0 - (~(subprotocol_body[7] + subprotocol_body[8] * 256) + 1) & 0xffff) / 100 + self.indoor_temperature = ( + 0 - (~(subprotocol_body[7] + subprotocol_body[8] * 256) + 1) + & 0xFFFF + ) / 100 else: - self.indoor_temperature = (subprotocol_body[7] + subprotocol_body[8] * 256) / 100 + self.indoor_temperature = ( + subprotocol_body[7] + subprotocol_body[8] * 256 + ) / 100 self.indoor_humidity = subprotocol_body[30] self.sn8_flag = subprotocol_body[80] == 0x31 elif data_type == 0x12: pass elif data_type == 0x30: if subprotocol_body[6] & 0x80 == 0x80: - self.outdoor_temperature = (0 - (~(subprotocol_body[5] + subprotocol_body[6] * 256) + 1) & 0xffff) / 100 + self.outdoor_temperature = ( + 0 - (~(subprotocol_body[5] + subprotocol_body[6] * 256) + 1) + & 0xFFFF + ) / 100 else: - self.outdoor_temperature = (subprotocol_body[5] + subprotocol_body[6] * 256) / 100 + self.outdoor_temperature = ( + subprotocol_body[5] + subprotocol_body[6] * 256 + ) / 100 elif data_type == 0x13 or data_type == 0x21: pass @@ -568,15 +697,25 @@ def __init__(self, message, power_analysis_method=3): self.set_body(XA0MessageBody(super().body)) elif self.message_type == MessageType.notify1 and self.body_type == 0xA1: self.set_body(XA1MessageBody(super().body)) - elif self.message_type in [MessageType.query, MessageType.set, MessageType.notify2] and \ - self.body_type in [0xB0, 0xB1, 0xB5]: + elif self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify2, + ] and self.body_type in [0xB0, 0xB1, 0xB5]: self.set_body(XBXMessageBody(super().body, self.body_type)) - elif self.message_type in [MessageType.query, MessageType.set] and self.body_type == 0xC0: + elif ( + self.message_type in [MessageType.query, MessageType.set] + and self.body_type == 0xC0 + ): self.set_body(XC0MessageBody(super().body)) elif self.message_type == MessageType.query and self.body_type == 0xC1: self.set_body(XC1MessageBody(super().body, power_analysis_method)) - elif self.message_type in [MessageType.set, MessageType.query, MessageType.notify2] and \ - self.body_type == 0xBB and len(super().body) >= 21: + elif ( + self.message_type + in [MessageType.set, MessageType.query, MessageType.notify2] + and self.body_type == 0xBB + and len(super().body) >= 21 + ): self.used_subprotocol = True self.set_body(XBBMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/b0/device.py b/custom_components/midea_ac_lan/midea/devices/b0/device.py index 4bfee5a6..8ba25bc3 100644 --- a/custom_components/midea_ac_lan/midea/devices/b0/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b0/device.py @@ -1,12 +1,12 @@ import logging -from .message import ( - MessageQuery01, - MessageB0Response -) + +from .message import MessageB0Response, MessageQuery01 + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,22 +24,26 @@ class DeviceAttributes(StrEnum): class MideaB0Device(MiedaDevice): _status = { - 0x01: "Standby", 0x02: "Idle", 0x03: "Working", - 0x04: "Finished", 0x05: "Delay", 0x06: "Paused" + 0x01: "Standby", + 0x02: "Idle", + 0x03: "Working", + 0x04: "Finished", + 0x05: "Delay", + 0x06: "Paused", } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -60,7 +64,8 @@ def __init__( DeviceAttributes.tank_ejected: False, DeviceAttributes.water_change_reminder: False, DeviceAttributes.water_shortage: False, - }) + }, + ) def build_query(self): return [MessageQuery01(self._protocol_version)] @@ -74,7 +79,9 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaB0Device._status.keys(): - self._attributes[DeviceAttributes.status] = MideaB0Device._status.get(value) + self._attributes[DeviceAttributes.status] = ( + MideaB0Device._status.get(value) + ) else: self._attributes[DeviceAttributes.status] = None else: diff --git a/custom_components/midea_ac_lan/midea/devices/b0/message.py b/custom_components/midea_ac_lan/midea/devices/b0/message.py index 1c4a9eb1..ebed3bdd 100644 --- a/custom_components/midea_ac_lan/midea/devices/b0/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b0/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageB0Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xB0, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00) + body_type=0x00, + ) @property def _body(self): @@ -37,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -60,9 +57,11 @@ def __init__(self, body): if len(body) > 15: self.door = (body[32] & 0x02) > 0 self.status = body[31] - self.time_remaining = (0 if body[22] == 0xFF else body[22]) * 3600 + \ - (0 if body[23] == 0xFF else body[23]) * 60 + \ - (0 if body[24] == 0xFF else body[24]) + self.time_remaining = ( + (0 if body[22] == 0xFF else body[22]) * 3600 + + (0 if body[23] == 0xFF else body[23]) * 60 + + (0 if body[24] == 0xFF else body[24]) + ) self.current_temperature = (body[25] << 8) + (body[26]) if self.current_temperature == 0: self.current_temperature = (body[27] << 8) + body[28] diff --git a/custom_components/midea_ac_lan/midea/devices/b1/device.py b/custom_components/midea_ac_lan/midea/devices/b1/device.py index cd030cd3..e9f45cc9 100644 --- a/custom_components/midea_ac_lan/midea/devices/b1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b1/device.py @@ -1,12 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageB1Response -) + +from .message import MessageB1Response, MessageQuery + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,22 +24,26 @@ class DeviceAttributes(StrEnum): class MideaB1Device(MiedaDevice): _status = { - 0x01: "Standby", 0x02: "Idle", 0x03: "Working", - 0x04: "Finished", 0x05: "Delay", 0x06: "Paused" + 0x01: "Standby", + 0x02: "Idle", + 0x03: "Working", + 0x04: "Finished", + 0x05: "Delay", + 0x06: "Paused", } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -60,7 +64,8 @@ def __init__( DeviceAttributes.tank_ejected: False, DeviceAttributes.water_change_reminder: False, DeviceAttributes.water_shortage: False, - }) + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -74,7 +79,9 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaB1Device._status.keys(): - self._attributes[DeviceAttributes.status] = MideaB1Device._status.get(value) + self._attributes[DeviceAttributes.status] = ( + MideaB1Device._status.get(value) + ) else: self._attributes[DeviceAttributes.status] = None else: diff --git a/custom_components/midea_ac_lan/midea/devices/b1/message.py b/custom_components/midea_ac_lan/midea/devices/b1/message.py index 8a9a74c7..d7d6fb63 100644 --- a/custom_components/midea_ac_lan/midea/devices/b1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b1/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageB1Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xB1, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00) + body_type=0x00, + ) @property def _body(self): @@ -37,9 +33,11 @@ def __init__(self, body): super().__init__(body) self.door = (body[16] & 0x02) > 0 self.status = body[1] - self.time_remaining = (0 if body[6] == 0xFF else body[6]) * 3600 + \ - (0 if body[7] == 0xFF else body[7]) * 60 + \ - (0 if body[8] == 0xFF else body[8]) + self.time_remaining = ( + (0 if body[6] == 0xFF else body[6]) * 3600 + + (0 if body[7] == 0xFF else body[7]) * 60 + + (0 if body[8] == 0xFF else body[8]) + ) self.current_temperature = body[19] self.tank_ejected = (body[16] & 0x04) > 0 self.water_shortage = (body[16] & 0x08) > 0 diff --git a/custom_components/midea_ac_lan/midea/devices/b3/device.py b/custom_components/midea_ac_lan/midea/devices/b3/device.py index 3d26da09..f19e6071 100644 --- a/custom_components/midea_ac_lan/midea/devices/b3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b3/device.py @@ -1,12 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageB3Response -) + +from .message import MessageB3Response, MessageQuery + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -39,22 +39,25 @@ class DeviceAttributes(StrEnum): class MideaB2Device(MiedaDevice): _status = { - 0x00: "Off", 0x01: "Standby", 0x02: "Working", - 0x03: "Delay", 0x04: "Finished" + 0x00: "Off", + 0x01: "Standby", + 0x02: "Working", + 0x03: "Delay", + 0x04: "Finished", } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -90,7 +93,8 @@ def __init__( DeviceAttributes.bottom_compartment_preheating: False, DeviceAttributes.bottom_compartment_cooling: False, DeviceAttributes.lock: False, - }) + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -105,7 +109,7 @@ def process_message(self, msg): if status in [ DeviceAttributes.top_compartment_status, DeviceAttributes.middle_compartment_status, - DeviceAttributes.bottom_compartment_status + DeviceAttributes.bottom_compartment_status, ]: if value in MideaB2Device._status.keys(): self._attributes[status] = MideaB2Device._status.get(value) diff --git a/custom_components/midea_ac_lan/midea/devices/b3/message.py b/custom_components/midea_ac_lan/midea/devices/b3/message.py index 8af1d9e6..c06e22a8 100644 --- a/custom_components/midea_ac_lan/midea/devices/b3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b3/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageB3Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xB3, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x31) + body_type=0x31, + ) @property def _body(self): @@ -39,25 +35,37 @@ def __init__(self, body): self.top_compartment_mode = body[2] self.top_compartment_temperature = body[3] self.top_compartment_remaining = ( - body[23] * 3600 if len(body) > 23 and body[23] != 0xFF else 0 + - body[4] * 60 if body[4] != 0xFF else 0 + - body[5] if body[5] != 0xFF else 0 + body[23] * 3600 + if len(body) > 23 and body[23] != 0xFF + else ( + 0 + body[4] * 60 + if body[4] != 0xFF + else 0 + body[5] if body[5] != 0xFF else 0 + ) ) self.bottom_compartment_status = body[6] self.bottom_compartment_mode = body[7] self.bottom_compartment_temperature = body[8] self.bottom_compartment_remaining = ( - body[24] * 3600 if len(body) > 24 and body[24] != 0xFF else 0 + - body[9] * 60 if body[9] != 0xFF else 0 + - body[10] if body[10] != 0xFF else 0 + body[24] * 3600 + if len(body) > 24 and body[24] != 0xFF + else ( + 0 + body[9] * 60 + if body[9] != 0xFF + else 0 + body[10] if body[10] != 0xFF else 0 + ) ) self.middle_compartment_status = body[17] self.middle_compartment_mode = body[18] self.middle_compartment_temperature = body[19] self.middle_compartment_remaining = ( - body[25] * 3600 if len(body) > 25 and body[25] != 0xFF else 0 + - body[20] * 60 if body[20] != 0xFF else 0 + - body[21] if body[21] != 0xFF else 0 + body[25] * 3600 + if len(body) > 25 and body[25] != 0xFF + else ( + 0 + body[20] * 60 + if body[20] != 0xFF + else 0 + body[21] if body[21] != 0xFF else 0 + ) ) self.lock = body[11] & 0x01 > 0 self.bottom_compartment_door = body[11] & 0x02 > 0 @@ -78,25 +86,37 @@ def __init__(self, body): self.top_compartment_mode = body[2] self.top_compartment_temperature = body[3] self.top_compartment_remaining = ( - body[17] * 3600 if len(body) > 17 and body[17] != 0xFF else 0 + - body[4] * 60 if body[4] != 0xFF else 0 + - body[5] if body[5] != 0xFF else 0 + body[17] * 3600 + if len(body) > 17 and body[17] != 0xFF + else ( + 0 + body[4] * 60 + if body[4] != 0xFF + else 0 + body[5] if body[5] != 0xFF else 0 + ) ) self.bottom_compartment_status = body[6] self.bottom_compartment_mode = body[7] self.bottom_compartment_temperature = body[8] self.bottom_compartment_remaining = ( - body[18] * 3600 if len(body) > 18 and body[18] != 0xFF else 0 + - body[9] * 60 if body[9] != 0xFF else 0 + - body[10] if body[10] != 0xFF else 0 + body[18] * 3600 + if len(body) > 18 and body[18] != 0xFF + else ( + 0 + body[9] * 60 + if body[9] != 0xFF + else 0 + body[10] if body[10] != 0xFF else 0 + ) ) self.middle_compartment_status = body[12] self.middle_compartment_mode = body[13] self.middle_compartment_temperature = body[14] self.middle_compartment_remaining = ( - body[19] * 3600 if len(body) > 19 and body[19] != 0xFF else 0 + - body[15] * 60 if body[15] != 0xFF else 0 + - body[16] if body[16] != 0xFF else 0 + body[19] * 3600 + if len(body) > 19 and body[19] != 0xFF + else ( + 0 + body[15] * 60 + if body[15] != 0xFF + else 0 + body[16] if body[16] != 0xFF else 0 + ) ) self.lock = body[11] & 0x01 > 0 @@ -108,30 +128,35 @@ def __init__(self, body): self.top_compartment_mode = body[6] self.top_compartment_temperature = body[7] self.top_compartment_remaining = ( - body[8] * 60 if body[8] != 0xFF else 0 + - body[9] if body[9] != 0xFF else 0 + body[8] * 60 if body[8] != 0xFF else 0 + body[9] if body[9] != 0xFF else 0 ) self.bottom_compartment_status = body[10] self.bottom_compartment_mode = body[11] self.bottom_compartment_temperature = body[12] self.bottom_compartment_remaining = ( - body[13] * 60 if body[13] != 0xFF else 0 + - body[14] if body[14] != 0xFF else 0 + body[13] * 60 + if body[13] != 0xFF + else 0 + body[14] if body[14] != 0xFF else 0 ) self.bottom_compartment_status = body[15] self.bottom_compartment_mode = body[16] self.bottom_compartment_temperature = body[17] self.bottom_compartment_remaining = ( - body[18] * 60 if body[18] != 0xFF else 0 + - body[19] if body[19] != 0xFF else 0 + body[18] * 60 + if body[18] != 0xFF + else 0 + body[19] if body[19] != 0xFF else 0 ) class MessageB3Response(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type == MessageType.query and self.body_type == 0x31 or - self.message_type == MessageType.notify1 and self.body_type == 0x41): + if ( + self.message_type == MessageType.query + and self.body_type == 0x31 + or self.message_type == MessageType.notify1 + and self.body_type == 0x41 + ): self.set_body(B3MessageBody31(super().body)) elif self.message_type == MessageType.set and self.body_type == 0x21: self.set_body(B3MessageBody21(super().body)) diff --git a/custom_components/midea_ac_lan/midea/devices/b4/device.py b/custom_components/midea_ac_lan/midea/devices/b4/device.py index 41b19763..07a3068a 100644 --- a/custom_components/midea_ac_lan/midea/devices/b4/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b4/device.py @@ -1,12 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageB4Response -) + +from .message import MessageB4Response, MessageQuery + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,22 +24,26 @@ class DeviceAttributes(StrEnum): class MideaB4Device(MiedaDevice): _status = { - 0x01: "Standby", 0x02: "Idle", 0x03: "Working", - 0x04: "Finished", 0x05: "Delay", 0x06: "Paused" + 0x01: "Standby", + 0x02: "Idle", + 0x03: "Working", + 0x04: "Finished", + 0x05: "Delay", + 0x06: "Paused", } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -60,7 +64,8 @@ def __init__( DeviceAttributes.tank_ejected: False, DeviceAttributes.water_change_reminder: False, DeviceAttributes.water_shortage: False, - }) + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -74,7 +79,9 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaB4Device._status.keys(): - self._attributes[DeviceAttributes.status] = MideaB4Device._status.get(value) + self._attributes[DeviceAttributes.status] = ( + MideaB4Device._status.get(value) + ) else: self._attributes[DeviceAttributes.status] = None else: diff --git a/custom_components/midea_ac_lan/midea/devices/b4/message.py b/custom_components/midea_ac_lan/midea/devices/b4/message.py index 91f55059..07cc1ee9 100644 --- a/custom_components/midea_ac_lan/midea/devices/b4/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b4/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageB4Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xB4, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -35,9 +31,11 @@ def _body(self): class B4MessageBody(MessageBody): def __init__(self, body): super().__init__(body) - self.time_remaining = (0 if body[22] == 0xFF else body[22]) * 3600 + \ - (0 if body[23] == 0xFF else body[23]) * 60 + \ - (0 if body[24] == 0xFF else body[24]) + self.time_remaining = ( + (0 if body[22] == 0xFF else body[22]) * 3600 + + (0 if body[23] == 0xFF else body[23]) * 60 + + (0 if body[24] == 0xFF else body[24]) + ) self.current_temperature = (body[25] << 8) + body[26] if self.current_temperature == 0: self.current_temperature = (body[27] << 8) + body[28] @@ -51,7 +49,11 @@ def __init__(self, body): class MessageB4Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.notify1, MessageType.query, MessageType.set]: + if self.message_type in [ + MessageType.notify1, + MessageType.query, + MessageType.set, + ]: if self.body_type == 0x01: self.set_body(B4MessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/b6/device.py b/custom_components/midea_ac_lan/midea/devices/b6/device.py index 53a98762..059b4282 100644 --- a/custom_components/midea_ac_lan/midea/devices/b6/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b6/device.py @@ -1,14 +1,13 @@ -import logging import json -from .message import ( - MessageQuery, - MessageB6Response, - MessageSet -) +import logging + +from .message import MessageB6Response, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -26,17 +25,17 @@ class DeviceAttributes(StrEnum): class MideaB6Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -56,11 +55,10 @@ def __init__( DeviceAttributes.fan_level: 0, DeviceAttributes.fan_speed: 0, DeviceAttributes.oilcup_full: False, - DeviceAttributes.cleaning_reminder: False - }) - self._default_speeds = { - 0: "Off", 1: "Level 1", 2: "Level 2" - } + DeviceAttributes.cleaning_reminder: False, + }, + ) + self._default_speeds = {0: "Off", 1: "Level 1", 2: "Level 2"} self._default_power_speed = 2 self._power_speed = self._default_power_speed self._speeds = self._default_speeds @@ -87,13 +85,21 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.fan_level: if value in self._speeds.keys(): - self._attributes[DeviceAttributes.mode] = self._speeds.get(value) - self._attributes[DeviceAttributes.fan_speed] = list(self._speeds.keys()).index(value) + self._attributes[DeviceAttributes.mode] = self._speeds.get( + value + ) + self._attributes[DeviceAttributes.fan_speed] = list( + self._speeds.keys() + ).index(value) else: self._attributes[DeviceAttributes.mode] = None self._attributes[DeviceAttributes.fan_speed] = 0 - new_status[DeviceAttributes.mode.value] = self._attributes[DeviceAttributes.mode] - new_status[DeviceAttributes.fan_speed.value] = self._attributes[DeviceAttributes.fan_speed] + new_status[DeviceAttributes.mode.value] = self._attributes[ + DeviceAttributes.mode + ] + new_status[DeviceAttributes.fan_speed.value] = self._attributes[ + DeviceAttributes.fan_speed + ] self._attributes[status] = getattr(message, str(status)) new_status[str(status)] = self._attributes[status] return new_status @@ -107,8 +113,9 @@ def set_attribute(self, attr, value): elif attr == DeviceAttributes.mode: if value in self._speeds.values(): message = MessageSet(self._protocol_version) - message.fan_level = \ - list(self._speeds.keys())[list(self._speeds.values()).index(value)] + message.fan_level = list(self._speeds.keys())[ + list(self._speeds.values()).index(value) + ] elif not value: message = MessageSet(self._protocol_version) message.power = False @@ -130,8 +137,9 @@ def turn_on(self, fan_speed=None, mode=None): else: message.fan_level = self._power_speed if mode is not None in self._speeds.values(): - message.fan_level = \ - list(self._speeds.keys())[list(self._speeds.values()).index(mode)] + message.fan_level = list(self._speeds.keys())[ + list(self._speeds.values()).index(mode) + ] self.build_send(message) def set_customize(self, customize): @@ -151,7 +159,9 @@ def set_customize(self, customize): keys = sorted(speeds.keys()) for k in keys: self._speeds[k] = speeds[k] - self.update_all({"speeds": self._speeds, "default_speed": self._power_speed}) + self.update_all( + {"speeds": self._speeds, "default_speed": self._power_speed} + ) except Exception as e: _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") diff --git a/custom_components/midea_ac_lan/midea/devices/b6/message.py b/custom_components/midea_ac_lan/midea/devices/b6/message.py index 10561f99..a780dad4 100644 --- a/custom_components/midea_ac_lan/midea/devices/b6/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b6/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageB6Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xB6, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x11 if protocol_version == 2 else 0x31 + body_type=0x11 if protocol_version == 2 else 0x31, ) @property @@ -83,10 +78,9 @@ def _body(self): else: value2 = 0x02 value3 = self.fan_level - return bytearray([ - 0x01, light, value2, value3, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - ]) + return bytearray( + [0x01, light, value2, value3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] + ) else: value13 = 0xFF value14 = 0xFF @@ -113,10 +107,7 @@ def _body(self): value13 = 0x02 value14 = 0x02 value15 = 0x01 if self.light else 0x00 - return bytearray([ - 0x01, value13, value14, value15, value16, - 0xFF, 0xFF - ]) + return bytearray([0x01, value13, value14, value15, value16, 0xFF, 0xFF]) class B6FeedbackBody(MessageBody): @@ -157,7 +148,7 @@ class B6NewProtocolBody(MessageBody): def __init__(self, body): super().__init__(body) if body[1] == 0x01: - pack_bytes = body[3: 3 + body[2]] + pack_bytes = body[3 : 3 + body[2]] if pack_bytes[1] != 0xFF: self.power = True self.power = pack_bytes[1] not in [0x00, 0x01, 0x05, 0x07] @@ -189,9 +180,17 @@ def __init__(self, body): class MessageB6Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type == MessageType.set and self.body_type == 0x22 and super().body[1] == 0x01: + if ( + self.message_type == MessageType.set + and self.body_type == 0x22 + and super().body[1] == 0x01 + ): self.set_body(B6SpecialBody(super().body)) - elif self.message_type == MessageType.set and self.body_type == 0x11 and super().body[1] == 0x01: + elif ( + self.message_type == MessageType.set + and self.body_type == 0x11 + and super().body[1] == 0x01 + ): ############################# pass elif self.message_type == MessageType.query: diff --git a/custom_components/midea_ac_lan/midea/devices/bf/device.py b/custom_components/midea_ac_lan/midea/devices/bf/device.py index 2f0b5685..7c9e3639 100644 --- a/custom_components/midea_ac_lan/midea/devices/bf/device.py +++ b/custom_components/midea_ac_lan/midea/devices/bf/device.py @@ -1,12 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageBFResponse -) + +from .message import MessageBFResponse, MessageQuery + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,22 +24,26 @@ class DeviceAttributes(StrEnum): class MideaBFDevice(MiedaDevice): _status = { - 0x01: "PowerSave", 0x02: "Standby", 0x03: "Working", - 0x04: "Finished", 0x05: "Delay", 0x06: "Paused" + 0x01: "PowerSave", + 0x02: "Standby", + 0x03: "Working", + 0x04: "Finished", + 0x05: "Delay", + 0x06: "Paused", } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -60,7 +64,8 @@ def __init__( DeviceAttributes.tank_ejected: None, DeviceAttributes.water_change_reminder: None, DeviceAttributes.water_shortage: None, - }) + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -74,7 +79,9 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaBFDevice._status.keys(): - self._attributes[DeviceAttributes.status] = MideaBFDevice._status.get(value) + self._attributes[DeviceAttributes.status] = ( + MideaBFDevice._status.get(value) + ) else: self._attributes[DeviceAttributes.status] = "Unknown" else: diff --git a/custom_components/midea_ac_lan/midea/devices/bf/message.py b/custom_components/midea_ac_lan/midea/devices/bf/message.py index e40da95b..358ce3eb 100644 --- a/custom_components/midea_ac_lan/midea/devices/bf/message.py +++ b/custom_components/midea_ac_lan/midea/devices/bf/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageBFBase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xBF, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -37,14 +33,17 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x02) + body_type=0x02, + ) self.power = None self.child_lock = None @property def _body(self): power = 0xFF if self.power is None else 0x11 if self.power else 0x01 - child_lock = 0xFF if self.child_lock is None else 0x01 if self.child_lock else 0x00 + child_lock = ( + 0xFF if self.child_lock is None else 0x01 if self.child_lock else 0x00 + ) return bytearray([power, child_lock] + [0xFF] * 7) @@ -52,9 +51,11 @@ class MessageBFBody(MessageBody): def __init__(self, body): super().__init__(body) self.status = body[31] - self.time_remaining = (0 if body[22] == 0xFF else body[22]) * 3600 + \ - (0 if body[23] == 0xFF else body[23]) * 60 + \ - (0 if body[24] == 0xFF else body[24]) + self.time_remaining = ( + (0 if body[22] == 0xFF else body[22]) * 3600 + + (0 if body[23] == 0xFF else body[23]) * 60 + + (0 if body[24] == 0xFF else body[24]) + ) cur_temperature = body[25] * 256 + body[26] if cur_temperature == 0: cur_temperature = body[27] * 256 + body[28] @@ -69,6 +70,10 @@ def __init__(self, body): class MessageBFResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: + if ( + self.message_type + in [MessageType.set, MessageType.notify1, MessageType.query] + and self.body_type == 0x01 + ): self.set_body(MessageBFBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/c2/device.py b/custom_components/midea_ac_lan/midea/devices/c2/device.py index e41bcb79..ef11240c 100644 --- a/custom_components/midea_ac_lan/midea/devices/c2/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c2/device.py @@ -1,15 +1,13 @@ -import logging import json -from .message import ( - MessageQuery, - MessageC2Response, - MessageSet, - MessagePower -) +import logging + +from .message import MessageC2Response, MessagePower, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -33,17 +31,17 @@ class DeviceAttributes(StrEnum): class MideaC2Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -69,8 +67,9 @@ def __init__( DeviceAttributes.seat_temp_level: 0, DeviceAttributes.water_temperature: None, DeviceAttributes.seat_temperature: None, - DeviceAttributes.filter_life: None - }) + DeviceAttributes.filter_life: None, + }, + ) self._max_dry_level = None self._max_water_temp_level = None self._max_seat_temp_level = None @@ -115,7 +114,7 @@ def set_attribute(self, attr, value): DeviceAttributes.foam_shield, DeviceAttributes.water_temp_level, DeviceAttributes.seat_temp_level, - DeviceAttributes.dry_level + DeviceAttributes.dry_level, ]: message = MessageSet(self._protocol_version) setattr(message, attr, value) @@ -137,10 +136,17 @@ def set_customize(self, customize): self._max_seat_temp_level = params.get("max_seat_temp_level") except Exception as e: _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"dry_level": {"max_dry_level": self._max_dry_level}, - "water_temp_level": {"max_water_temp_level": self._max_water_temp_level}, - "seat_temp_level": {"max_seat_temp_level": self._max_seat_temp_level} - }) + self.update_all( + { + "dry_level": {"max_dry_level": self._max_dry_level}, + "water_temp_level": { + "max_water_temp_level": self._max_water_temp_level + }, + "seat_temp_level": { + "max_seat_temp_level": self._max_seat_temp_level + }, + } + ) class MideaAppliance(MideaC2Device): diff --git a/custom_components/midea_ac_lan/midea/devices/c2/message.py b/custom_components/midea_ac_lan/midea/devices/c2/message.py index 343fa1b3..4ab2c023 100644 --- a/custom_components/midea_ac_lan/midea/devices/c2/message.py +++ b/custom_components/midea_ac_lan/midea/devices/c2/message.py @@ -1,10 +1,6 @@ from enum import IntEnum -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) + +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class C2MessageEnum(IntEnum): @@ -12,8 +8,8 @@ class C2MessageEnum(IntEnum): child_lock = 0x10 foam_shield = 0x1F water_temp_level = 0x09 - seat_temp_level = 0x0a - dry_level = 0x0c + seat_temp_level = 0x0A + dry_level = 0x0C C2_MESSAGE_KEYS = { @@ -21,8 +17,22 @@ class C2MessageEnum(IntEnum): C2MessageEnum.sensor_light: {True: 0x01 << 1, False: 0x00}, C2MessageEnum.foam_shield: {True: 0x01 << 2, False: 0x00}, C2MessageEnum.dry_level: {0: 0x00, 1: 0x01 << 1, 2: 0x02 << 1, 3: 0x03 << 1}, - C2MessageEnum.seat_temp_level: {0: 0x00, 1: 0x01 << 3, 2: 0x02 << 3, 3: 0x03 << 3, 4: 0x04 << 3, 5: 0x05 << 3}, - C2MessageEnum.water_temp_level: {0: 0x00, 1: 0x01, 2: 0x02, 3: 0x03, 4: 0x04, 5: 0x05} + C2MessageEnum.seat_temp_level: { + 0: 0x00, + 1: 0x01 << 3, + 2: 0x02 << 3, + 3: 0x03 << 3, + 4: 0x04 << 3, + 5: 0x05 << 3, + }, + C2MessageEnum.water_temp_level: { + 0: 0x00, + 1: 0x01, + 2: 0x02, + 3: 0x03, + 4: 0x04, + 5: 0x05, + }, } @@ -32,7 +42,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xC2, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -45,7 +55,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -57,7 +68,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00) + body_type=0x00, + ) self.power = False @property @@ -74,7 +86,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02) + body_type=0x02, + ) @property def _body(self): @@ -86,7 +99,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00) + body_type=0x00, + ) self.child_lock = None self.sensor_light = None @@ -127,9 +141,9 @@ def __init__(self, body): super().__init__(body) self.power = (body[2] & 0x01) > 0 self.seat_status = (body[3] & 0x01) > 0 - self.dry_level = ((body[6] & 0x7E) >> 1) - self.water_temp_level = (body[9] & 0x07) - self.seat_temp_level = ((body[9] & 0x38) >> 3) + self.dry_level = (body[6] & 0x7E) >> 1 + self.water_temp_level = body[9] & 0x07 + self.seat_temp_level = (body[9] & 0x38) >> 3 self.lid_status = (body[12] & 0x40) > 0 self.foam_shield = (body[13] & 0x80) > 0 self.sensor_light = (body[14] & 0x01) > 0 @@ -148,6 +162,10 @@ def __init__(self, body): class MessageC2Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.notify1, MessageType.query, MessageType.set]: + if self.message_type in [ + MessageType.notify1, + MessageType.query, + MessageType.set, + ]: self.set_body(C2MessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/c3/device.py b/custom_components/midea_ac_lan/midea/devices/c3/device.py index cf1679b3..9bf79b77 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/device.py @@ -1,15 +1,18 @@ import logging + from .message import ( + MessageC3Response, MessageQuery, - MessageSetSilent, + MessageSet, MessageSetECO, - MessageC3Response, - MessageSet + MessageSetSilent, ) + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -60,17 +63,17 @@ class DeviceAttributes(StrEnum): class MideaC3Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -123,8 +126,9 @@ def __init__( DeviceAttributes.status_ibh: None, DeviceAttributes.total_produced_energy: None, DeviceAttributes.outdoor_temperature: None, - DeviceAttributes.error_code: 0 - }) + DeviceAttributes.error_code: 0, + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -137,28 +141,48 @@ def process_message(self, msg): if hasattr(message, str(status)): self._attributes[status] = getattr(message, str(status)) new_status[str(status)] = getattr(message, str(status)) - if 'zone_temp_type' in new_status: + if "zone_temp_type" in new_status: for zone in [0, 1]: - if self._attributes[DeviceAttributes.zone_temp_type][zone]: # Water temp mode - self._attributes[DeviceAttributes.target_temperature][zone] = \ + if self._attributes[DeviceAttributes.zone_temp_type][ + zone + ]: # Water temp mode + self._attributes[DeviceAttributes.target_temperature][zone] = ( self._attributes[DeviceAttributes.zone_target_temp][zone] - if self._attributes[DeviceAttributes.mode_auto] == 2: # cooling mode - self._attributes[DeviceAttributes.temperature_max][zone] = \ - self._attributes[DeviceAttributes.zone_cooling_temp_max][zone] - self._attributes[DeviceAttributes.temperature_min][zone] = \ - self._attributes[DeviceAttributes.zone_cooling_temp_min][zone] + ) + if ( + self._attributes[DeviceAttributes.mode_auto] == 2 + ): # cooling mode + self._attributes[DeviceAttributes.temperature_max][zone] = ( + self._attributes[DeviceAttributes.zone_cooling_temp_max][ + zone + ] + ) + self._attributes[DeviceAttributes.temperature_min][zone] = ( + self._attributes[DeviceAttributes.zone_cooling_temp_min][ + zone + ] + ) elif self._attributes[DeviceAttributes.mode] == 3: # heating mode - self._attributes[DeviceAttributes.temperature_max][zone] = \ - self._attributes[DeviceAttributes.zone_heating_temp_max][zone] - self._attributes[DeviceAttributes.temperature_min][zone] = \ - self._attributes[DeviceAttributes.zone_heating_temp_min][zone] + self._attributes[DeviceAttributes.temperature_max][zone] = ( + self._attributes[DeviceAttributes.zone_heating_temp_max][ + zone + ] + ) + self._attributes[DeviceAttributes.temperature_min][zone] = ( + self._attributes[DeviceAttributes.zone_heating_temp_min][ + zone + ] + ) else: # Room temp mode - self._attributes[DeviceAttributes.target_temperature][zone] = \ + self._attributes[DeviceAttributes.target_temperature][zone] = ( self._attributes[DeviceAttributes.room_target_temp] - self._attributes[DeviceAttributes.temperature_max][zone] = \ + ) + self._attributes[DeviceAttributes.temperature_max][zone] = ( self._attributes[DeviceAttributes.room_temp_max] - self._attributes[DeviceAttributes.temperature_min][zone] = \ + ) + self._attributes[DeviceAttributes.temperature_min][zone] = ( self._attributes[DeviceAttributes.room_temp_min] + ) if self._attributes[DeviceAttributes.zone1_power]: if self._attributes[DeviceAttributes.zone_temp_type][zone]: self._attributes[DeviceAttributes.zone1_water_temp_mode] = True @@ -179,14 +203,18 @@ def process_message(self, msg): else: self._attributes[DeviceAttributes.zone2_water_temp_mode] = False self._attributes[DeviceAttributes.zone2_room_temp_mode] = False - new_status[DeviceAttributes.zone1_water_temp_mode.value] = \ - self._attributes[DeviceAttributes.zone1_water_temp_mode] - new_status[DeviceAttributes.zone2_water_temp_mode.value] = \ - self._attributes[DeviceAttributes.zone2_water_temp_mode] - new_status[DeviceAttributes.zone1_room_temp_mode.value] = \ - self._attributes[DeviceAttributes.zone1_room_temp_mode] - new_status[DeviceAttributes.zone2_room_temp_mode.value] = \ - self._attributes[DeviceAttributes.zone2_room_temp_mode] + new_status[DeviceAttributes.zone1_water_temp_mode.value] = self._attributes[ + DeviceAttributes.zone1_water_temp_mode + ] + new_status[DeviceAttributes.zone2_water_temp_mode.value] = self._attributes[ + DeviceAttributes.zone2_water_temp_mode + ] + new_status[DeviceAttributes.zone1_room_temp_mode.value] = self._attributes[ + DeviceAttributes.zone1_room_temp_mode + ] + new_status[DeviceAttributes.zone2_room_temp_mode.value] = self._attributes[ + DeviceAttributes.zone2_room_temp_mode + ] return new_status @@ -219,7 +247,7 @@ def set_attribute(self, attr, value): DeviceAttributes.disinfect, DeviceAttributes.fast_dhw, DeviceAttributes.dhw_target_temp, - DeviceAttributes.tbh + DeviceAttributes.tbh, ]: message = self.make_message_set() setattr(message, str(attr), value) diff --git a/custom_components/midea_ac_lan/midea/devices/c3/message.py b/custom_components/midea_ac_lan/midea/devices/c3/message.py index 577d8c20..bfae8488 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageC3Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xC3, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -37,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01) + body_type=0x01, + ) self.zone1_power = False self.zone2_power = False self.dhw_power = False @@ -66,12 +63,17 @@ def _body(self): zone1_target_temp = int(self.zone_target_temp[0]) zone2_target_temp = int(self.zone_target_temp[1]) dhw_target_temp = int(self.dhw_target_temp) - return bytearray([ - zone1_power | zone2_power | dhw_power, - self.mode, zone1_target_temp, zone2_target_temp, - dhw_target_temp, room_target_temp, - zone1_curve | zone2_curve | disinfect | fast_dhw - ]) + return bytearray( + [ + zone1_power | zone2_power | dhw_power, + self.mode, + zone1_target_temp, + zone2_target_temp, + dhw_target_temp, + room_target_temp, + zone1_curve | zone2_curve | disinfect | fast_dhw, + ] + ) class MessageSetSilent(MessageC3Base): @@ -79,7 +81,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x05) + body_type=0x05, + ) self.silent_mode = False self.super_silent = False @@ -88,11 +91,9 @@ def _body(self): silent_mode = 0x01 if self.silent_mode else 0 super_silent = 0x02 if self.super_silent else 0 - return bytearray([ - silent_mode | super_silent, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - ]) + return bytearray( + [silent_mode | super_silent, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + ) class MessageSetECO(MessageC3Base): @@ -100,18 +101,15 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x07) + body_type=0x07, + ) self.eco_mode = False @property def _body(self): eco_mode = 0x01 if self.eco_mode else 0 - return bytearray([ - eco_mode, - 0x00, 0x00, 0x00, 0x00, - 0x00 - ]) + return bytearray([eco_mode, 0x00, 0x00, 0x00, 0x00, 0x00]) class C3MessageBody(MessageBody): @@ -127,34 +125,19 @@ def __init__(self, body, data_offset=0): self.fast_dhw = body[data_offset + 0] & 0x40 > 0 self.zone_temp_type = [ body[data_offset + 1] & 0x10 > 0, - body[data_offset + 1] & 0x20 > 0 + body[data_offset + 1] & 0x20 > 0, ] self.silent_mode = body[data_offset + 2] & 0x02 > 0 self.eco_mode = body[data_offset + 2] & 0x08 > 0 self.mode = body[data_offset + 3] self.mode_auto = body[data_offset + 4] - self.zone_target_temp = [ - body[data_offset + 5], - body[data_offset + 6] - ] + self.zone_target_temp = [body[data_offset + 5], body[data_offset + 6]] self.dhw_target_temp = body[data_offset + 7] self.room_target_temp = body[data_offset + 8] / 2 - self.zone_heating_temp_max = [ - body[data_offset + 9], - body[data_offset + 13] - ] - self.zone_heating_temp_min = [ - body[data_offset + 10], - body[data_offset + 14] - ] - self.zone_cooling_temp_max = [ - body[data_offset + 11], - body[data_offset + 15] - ] - self.zone_cooling_temp_min = [ - body[data_offset + 12], - body[data_offset + 16] - ] + self.zone_heating_temp_max = [body[data_offset + 9], body[data_offset + 13]] + self.zone_heating_temp_min = [body[data_offset + 10], body[data_offset + 14]] + self.zone_cooling_temp_max = [body[data_offset + 11], body[data_offset + 15]] + self.zone_cooling_temp_min = [body[data_offset + 12], body[data_offset + 16]] self.room_temp_max = body[data_offset + 17] / 2 self.room_temp_min = body[data_offset + 18] / 2 self.dhw_temp_max = body[data_offset + 19] @@ -173,24 +156,29 @@ def __init__(self, body, data_offset=0): self.status_heating = (status_byte & 0x01) > 0 self.total_energy_consumption = ( - (body[data_offset + 1] << 32) + - (body[data_offset + 2] << 16) + - (body[data_offset + 3] << 8) + - (body[data_offset + 4])) + (body[data_offset + 1] << 32) + + (body[data_offset + 2] << 16) + + (body[data_offset + 3] << 8) + + (body[data_offset + 4]) + ) self.total_produced_energy = ( - (body[data_offset + 5] << 32) + - (body[data_offset + 6] << 16) + - (body[data_offset + 7] << 8) + - (body[data_offset + 8])) + (body[data_offset + 5] << 32) + + (body[data_offset + 6] << 16) + + (body[data_offset + 7] << 8) + + (body[data_offset + 8]) + ) self.outdoor_temperature = int(body[data_offset + 9]) class MessageC3Response(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01) or self.message_type == MessageType.notify2: + if ( + self.message_type + in [MessageType.set, MessageType.notify1, MessageType.query] + and self.body_type == 0x01 + ) or self.message_type == MessageType.notify2: self.set_body(C3MessageBody(super().body, data_offset=1)) elif self.message_type == MessageType.notify1 and self.body_type == 0x04: self.set_body(C3Notify1MessageBody(super().body, data_offset=1)) diff --git a/custom_components/midea_ac_lan/midea/devices/ca/device.py b/custom_components/midea_ac_lan/midea/devices/ca/device.py index 844584e0..102b30c2 100644 --- a/custom_components/midea_ac_lan/midea/devices/ca/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ca/device.py @@ -1,12 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageCAResponse -) + +from .message import MessageCAResponse, MessageQuery + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -35,17 +35,17 @@ class DeviceAttributes(StrEnum): class MideaCADevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -75,8 +75,9 @@ def __init__( DeviceAttributes.refrigerator_door: False, DeviceAttributes.freezer_door: False, DeviceAttributes.bar_door: False, - DeviceAttributes.flex_zone_door: False - }) + DeviceAttributes.flex_zone_door: False, + }, + ) self._modes = [""] def build_query(self): diff --git a/custom_components/midea_ac_lan/midea/devices/ca/message.py b/custom_components/midea_ac_lan/midea/devices/ca/message.py index e9178e0a..8f95db0b 100644 --- a/custom_components/midea_ac_lan/midea/devices/ca/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ca/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageCABase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCA, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00) + body_type=0x00, + ) @property def _body(self): @@ -35,8 +31,8 @@ def _body(self): class CAGeneralMessageBody(MessageBody): def __init__(self, body): super().__init__(body) - self.refrigerator_setting_temp = (body[2] & 0x0f) - self.freezer_setting_temp = -12 - ((body[2] & 0xf0) >> 4) + self.refrigerator_setting_temp = body[2] & 0x0F + self.freezer_setting_temp = -12 - ((body[2] & 0xF0) >> 4) flex_zone_setting_temp = body[3] right_flex_zone_setting_temp = body[4] @@ -103,14 +99,23 @@ def __init__(self, body): class MessageCAResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if ((self.message_type in [MessageType.query, MessageType.set] and self.body_type == 0x00) or - (self.message_type == MessageType.notify1 and self.body_type == 0x02)) and len(super().body) > 20: + if ( + ( + self.message_type in [MessageType.query, MessageType.set] + and self.body_type == 0x00 + ) + or (self.message_type == MessageType.notify1 and self.body_type == 0x02) + ) and len(super().body) > 20: self.set_body(CAGeneralMessageBody(super().body)) - elif (self.message_type == MessageType.exception and self.body_type == 0x01) or \ - (self.message_type == 0x03 and self.body_type == 0x02): + elif ( + self.message_type == MessageType.exception and self.body_type == 0x01 + ) or (self.message_type == 0x03 and self.body_type == 0x02): self.set_body(CAExceptionMessageBody(super().body)) elif self.message_type == MessageType.notify1 and self.body_type == 0x00: self.set_body(CANotify00MessageBody(super().body)) - elif self.message_type in [MessageType.query, MessageType.notify1] and self.body_type == 0x01: + elif ( + self.message_type in [MessageType.query, MessageType.notify1] + and self.body_type == 0x01 + ): self.set_body(CANotify01MessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cc/device.py b/custom_components/midea_ac_lan/midea/devices/cc/device.py index 289baaf6..7ae56f66 100644 --- a/custom_components/midea_ac_lan/midea/devices/cc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cc/device.py @@ -1,13 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageSet, - MessageCCResponse -) + +from .message import MessageCCResponse, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -34,26 +33,29 @@ class DeviceAttributes(StrEnum): class MideaCCDevice(MiedaDevice): _fan_speeds_7level = { - 0x01: "Level 1", 0x02: "Level 2", 0x04: "Level 3", - 0x08: "Level 4", 0x10: "Level 5", 0x20: "Level 6", - 0x40: "Level 7", 0x80: "Auto", - } - _fan_speeds_3level = { - 0x01: "Low", 0x08: "Medium", 0x40: "High", 0x80: "Auto" + 0x01: "Level 1", + 0x02: "Level 2", + 0x04: "Level 3", + 0x08: "Level 4", + 0x10: "Level 5", + 0x20: "Level 6", + 0x40: "Level 7", + 0x80: "Auto", } + _fan_speeds_3level = {0x01: "Low", 0x08: "Medium", 0x40: "High", 0x80: "Auto"} def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -82,8 +84,9 @@ def __init__( DeviceAttributes.fan_speed_level: None, DeviceAttributes.indoor_temperature: None, DeviceAttributes.temperature_precision: 1, - DeviceAttributes.temp_fahrenheit: False - }) + DeviceAttributes.temp_fahrenheit: False, + }, + ) self._fan_speeds = None @property @@ -106,32 +109,46 @@ def process_message(self, msg): else: self._attributes[status] = getattr(message, str(status)) new_status[str(status)] = getattr(message, str(status)) - if fan_speed is not None and self._attributes[DeviceAttributes.fan_speed_level] is not None: + if ( + fan_speed is not None + and self._attributes[DeviceAttributes.fan_speed_level] is not None + ): if self._fan_speeds is None: if self._attributes[DeviceAttributes.fan_speed_level]: self._fan_speeds = MideaCCDevice._fan_speeds_3level else: self._fan_speeds = MideaCCDevice._fan_speeds_7level if fan_speed in self._fan_speeds.keys(): - self._attributes[DeviceAttributes.fan_speed] = self._fan_speeds.get(fan_speed) + self._attributes[DeviceAttributes.fan_speed] = self._fan_speeds.get( + fan_speed + ) else: self._attributes[DeviceAttributes.fan_speed] = None - new_status[DeviceAttributes.fan_speed.value] = self._attributes[DeviceAttributes.fan_speed] - aux_heating = \ - self._attributes[DeviceAttributes.aux_heat_status] == 1 or \ - self._attributes[DeviceAttributes.auto_aux_heat_running] + new_status[DeviceAttributes.fan_speed.value] = self._attributes[ + DeviceAttributes.fan_speed + ] + aux_heating = ( + self._attributes[DeviceAttributes.aux_heat_status] == 1 + or self._attributes[DeviceAttributes.auto_aux_heat_running] + ) if self._attributes[DeviceAttributes.aux_heating] != aux_heating: self._attributes[DeviceAttributes.aux_heating] = aux_heating - new_status[DeviceAttributes.aux_heating.value] = self._attributes[DeviceAttributes.aux_heating] + new_status[DeviceAttributes.aux_heating.value] = self._attributes[ + DeviceAttributes.aux_heating + ] return new_status def make_message_set(self): message = MessageSet(self._protocol_version) message.power = self._attributes[DeviceAttributes.power] message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[DeviceAttributes.target_temperature] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] message.fan_speed = list(self._fan_speeds.keys())[ - list(self._fan_speeds.values()).index(self._attributes[DeviceAttributes.fan_speed]) + list(self._fan_speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + ) ] message.eco_mode = self._attributes[DeviceAttributes.eco_mode] message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] @@ -150,11 +167,13 @@ def set_target_temperature(self, target_temperature, mode): def set_attribute(self, attr, value): # if nat a sensor - if attr not in [DeviceAttributes.indoor_temperature, - DeviceAttributes.temperature_precision, - DeviceAttributes.fan_speed_level, - DeviceAttributes.aux_heat_status, - DeviceAttributes.auto_aux_heat_running]: + if attr not in [ + DeviceAttributes.indoor_temperature, + DeviceAttributes.temperature_precision, + DeviceAttributes.fan_speed_level, + DeviceAttributes.aux_heat_status, + DeviceAttributes.auto_aux_heat_running, + ]: message = self.make_message_set() if attr == DeviceAttributes.fan_speed: if value in self._fan_speeds.values(): diff --git a/custom_components/midea_ac_lan/midea/devices/cc/message.py b/custom_components/midea_ac_lan/midea/devices/cc/message.py index f71496a8..eb1dba47 100644 --- a/custom_components/midea_ac_lan/midea/devices/cc/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cc/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageCCBase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -37,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0xC3) + body_type=0xC3, + ) self.power = False self.mode = 4 self.fan_speed = 0x80 @@ -73,23 +70,38 @@ def _body(self): sleep_mode = 0x10 if self.sleep_mode else 0 night_light = 0x08 if self.night_light else 0 # Byte11 Dot of target_temperature - temperature_dot = int((self.target_temperature - temperature_integer) * 10) & 0xFF - return bytearray([ - power | mode, - fan_speed, - temperature_integer, - # timer - 0x00, 0x00, - eco_mode | ventilation | swing | aux_heating, - # non-stepless fan speed - 0xFF, - sleep_mode | night_light, - 0x00, 0x00, - temperature_dot, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - ]) + temperature_dot = ( + int((self.target_temperature - temperature_integer) * 10) & 0xFF + ) + return bytearray( + [ + power | mode, + fan_speed, + temperature_integer, + # timer + 0x00, + 0x00, + eco_mode | ventilation | swing | aux_heating, + # non-stepless fan speed + 0xFF, + sleep_mode | night_light, + 0x00, + 0x00, + temperature_dot, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class CCGeneralMessageBody(MessageBody): @@ -119,8 +131,13 @@ def __init__(self, body): class MessageCCResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type == MessageType.query and self.body_type == 0x01) or \ - (self.message_type in [MessageType.notify1, MessageType.notify2] and self.body_type == 0x01) or \ - (self.message_type == MessageType.set and self.body_type == 0xC3): + if ( + (self.message_type == MessageType.query and self.body_type == 0x01) + or ( + self.message_type in [MessageType.notify1, MessageType.notify2] + and self.body_type == 0x01 + ) + or (self.message_type == MessageType.set and self.body_type == 0xC3) + ): self.set_body(CCGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cd/device.py b/custom_components/midea_ac_lan/midea/devices/cd/device.py index 917a00b0..1fdfa998 100644 --- a/custom_components/midea_ac_lan/midea/devices/cd/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cd/device.py @@ -1,14 +1,13 @@ -import logging import json -from .message import ( - MessageQuery, - MessageSet, - MessageCDResponse -) +import logging + +from .message import MessageCDResponse, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -31,17 +30,17 @@ class MideaCDDevice(MiedaDevice): _modes = ["Energy-save", "Standard", "Dual", "Smart"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -64,8 +63,9 @@ def __init__( DeviceAttributes.outdoor_temperature: None, DeviceAttributes.condenser_temperature: None, DeviceAttributes.compressor_temperature: None, - DeviceAttributes.compressor_status: None - }) + DeviceAttributes.compressor_status: None, + }, + ) self._fields = {} self._temperature_step = None self._default_temperature_step = 1 @@ -99,12 +99,20 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [DeviceAttributes.mode, DeviceAttributes.power, DeviceAttributes.target_temperature]: + if attr in [ + DeviceAttributes.mode, + DeviceAttributes.power, + DeviceAttributes.target_temperature, + ]: message = MessageSet(self._protocol_version) message.fields = self._fields - message.mode = MideaCDDevice._modes.index(self._attributes[DeviceAttributes.mode]) + message.mode = MideaCDDevice._modes.index( + self._attributes[DeviceAttributes.mode] + ) message.power = self._attributes[DeviceAttributes.power] - message.target_temperature = self._attributes[DeviceAttributes.target_temperature] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] if attr == DeviceAttributes.mode: if value in MideaCDDevice._modes: setattr(message, str(attr), MideaCDDevice._modes.index(value)) diff --git a/custom_components/midea_ac_lan/midea/devices/cd/message.py b/custom_components/midea_ac_lan/midea/devices/cd/message.py index 40c4ba0c..454cfe00 100644 --- a/custom_components/midea_ac_lan/midea/devices/cd/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cd/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageCDBase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCD, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -37,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01) + body_type=0x01, + ) self.power = False self.target_temperature = 0 self.aux_heating = False @@ -53,13 +50,18 @@ def _body(self): power = 0x01 if self.power else 0x00 mode = self.mode + 1 target_temperature = round(self.target_temperature * 2 + 30) - return bytearray([ - 0x01, power, mode, target_temperature, - self.read_field("trValue"), - self.read_field("openPTC"), - self.read_field("ptcTemp"), - 0 # self.read_field("byte8") - ]) + return bytearray( + [ + 0x01, + power, + mode, + target_temperature, + self.read_field("trValue"), + self.read_field("openPTC"), + self.read_field("ptcTemp"), + 0, # self.read_field("byte8") + ] + ) class CDGeneralMessageBody(MessageBody): diff --git a/custom_components/midea_ac_lan/midea/devices/ce/device.py b/custom_components/midea_ac_lan/midea/devices/ce/device.py index a4e394b5..dde6a1da 100644 --- a/custom_components/midea_ac_lan/midea/devices/ce/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ce/device.py @@ -1,14 +1,13 @@ -import logging import json -from .message import ( - MessageQuery, - MessageCEResponse, - MessageSet -) +import logging + +from .message import MessageCEResponse, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -36,22 +35,20 @@ class DeviceAttributes(StrEnum): class MideaCEDevice(MiedaDevice): - _modes = [ - "Normal", "Sleep mode", "ECO mode" - ] + _modes = ["Normal", "Sleep mode", "ECO mode"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -82,8 +79,9 @@ def __init__( DeviceAttributes.powerful_purify: False, DeviceAttributes.filter_cleaning_reminder: False, DeviceAttributes.filter_change_reminder: False, - DeviceAttributes.error_code: 0 - }) + DeviceAttributes.error_code: 0, + }, + ) self._default_speed_count = 7 self._speed_count = self._default_speed_count self.set_customize(customize) @@ -114,7 +112,9 @@ def process_message(self, msg): self._attributes[DeviceAttributes.mode] = "ECO mode" else: self._attributes[DeviceAttributes.mode] = "None" - new_status[DeviceAttributes.mode.value] = self._attributes[DeviceAttributes.mode] + new_status[DeviceAttributes.mode.value] = self._attributes[ + DeviceAttributes.mode + ] return new_status def make_message_set(self): diff --git a/custom_components/midea_ac_lan/midea/devices/ce/message.py b/custom_components/midea_ac_lan/midea/devices/ce/message.py index c313709b..91c995a0 100644 --- a/custom_components/midea_ac_lan/midea/devices/ce/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ce/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageFABase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCE, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -37,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01) + body_type=0x01, + ) self.power = False self.fan_speed = 0 @@ -59,14 +56,16 @@ def _body(self): powerful_purify = 0x10 if self.powerful_purify else 0x00 scheduled = 0x01 if self.scheduled else 0x00 child_lock = 0x7F if self.child_lock else 0x00 - return bytearray([ - power | 0x01, - self.fan_speed, - link_to_ac | sleep_mode | eco_mode | aux_heating | powerful_purify, - scheduled, - 0x00, - child_lock - ]) + return bytearray( + [ + power | 0x01, + self.fan_speed, + link_to_ac | sleep_mode | eco_mode | aux_heating | powerful_purify, + scheduled, + 0x00, + child_lock, + ] + ) class CEGeneralMessageBody(MessageBody): @@ -126,8 +125,10 @@ def __init__(self, body): class MessageCEResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type in [MessageType.query, MessageType.set] and self.body_type == 0x01) or \ - (self.message_type == MessageType.notify1 and self.body_type == 0x02): + if ( + self.message_type in [MessageType.query, MessageType.set] + and self.body_type == 0x01 + ) or (self.message_type == MessageType.notify1 and self.body_type == 0x02): self.set_body(CEGeneralMessageBody(super().body)) elif self.message_type == MessageType.notify1 and self.body_type == 0x01: self.set_body(CENotifyMessageBody(super().body)) diff --git a/custom_components/midea_ac_lan/midea/devices/cf/device.py b/custom_components/midea_ac_lan/midea/devices/cf/device.py index bbfad209..fada640d 100644 --- a/custom_components/midea_ac_lan/midea/devices/cf/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cf/device.py @@ -1,13 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageCFResponse, - MessageSet -) + +from .message import MessageCFResponse, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -25,17 +24,17 @@ class DeviceAttributes(StrEnum): class MideaCFDevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -55,8 +54,9 @@ def __init__( DeviceAttributes.aux_heating: False, DeviceAttributes.current_temperature: 0, DeviceAttributes.max_temperature: 55, - DeviceAttributes.min_temperature: 5 - }) + DeviceAttributes.min_temperature: 5, + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] diff --git a/custom_components/midea_ac_lan/midea/devices/cf/message.py b/custom_components/midea_ac_lan/midea/devices/cf/message.py index 6c7105bd..0ca806fb 100644 --- a/custom_components/midea_ac_lan/midea/devices/cf/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cf/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageCFBase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCF, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -37,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01) + body_type=0x01, + ) self.power = False self.mode = 0 # 1 自动 2 制冷 3 制热 self.target_temperature = None @@ -47,11 +44,15 @@ def __init__(self, protocol_version): def _body(self): power = 0x01 if self.power else 0x00 mode = self.mode - target_temperature = 0xFF if self.target_temperature is None else (int(self.target_temperature) & 0xFF) - aux_heating = 0xFF if self.aux_heating is None else (0x01 if self.aux_heating else 0x00) - return bytearray([ - power, mode, target_temperature, aux_heating - ]) + target_temperature = ( + 0xFF + if self.target_temperature is None + else (int(self.target_temperature) & 0xFF) + ) + aux_heating = ( + 0xFF if self.aux_heating is None else (0x01 if self.aux_heating else 0x00) + ) + return bytearray([power, mode, target_temperature, aux_heating]) class CFMessageBody(MessageBody): @@ -77,7 +78,10 @@ def __init__(self, body, data_offset=0): class MessageCFResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] and self.body_type == 0x01: + if ( + self.message_type in [MessageType.query, MessageType.set] + and self.body_type == 0x01 + ): self.set_body(CFMessageBody(super().body, data_offset=1)) elif self.message_type in [MessageType.notify1, MessageType.notify2]: self.set_body(CFMessageBody(super().body, data_offset=0)) diff --git a/custom_components/midea_ac_lan/midea/devices/da/device.py b/custom_components/midea_ac_lan/midea/devices/da/device.py index 41b3a624..579e8827 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/device.py +++ b/custom_components/midea_ac_lan/midea/devices/da/device.py @@ -1,14 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessagePower, - MessageStart, - MessageDAResponse -) + +from .message import MessageDAResponse, MessagePower, MessageQuery, MessageStart + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -36,17 +34,17 @@ class DeviceAttributes(StrEnum): class MideaDADevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -76,8 +74,9 @@ def __init__( DeviceAttributes.wash_level: None, DeviceAttributes.wash_strength: None, DeviceAttributes.softener: None, - DeviceAttributes.detergent: None - }) + DeviceAttributes.detergent: None, + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -86,15 +85,47 @@ def process_message(self, msg): message = MessageDAResponse(msg) _LOGGER.debug(f"[{self.device_id}] Received: {message}") new_status = {} - progress = ["Idle", "Spin", "Rinse", "Wash", - "Weight", "Unknown", "Dry", "Soak"] - program = ["Standard", "Fast", "Blanket", "Wool", - "embathe", "Memory", "Child", "Down Jacket", - "Stir", "Mute", "Bucket Self Clean", "Air Dry"] + progress = ["Idle", "Spin", "Rinse", "Wash", "Weight", "Unknown", "Dry", "Soak"] + program = [ + "Standard", + "Fast", + "Blanket", + "Wool", + "embathe", + "Memory", + "Child", + "Down Jacket", + "Stir", + "Mute", + "Bucket Self Clean", + "Air Dry", + ] speed = ["-", "Low", "Medium", "High"] strength = ["-", "Week", "Medium", "Strong"] - detergent = ["No", "Less", "Medium", "More", "4", "5", "6", "7", "8", "Insufficient"] - softener = ["No", "Intelligent", "Programed", "3", "4", "5", "6", "7", "8", "Insufficient"] + detergent = [ + "No", + "Less", + "Medium", + "More", + "4", + "5", + "6", + "7", + "8", + "Insufficient", + ] + softener = [ + "No", + "Intelligent", + "Programed", + "3", + "4", + "5", + "6", + "7", + "8", + "Insufficient", + ] for status in self._attributes.keys(): if hasattr(message, str(status)): if status == DeviceAttributes.progress: diff --git a/custom_components/midea_ac_lan/midea/devices/da/message.py b/custom_components/midea_ac_lan/midea/devices/da/message.py index 25b1dd81..89064e77 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/message.py +++ b/custom_components/midea_ac_lan/midea/devices/da/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageDABase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xDA, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x03) + body_type=0x03, + ) @property def _body(self): @@ -37,15 +33,14 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02) + body_type=0x02, + ) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray([ - power, 0xFF - ]) + return bytearray([power, 0xFF]) class MessageStart(MessageDABase): @@ -53,21 +48,18 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02) + body_type=0x02, + ) self.start = False self.washing_data = bytearray([]) @property def _body(self): if self.start: - return bytearray([ - 0xFF, 0x01 - ]) + self.washing_data + return bytearray([0xFF, 0x01]) + self.washing_data else: # Stop - return bytearray([ - 0xFF, 0x00 - ]) + return bytearray([0xFF, 0x00]) class DAGeneralMessageBody(MessageBody): @@ -79,14 +71,14 @@ def __init__(self, body): self.program = body[4] self.wash_time = body[9] self.soak_time = body[12] - self.dehydration_time = (body[10] & 0xf0) >> 4 - self.dehydration_speed = (body[6] & 0xf0) >> 4 - self.rinse_count = body[10] & 0xf - self.rinse_level = (body[5] & 0xf0) >> 4 - self.wash_level = body[5] & 0xf - self.wash_strength = body[6] & 0xf - self.softener = (body[8] & 0xf0) >> 4 - self.detergent = body[8] & 0x0f + self.dehydration_time = (body[10] & 0xF0) >> 4 + self.dehydration_speed = (body[6] & 0xF0) >> 4 + self.rinse_count = body[10] & 0xF + self.rinse_level = (body[5] & 0xF0) >> 4 + self.wash_level = body[5] & 0xF + self.wash_strength = body[6] & 0xF + self.softener = (body[8] & 0xF0) >> 4 + self.detergent = body[8] & 0x0F self.washing_data = body[3:15] self.progress = 0 for i in range(1, 7): @@ -102,7 +94,8 @@ def __init__(self, body): class MessageDAResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or \ - (self.message_type == MessageType.notify1 and self.body_type == 0x04): + if self.message_type in [MessageType.query, MessageType.set] or ( + self.message_type == MessageType.notify1 and self.body_type == 0x04 + ): self.set_body(DAGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/db/device.py b/custom_components/midea_ac_lan/midea/devices/db/device.py index 40957a04..a18e253a 100644 --- a/custom_components/midea_ac_lan/midea/devices/db/device.py +++ b/custom_components/midea_ac_lan/midea/devices/db/device.py @@ -1,14 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessagePower, - MessageStart, - MessageDBResponse -) + +from .message import MessageDBResponse, MessagePower, MessageQuery, MessageStart + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,17 +22,17 @@ class DeviceAttributes(StrEnum): class MideaDBDevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -52,8 +50,9 @@ def __init__( DeviceAttributes.start: False, DeviceAttributes.washing_data: bytearray([]), DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None - }) + DeviceAttributes.time_remaining: None, + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -62,8 +61,17 @@ def process_message(self, msg): message = MessageDBResponse(msg) _LOGGER.debug(f"[{self.device_id}] Received: {message}") new_status = {} - progress = ["Idle", "Spin", "Rinse", "Wash", "Pre-wash", - "Dry", "Weight", "Hi-speed Spin", "Unknown"] + progress = [ + "Idle", + "Spin", + "Rinse", + "Wash", + "Pre-wash", + "Dry", + "Weight", + "Hi-speed Spin", + "Unknown", + ] for status in self._attributes.keys(): if hasattr(message, str(status)): if status == DeviceAttributes.progress: diff --git a/custom_components/midea_ac_lan/midea/devices/db/message.py b/custom_components/midea_ac_lan/midea/devices/db/message.py index c581d1e3..63e918b3 100644 --- a/custom_components/midea_ac_lan/midea/devices/db/message.py +++ b/custom_components/midea_ac_lan/midea/devices/db/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageDBBase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xDB, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x03) + body_type=0x03, + ) @property def _body(self): @@ -37,20 +33,38 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02) + body_type=0x02, + ) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray([ - power, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF - ]) + return bytearray( + [ + power, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + ] + ) class MessageStart(MessageDBBase): @@ -58,21 +72,18 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02) + body_type=0x02, + ) self.start = False self.washing_data = bytearray([]) @property def _body(self): - if self.start: # Pause - return bytearray([ - 0xFF, 0x01 - ]) + self.washing_data + if self.start: # Pause + return bytearray([0xFF, 0x01]) + self.washing_data else: # Pause - return bytearray([ - 0xFF, 0x00 - ]) + return bytearray([0xFF, 0x00]) class DBGeneralMessageBody(MessageBody): @@ -95,7 +106,8 @@ def __init__(self, body): class MessageDBResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or \ - (self.message_type == MessageType.notify1 and self.body_type == 0x04): + if self.message_type in [MessageType.query, MessageType.set] or ( + self.message_type == MessageType.notify1 and self.body_type == 0x04 + ): self.set_body(DBGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/dc/device.py b/custom_components/midea_ac_lan/midea/devices/dc/device.py index b99362a5..302f514e 100644 --- a/custom_components/midea_ac_lan/midea/devices/dc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/dc/device.py @@ -1,14 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessagePower, - MessageStart, - MessageDCResponse -) + +from .message import MessageDCResponse, MessagePower, MessageQuery, MessageStart + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,17 +22,17 @@ class DeviceAttributes(StrEnum): class MideaDADevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -52,8 +50,9 @@ def __init__( DeviceAttributes.start: False, DeviceAttributes.washing_data: bytearray([]), DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None - }) + DeviceAttributes.time_remaining: None, + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -62,8 +61,16 @@ def process_message(self, msg): message = MessageDCResponse(msg) _LOGGER.debug(f"[{self.device_id}] Received: {message}") new_status = {} - progress = ["Prog0", "Prog1", "Prog2", "Prog3", - "Prog4", "Prog5", "Prog6", "Prog7"] + progress = [ + "Prog0", + "Prog1", + "Prog2", + "Prog3", + "Prog4", + "Prog5", + "Prog6", + "Prog7", + ] for status in self._attributes.keys(): if hasattr(message, str(status)): if status == DeviceAttributes.progress: diff --git a/custom_components/midea_ac_lan/midea/devices/dc/message.py b/custom_components/midea_ac_lan/midea/devices/dc/message.py index ed36038a..03c45141 100644 --- a/custom_components/midea_ac_lan/midea/devices/dc/message.py +++ b/custom_components/midea_ac_lan/midea/devices/dc/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageDCBase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xDC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x03) + body_type=0x03, + ) @property def _body(self): @@ -37,15 +33,14 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02) + body_type=0x02, + ) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray([ - power, 0xFF - ]) + return bytearray([power, 0xFF]) class MessageStart(MessageDCBase): @@ -53,21 +48,18 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02) + body_type=0x02, + ) self.start = False self.washing_data = bytearray([]) @property def _body(self): if self.start: - return bytearray([ - 0xFF, 0x01 - ]) + self.washing_data + return bytearray([0xFF, 0x01]) + self.washing_data else: # Stop - return bytearray([ - 0xFF, 0x00 - ]) + return bytearray([0xFF, 0x00]) class DCGeneralMessageBody(MessageBody): @@ -90,7 +82,8 @@ def __init__(self, body): class MessageDCResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or \ - (self.message_type == MessageType.notify1 and self.body_type == 0x04): + if self.message_type in [MessageType.query, MessageType.set] or ( + self.message_type == MessageType.notify1 and self.body_type == 0x04 + ): self.set_body(DCGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e1/device.py b/custom_components/midea_ac_lan/midea/devices/e1/device.py index 0064bfe4..74c6a390 100644 --- a/custom_components/midea_ac_lan/midea/devices/e1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e1/device.py @@ -1,15 +1,18 @@ import logging + from .message import ( - MessageQuery, + MessageE1Response, + MessageLock, MessagePower, + MessageQuery, MessageStorage, - MessageLock, - MessageE1Response ) + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -44,17 +47,17 @@ class DeviceAttributes(StrEnum): class MideaE1Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -91,31 +94,32 @@ def __init__( DeviceAttributes.error_code: None, DeviceAttributes.softwater: 0, DeviceAttributes.wrong_operation: None, - DeviceAttributes.bright: 0 - }) + DeviceAttributes.bright: 0, + }, + ) self._modes = { - 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR - 0x1: "Auto", # BYTE_MODE_AUTO_WASH - 0x2: "Heavy", # BYTE_MODE_STRONG_WASH - 0x3: "Normal", # BYTE_MODE_STANDARD_WASH - 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH - 0x5: "Delicate", # BYTE_MODE_GLASS_WASH - 0x6: "Hour", # BYTE_MODE_HOUR_WASH - 0x7: "Quick", # BYTE_MODE_FAST_WASH - 0x8: "Rinse", # BYTE_MODE_SOAK_WASH - 0x9: "90min", # BYTE_MODE_90MIN_WASH - 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN - 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH - 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE - 0xD: "Germ", # BYTE_MODE_GERM ??? - 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH - 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM - 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH - 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH - 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH - 0x14: "Less Wash", # BYTE_MODE_LESS_WASH - 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH - 0x19: "Cloud Wash" # BYTE_MODE_CLOUD_WASH + 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR + 0x1: "Auto", # BYTE_MODE_AUTO_WASH + 0x2: "Heavy", # BYTE_MODE_STRONG_WASH + 0x3: "Normal", # BYTE_MODE_STANDARD_WASH + 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH + 0x5: "Delicate", # BYTE_MODE_GLASS_WASH + 0x6: "Hour", # BYTE_MODE_HOUR_WASH + 0x7: "Quick", # BYTE_MODE_FAST_WASH + 0x8: "Rinse", # BYTE_MODE_SOAK_WASH + 0x9: "90min", # BYTE_MODE_90MIN_WASH + 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN + 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH + 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE + 0xD: "Germ", # BYTE_MODE_GERM ??? + 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH + 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM + 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH + 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH + 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH + 0x14: "Less Wash", # BYTE_MODE_LESS_WASH + 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH + 0x19: "Cloud Wash", # BYTE_MODE_CLOUD_WASH } self._status = ["Off", "Idle", "Delay", "Running", "Error"] self._progress = ["Idle", "Pre-wash", "Wash", "Rinse", "Dry", "Complete"] diff --git a/custom_components/midea_ac_lan/midea/devices/e1/message.py b/custom_components/midea_ac_lan/midea/devices/e1/message.py index 9ec073c2..f74ee45d 100644 --- a/custom_components/midea_ac_lan/midea/devices/e1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e1/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageE1Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xE1, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,16 +20,14 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x08) + body_type=0x08, + ) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray([ - power, - 0x00, 0x00, 0x00 - ]) + return bytearray([power, 0x00, 0x00, 0x00]) class MessageLock(MessageE1Base): @@ -42,7 +35,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x83) + body_type=0x83, + ) self.lock = False @property @@ -56,14 +50,18 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x81) + body_type=0x81, + ) self.storage = False @property def _body(self): storage = 0x01 if self.storage else 0x00 - return bytearray([0x00, 0x00, 0x00, storage]) + \ - bytearray([0xff] * 6) + bytearray([0x00] * 27) + return ( + bytearray([0x00, 0x00, 0x00, storage]) + + bytearray([0xFF] * 6) + + bytearray([0x00] * 27) + ) class MessageQuery(MessageE1Base): @@ -71,7 +69,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00) + body_type=0x00, + ) @property def _body(self): @@ -85,9 +84,9 @@ def __init__(self, body): self.status = body[1] self.mode = body[2] self.additional = body[3] - self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close - self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage - self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage + self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close + self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage + self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage start_pause = (body[5] & 0x08) > 0 if start_pause: self.start = True @@ -115,7 +114,9 @@ def __init__(self, body): class MessageE1Response(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or \ - (self.message_type in [MessageType.query, MessageType.notify1] and self.body_type == 0): + if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or ( + self.message_type in [MessageType.query, MessageType.notify1] + and self.body_type == 0 + ): self.set_body(E1GeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e2/device.py b/custom_components/midea_ac_lan/midea/devices/e2/device.py index 386a5467..ae0cf739 100644 --- a/custom_components/midea_ac_lan/midea/devices/e2/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e2/device.py @@ -1,16 +1,19 @@ -import logging import json +import logging + from .message import ( - MessageQuery, - MessageSet, MessageE2Response, + MessageNewProtocolSet, MessagePower, - MessageNewProtocolSet + MessageQuery, + MessageSet, ) + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -32,17 +35,17 @@ class DeviceAttributes(StrEnum): class MideaE2Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -66,8 +69,9 @@ def __init__( DeviceAttributes.variable_heating: False, DeviceAttributes.heating_time_remaining: 0, DeviceAttributes.water_consumption: None, - DeviceAttributes.heating_power: None - }) + DeviceAttributes.heating_power: None, + }, + ) self._default_old_protocol = "auto" self._old_protocol = self._default_old_protocol self.set_customize(customize) @@ -91,15 +95,21 @@ def process_message(self, msg): def make_message_set(self): message = MessageSet(self._protocol_version) message.protection = self._attributes[DeviceAttributes.protection] - message.whole_tank_heating = self._attributes[DeviceAttributes.whole_tank_heating] - message.target_temperature = self._attributes[DeviceAttributes.target_temperature] + message.whole_tank_heating = self._attributes[ + DeviceAttributes.whole_tank_heating + ] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] message.variable_heating = self._attributes[DeviceAttributes.variable_heating] return message def set_attribute(self, attr, value): - if attr not in [DeviceAttributes.heating, - DeviceAttributes.keep_warm, - DeviceAttributes.current_temperature]: + if attr not in [ + DeviceAttributes.heating, + DeviceAttributes.keep_warm, + DeviceAttributes.current_temperature, + ]: if self._old_protocol is not None and self._old_protocol != "auto": old_protocol = self._old_protocol else: diff --git a/custom_components/midea_ac_lan/midea/devices/e2/message.py b/custom_components/midea_ac_lan/midea/devices/e2/message.py index fb95363a..e07c8669 100644 --- a/custom_components/midea_ac_lan/midea/devices/e2/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e2/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageE2Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xE2, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -37,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02) + body_type=0x02, + ) self.power = False @property @@ -54,7 +51,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x14) + body_type=0x14, + ) self.target_temperature = None self.variable_heating = None self.whole_tank_heating = None @@ -80,7 +78,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x04) + body_type=0x04, + ) self.target_temperature = 0 self.variable_heating = False self.whole_tank_heating = False @@ -95,18 +94,28 @@ def _body(self): target_temperature = self.target_temperature & 0xFF # Byte 9 variable_heating variable_heating = 0x10 if self.variable_heating else 0x00 - return bytearray([ - 0x01, - 0x00, - 0x80, - whole_tank_heating | protection, - target_temperature, - 0x00, 0x00, 0x00, - variable_heating, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00 - ]) + return bytearray( + [ + 0x01, + 0x00, + 0x80, + whole_tank_heating | protection, + target_temperature, + 0x00, + 0x00, + 0x00, + variable_heating, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class E2GeneralMessageBody(MessageBody): @@ -130,7 +139,12 @@ def __init__(self, body): class MessageE2Response(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type in [MessageType.query, MessageType.notify1] and self.body_type == 0x01) or \ - (self.message_type == MessageType.set and self.body_type in [0x01, 0x02, 0x04, 0x14]): + if ( + self.message_type in [MessageType.query, MessageType.notify1] + and self.body_type == 0x01 + ) or ( + self.message_type == MessageType.set + and self.body_type in [0x01, 0x02, 0x04, 0x14] + ): self.set_body(E2GeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e3/device.py b/custom_components/midea_ac_lan/midea/devices/e3/device.py index 5ad753d2..27cf42da 100644 --- a/custom_components/midea_ac_lan/midea/devices/e3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e3/device.py @@ -1,16 +1,19 @@ -import logging import json +import logging + from .message import ( - MessageQuery, - MessageSet, + MessageE3Response, MessageNewProtocolSet, MessagePower, - MessageE3Response + MessageQuery, + MessageSet, ) + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -29,17 +32,17 @@ class DeviceAttributes(StrEnum): class MideaE3Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -61,10 +64,9 @@ def __init__( DeviceAttributes.smart_volume: False, DeviceAttributes.current_temperature: None, DeviceAttributes.target_temperature: 40, - }) - self._old_subtypes = [ - 32, 33, 34, 35, 36, 37, 40, 43, 48, 49, 80 - ] + }, + ) + self._old_subtypes = [32, 33, 34, 35, 36, 37, 40, 43, 48, 49, 80] self._precision_halves = None self._default_precision_halves = False self.set_customize(customize) @@ -82,7 +84,10 @@ def process_message(self, msg): new_status = {} for status in self._attributes.keys(): if hasattr(message, str(status)): - if self._precision_halves and status in [DeviceAttributes.current_temperature, DeviceAttributes.target_temperature]: + if self._precision_halves and status in [ + DeviceAttributes.current_temperature, + DeviceAttributes.target_temperature, + ]: self._attributes[status] = getattr(message, str(status)) / 2 else: self._attributes[status] = getattr(message, str(status)) @@ -96,13 +101,17 @@ def make_message_set(self): message.protection = self._attributes[DeviceAttributes.protection] message.zero_clod_pulse = self._attributes[DeviceAttributes.zero_cold_pulse] message.smart_volume = self._attributes[DeviceAttributes.smart_volume] - message.target_temperature = self._attributes[DeviceAttributes.target_temperature] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] return message def set_attribute(self, attr, value): - if attr not in [DeviceAttributes.burning_state, - DeviceAttributes.current_temperature, - DeviceAttributes.protection]: + if attr not in [ + DeviceAttributes.burning_state, + DeviceAttributes.current_temperature, + DeviceAttributes.protection, + ]: if self._precision_halves and attr == DeviceAttributes.target_temperature: value = int(value * 2) if attr == DeviceAttributes.power: diff --git a/custom_components/midea_ac_lan/midea/devices/e3/message.py b/custom_components/midea_ac_lan/midea/devices/e3/message.py index 068644a3..53004ed7 100644 --- a/custom_components/midea_ac_lan/midea/devices/e3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e3/message.py @@ -1,17 +1,11 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType NEW_PROTOCOL_PARAMS = { "zero_cold_water": 0x03, # "zero_cold_master": 0x12, "zero_cold_pulse": 0x04, "smart_volume": 0x07, - "target_temperature": 0x08 + "target_temperature": 0x08, } @@ -21,7 +15,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xE3, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -34,7 +28,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): @@ -46,7 +41,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02) + body_type=0x02, + ) self.power = False @property @@ -63,7 +59,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x04) + body_type=0x04, + ) self.target_temperature = 0 self.zero_cold_water = False @@ -83,17 +80,24 @@ def _body(self): # Byte 5 target_temperature target_temperature = self.target_temperature & 0xFF - return bytearray([ - 0x01, - zero_cold_water | 0x02, - protection | zero_cold_pulse | smart_volume, - 0x00, - target_temperature, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, - - ]) + return bytearray( + [ + 0x01, + zero_cold_water | 0x02, + protection | zero_cold_pulse | smart_volume, + 0x00, + target_temperature, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class MessageNewProtocolSet(MessageE3Base): @@ -101,7 +105,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x14) + body_type=0x14, + ) self.key = None self.value = None @@ -112,14 +117,29 @@ def _body(self): value = self.value else: value = 0x01 if self.value else 0x00 - return bytearray([ - key, value, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00 - ]) + return bytearray( + [ + key, + value, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class E3GeneralMessageBody(MessageBody): @@ -138,8 +158,16 @@ def __init__(self, body): class MessageE3Response(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type == MessageType.query and self.body_type == 0x01) or \ - (self.message_type == MessageType.set and self.body_type in [0x01, 0x02, 0x04, 0x14]) or \ - (self.message_type == MessageType.notify1 and self.body_type in [0x00, 0x01]): + if ( + (self.message_type == MessageType.query and self.body_type == 0x01) + or ( + self.message_type == MessageType.set + and self.body_type in [0x01, 0x02, 0x04, 0x14] + ) + or ( + self.message_type == MessageType.notify1 + and self.body_type in [0x00, 0x01] + ) + ): self.set_body(E3GeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e6/device.py b/custom_components/midea_ac_lan/midea/devices/e6/device.py index 75109667..5c2751c8 100644 --- a/custom_components/midea_ac_lan/midea/devices/e6/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e6/device.py @@ -1,13 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageSet, - MessageE6Response -) + +from .message import MessageE6Response, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -28,17 +27,17 @@ class DeviceAttributes(StrEnum): class MideaE6Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -61,8 +60,9 @@ def __init__( DeviceAttributes.heating_temperature: 50, DeviceAttributes.bathing_temperature: 40, DeviceAttributes.heating_leaving_temperature: None, - DeviceAttributes.bathing_leaving_temperature: None - }) + DeviceAttributes.bathing_leaving_temperature: None, + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -78,10 +78,12 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [DeviceAttributes.main_power, - DeviceAttributes.heating_power, - DeviceAttributes.heating_temperature, - DeviceAttributes.bathing_temperature]: + if attr in [ + DeviceAttributes.main_power, + DeviceAttributes.heating_power, + DeviceAttributes.heating_temperature, + DeviceAttributes.bathing_temperature, + ]: message = MessageSet(self._protocol_version) setattr(message, str(attr), value) self.build_send(message) diff --git a/custom_components/midea_ac_lan/midea/devices/e6/message.py b/custom_components/midea_ac_lan/midea/devices/e6/message.py index 79738af4..230e2d5d 100644 --- a/custom_components/midea_ac_lan/midea/devices/e6/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e6/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageE6Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type): device_type=0xE6, protocol_version=protocol_version, message_type=message_type, - body_type=None + body_type=None, ) @property @@ -27,8 +22,7 @@ def _body(self): class MessageQuery(MessageE6Base): def __init__(self, protocol_version): super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query + protocol_version=protocol_version, message_type=MessageType.query ) @property @@ -39,8 +33,7 @@ def _body(self): class MessageSet(MessageE6Base): def __init__(self, protocol_version): super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set + protocol_version=protocol_version, message_type=MessageType.set ) self.main_power = None self.heating_temperature = None @@ -71,14 +64,8 @@ def __init__(self, body): self.heating_working = (body[2] & 0x10) > 0 self.bathing_working = (body[2] & 0x20) > 0 self.heating_power = (body[4] & 0x01) > 0 - self.min_temperature = [ - body[16], - body[11] - ] - self.max_temperature = [ - body[15], - body[10] - ] + self.min_temperature = [body[16], body[11]] + self.max_temperature = [body[15], body[10]] self.heating_temperature = body[17] self.bathing_temperature = body[12] self.heating_leaving_temperature = body[14] diff --git a/custom_components/midea_ac_lan/midea/devices/e8/device.py b/custom_components/midea_ac_lan/midea/devices/e8/device.py index c479e374..3d98bf4e 100644 --- a/custom_components/midea_ac_lan/midea/devices/e8/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e8/device.py @@ -1,12 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageE8Response -) + +from .message import MessageE8Response, MessageQuery + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -25,22 +25,26 @@ class DeviceAttributes(StrEnum): class MideaE8Device(MiedaDevice): _status = { - 0x00: "Standby", 0x01: "Delay", 0x02: "Working", - 0x03: "Paused", 0x04: "Keep-Warming", 0xFF: "Error" + 0x00: "Standby", + 0x01: "Delay", + 0x02: "Working", + 0x03: "Paused", + 0x04: "Keep-Warming", + 0xFF: "Error", } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -62,7 +66,8 @@ def __init__( DeviceAttributes.current_temperature: None, DeviceAttributes.finished: None, DeviceAttributes.water_shortage: None, - }) + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -76,7 +81,9 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaE8Device._status.keys(): - self._attributes[DeviceAttributes.status] = MideaE8Device._status.get(value) + self._attributes[DeviceAttributes.status] = ( + MideaE8Device._status.get(value) + ) else: self._attributes[DeviceAttributes.status] = None else: diff --git a/custom_components/midea_ac_lan/midea/devices/e8/message.py b/custom_components/midea_ac_lan/midea/devices/e8/message.py index 9c45c688..6fe81409 100644 --- a/custom_components/midea_ac_lan/midea/devices/e8/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e8/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageE8Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xE8, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0xAA) + body_type=0xAA, + ) @property def _body(self): @@ -50,7 +46,10 @@ def __init__(self, message): super().__init__(message) if len(super().body) > 6: sub_cmd = super().body[6] - if ((self.message_type == MessageType.set and sub_cmd in [0x02, 0x04, 0x06]) or - self.message_type in [MessageType.query, MessageType.notify1] and sub_cmd == 2): + if ( + (self.message_type == MessageType.set and sub_cmd in [0x02, 0x04, 0x06]) + or self.message_type in [MessageType.query, MessageType.notify1] + and sub_cmd == 2 + ): self.set_body(E8MessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ea/device.py b/custom_components/midea_ac_lan/midea/devices/ea/device.py index 66c2a8e3..c5268d26 100644 --- a/custom_components/midea_ac_lan/midea/devices/ea/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ea/device.py @@ -1,12 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageEAResponse -) + +from .message import MessageEAResponse, MessageQuery + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,43 +24,122 @@ class DeviceAttributes(StrEnum): class MideaEADevice(MiedaDevice): - _mode_list = [ - "smart", "reserve", "cook_rice", "fast_cook_rice", "standard_cook_rice", - "gruel", "cook_congee", "stew_soup", "stewing", "heat_rice", "make_cake", - "yoghourt", "soup_rice", "coarse_rice", "five_ceeals_rice", "eight_treasures_rice", - "crispy_rice", "shelled_rice", "eight_treasures_congee", "infant_congee", - "older_rice", "rice_soup", "rice_paste", "egg_custard", "warm_milk", - "hot_spring_egg", "millet_congee", "firewood_rice", "few_rice", - "red_potato", "corn", "quick_freeze_bun", "steam_ribs", "steam_egg", - "coarse_congee", "steep_rice", "appetizing_congee", "corn_congee", - "sprout_rice", "luscious_rice", "luscious_boiled", "fast_rice", "fast_boil", - "bean_rice_congee", "fast_congee", "baby_congee", "cook_soup", "congee_coup", - "steam_corn", "steam_red_potato", "boil_congee", "delicious_steam", "boil_egg", - "rice_wine", "fruit_vegetable_paste", "vegetable_porridge", "pork_porridge", - "fragrant_rice", "assorte_rice", "steame_fish", "baby_rice", "essence_rice", - "fragrant_dense_congee", "one_two_cook", "original_steame", "hot_fast_rice", - "online_celebrity_rice", "sushi_rice", "stone_bowl_rice", "no_water_treat", - "keep_fresh", "low_sugar_rice", "black_buckwheat_rice", "resveratrol_rice", - "yellow_wheat_rice", "green_buckwheat_rice", "roughage_rice", "millet_mixed_rice", - "iron_pan_rice", "olla_pan_rice", "vegetable_rice", "baby_side", "regimen_congee", - "earthen_pot_congee", "regimen_soup", "pottery_jar_soup", "canton_soup", - "nutrition_stew", "northeast_stew", "uncap_boil", "trichromatic_coarse_grain", - "four_color_vegetables", "egg", "chop", - ] + ["unknown"] * 98 + ["clean"] + ["unknown"] * 5 + ["keep_warm"] + _mode_list = ( + [ + "smart", + "reserve", + "cook_rice", + "fast_cook_rice", + "standard_cook_rice", + "gruel", + "cook_congee", + "stew_soup", + "stewing", + "heat_rice", + "make_cake", + "yoghourt", + "soup_rice", + "coarse_rice", + "five_ceeals_rice", + "eight_treasures_rice", + "crispy_rice", + "shelled_rice", + "eight_treasures_congee", + "infant_congee", + "older_rice", + "rice_soup", + "rice_paste", + "egg_custard", + "warm_milk", + "hot_spring_egg", + "millet_congee", + "firewood_rice", + "few_rice", + "red_potato", + "corn", + "quick_freeze_bun", + "steam_ribs", + "steam_egg", + "coarse_congee", + "steep_rice", + "appetizing_congee", + "corn_congee", + "sprout_rice", + "luscious_rice", + "luscious_boiled", + "fast_rice", + "fast_boil", + "bean_rice_congee", + "fast_congee", + "baby_congee", + "cook_soup", + "congee_coup", + "steam_corn", + "steam_red_potato", + "boil_congee", + "delicious_steam", + "boil_egg", + "rice_wine", + "fruit_vegetable_paste", + "vegetable_porridge", + "pork_porridge", + "fragrant_rice", + "assorte_rice", + "steame_fish", + "baby_rice", + "essence_rice", + "fragrant_dense_congee", + "one_two_cook", + "original_steame", + "hot_fast_rice", + "online_celebrity_rice", + "sushi_rice", + "stone_bowl_rice", + "no_water_treat", + "keep_fresh", + "low_sugar_rice", + "black_buckwheat_rice", + "resveratrol_rice", + "yellow_wheat_rice", + "green_buckwheat_rice", + "roughage_rice", + "millet_mixed_rice", + "iron_pan_rice", + "olla_pan_rice", + "vegetable_rice", + "baby_side", + "regimen_congee", + "earthen_pot_congee", + "regimen_soup", + "pottery_jar_soup", + "canton_soup", + "nutrition_stew", + "northeast_stew", + "uncap_boil", + "trichromatic_coarse_grain", + "four_color_vegetables", + "egg", + "chop", + ] + + ["unknown"] * 98 + + ["clean"] + + ["unknown"] * 5 + + ["keep_warm"] + ) _progress = ["Idle", "Delay", "Cooking", "Keep-warm"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -81,8 +160,9 @@ def __init__( DeviceAttributes.top_temperature: None, DeviceAttributes.bottom_temperature: None, DeviceAttributes.keep_warm_time: None, - DeviceAttributes.progress: "Unknown" - }) + DeviceAttributes.progress: "Unknown", + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] diff --git a/custom_components/midea_ac_lan/midea/devices/ea/message.py b/custom_components/midea_ac_lan/midea/devices/ea/message.py index e6f147a1..636ed1e2 100644 --- a/custom_components/midea_ac_lan/midea/devices/ea/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ea/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageEABase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xEA, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,13 +20,12 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=None) + body_type=None, + ) @property def body(self): - return bytearray([ - 0xAA, 0x55, 0x01, 0x03, 0x00 - ]) + return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00]) @property def _body(self): @@ -100,16 +94,22 @@ def __init__(self, message): if self.message_type == MessageType.set and super().body[5] == 0x16: # 381 self.set_body(EABody1(super().body)) elif self.message_type == MessageType.query: - if super().body[6] == 0x52 and super().body[7] == 0xc3: # 404 + if super().body[6] == 0x52 and super().body[7] == 0xC3: # 404 self.set_body(EABody2(super().body)) - elif super().body[5] == 0x3d: # 420 + elif super().body[5] == 0x3D: # 420 self.set_body(EABody1(super().body)) - elif self.message_type == MessageType.notify1 and super().body[5] == 0x3d: # 463 + elif ( + self.message_type == MessageType.notify1 and super().body[5] == 0x3D + ): # 463 self.set_body(EABody1(super().body)) else: - if (self.message_type == MessageType.set and super().body[3] == 0x02) or \ - (self.message_type == MessageType.query and super().body[3] == 0x03) or \ - (self.message_type == MessageType.notify1 and super().body[3] == 0x04): # 351 + if ( + (self.message_type == MessageType.set and super().body[3] == 0x02) + or (self.message_type == MessageType.query and super().body[3] == 0x03) + or ( + self.message_type == MessageType.notify1 and super().body[3] == 0x04 + ) + ): # 351 self.set_body(EABody3(super().body)) elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: self.mode = super().body[4] + (super().body[5] << 8) diff --git a/custom_components/midea_ac_lan/midea/devices/ec/device.py b/custom_components/midea_ac_lan/midea/devices/ec/device.py index 1b4429ed..0a66ab66 100644 --- a/custom_components/midea_ac_lan/midea/devices/ec/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ec/device.py @@ -1,12 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageECResponse -) + +from .message import MessageECResponse, MessageQuery + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,45 +24,134 @@ class DeviceAttributes(StrEnum): class MideaECDevice(MiedaDevice): - _mode_list = [ - "smart", "reserve", "cook_rice", "fast_cook_rice", "standard_cook_rice", - "gruel", "cook_congee", "stew_soup", "stewing", "heat_rice", "make_cake", - "yoghourt", "soup_rice", "coarse_rice", "five_ceeals_rice", "eight_treasures_rice", - "crispy_rice", "shelled_rice", "eight_treasures_congee", "infant_congee", - "older_rice", "rice_soup", "rice_paste", "egg_custard", "warm_milk", - "hot_spring_egg", "millet_congee", "firewood_rice", "few_rice", - "red_potato", "corn", "quick_freeze_bun", "steam_ribs", "steam_egg", - "coarse_congee", "steep_rice", "appetizing_congee", "corn_congee", - "sprout_rice", "luscious_rice", "luscious_boiled", "fast_rice", "fast_boil", - "bean_rice_congee", "fast_congee", "baby_congee", "cook_soup", "congee_coup", - "steam_corn", "steam_red_potato", "boil_congee", "delicious_steam", "boil_egg", - "rice_wine", "fruit_vegetable_paste", "vegetable_porridge", "pork_porridge", - "fragrant_rice", "assorte_rice", "steame_fish", "baby_rice", "essence_rice", - "fragrant_dense_congee", "one_two_cook", "original_steame", "hot_fast_rice", - "online_celebrity_rice", "sushi_rice", "stone_bowl_rice", "no_water_treat", - "keep_fresh", "low_sugar_rice", "black_buckwheat_rice", "resveratrol_rice", - "yellow_wheat_rice", "green_buckwheat_rice", "roughage_rice", "millet_mixed_rice", - "iron_pan_rice", "olla_pan_rice", "vegetable_rice", "baby_side", "regimen_congee", - "earthen_pot_congee", "regimen_soup", "pottery_jar_soup", "canton_soup", - "nutrition_stew", "northeast_stew", "uncap_boil", "trichromatic_coarse_grain", - "four_color_vegetables", "egg", "chop", - ] + ["unknown"] * 98 + ["clean"] + ["unknown"] * 5 + ["keep_warm", "diy"] - _progress = ["Idle", "Cooking", "Delay", "Keep-warm", - "Lid-open", "Relieving", "Keep-pressure", - "Relieving", "Cooking", "Relieving", "Lid-open"] + _mode_list = ( + [ + "smart", + "reserve", + "cook_rice", + "fast_cook_rice", + "standard_cook_rice", + "gruel", + "cook_congee", + "stew_soup", + "stewing", + "heat_rice", + "make_cake", + "yoghourt", + "soup_rice", + "coarse_rice", + "five_ceeals_rice", + "eight_treasures_rice", + "crispy_rice", + "shelled_rice", + "eight_treasures_congee", + "infant_congee", + "older_rice", + "rice_soup", + "rice_paste", + "egg_custard", + "warm_milk", + "hot_spring_egg", + "millet_congee", + "firewood_rice", + "few_rice", + "red_potato", + "corn", + "quick_freeze_bun", + "steam_ribs", + "steam_egg", + "coarse_congee", + "steep_rice", + "appetizing_congee", + "corn_congee", + "sprout_rice", + "luscious_rice", + "luscious_boiled", + "fast_rice", + "fast_boil", + "bean_rice_congee", + "fast_congee", + "baby_congee", + "cook_soup", + "congee_coup", + "steam_corn", + "steam_red_potato", + "boil_congee", + "delicious_steam", + "boil_egg", + "rice_wine", + "fruit_vegetable_paste", + "vegetable_porridge", + "pork_porridge", + "fragrant_rice", + "assorte_rice", + "steame_fish", + "baby_rice", + "essence_rice", + "fragrant_dense_congee", + "one_two_cook", + "original_steame", + "hot_fast_rice", + "online_celebrity_rice", + "sushi_rice", + "stone_bowl_rice", + "no_water_treat", + "keep_fresh", + "low_sugar_rice", + "black_buckwheat_rice", + "resveratrol_rice", + "yellow_wheat_rice", + "green_buckwheat_rice", + "roughage_rice", + "millet_mixed_rice", + "iron_pan_rice", + "olla_pan_rice", + "vegetable_rice", + "baby_side", + "regimen_congee", + "earthen_pot_congee", + "regimen_soup", + "pottery_jar_soup", + "canton_soup", + "nutrition_stew", + "northeast_stew", + "uncap_boil", + "trichromatic_coarse_grain", + "four_color_vegetables", + "egg", + "chop", + ] + + ["unknown"] * 98 + + ["clean"] + + ["unknown"] * 5 + + ["keep_warm", "diy"] + ) + _progress = [ + "Idle", + "Cooking", + "Delay", + "Keep-warm", + "Lid-open", + "Relieving", + "Keep-pressure", + "Relieving", + "Cooking", + "Relieving", + "Lid-open", + ] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -83,8 +172,9 @@ def __init__( DeviceAttributes.bottom_temperature: None, DeviceAttributes.keep_warm_time: None, DeviceAttributes.progress: "Unknown", - DeviceAttributes.with_pressure: None - }) + DeviceAttributes.with_pressure: None, + }, + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -98,7 +188,9 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.progress: if value < len(MideaECDevice._progress): - self._attributes[status] = MideaECDevice._progress[getattr(message, str(status))] + self._attributes[status] = MideaECDevice._progress[ + getattr(message, str(status)) + ] else: self._attributes[status] = "Unknown" elif status == DeviceAttributes.mode: diff --git a/custom_components/midea_ac_lan/midea/devices/ec/message.py b/custom_components/midea_ac_lan/midea/devices/ec/message.py index 3e0c42f2..e4d65645 100644 --- a/custom_components/midea_ac_lan/midea/devices/ec/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ec/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageECBase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xEC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,16 +20,12 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=None) + body_type=None, + ) @property def body(self): - return bytearray([ - 0xAA, 0x55, - 0x01, 0x03, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00 - ]) + return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) @property def _body(self): @@ -63,7 +54,7 @@ def __init__(self, body): self.keep_warm_time = body[19] * 60 + body[20] self.top_temperature = body[48] self.bottom_temperature = body[49] - self.with_pressure = (body[33] > 0) + self.with_pressure = body[33] > 0 class MessageECResponse(MessageResponse): @@ -71,10 +62,12 @@ def __init__(self, message): super().__init__(message) if self.message_type == MessageType.notify1 and super().body[3] == 0x01: self.set_body(ECBodyNew(super().body)) - elif (self.message_type == MessageType.set and super().body[3] == 0x02) or \ - (self.message_type == MessageType.query and super().body[3] == 0x03) or \ - (self.message_type == MessageType.notify1 and super().body[3] == 0x04) or \ - (self.message_type == MessageType.notify1 and super().body[3] == 0x3d): + elif ( + (self.message_type == MessageType.set and super().body[3] == 0x02) + or (self.message_type == MessageType.query and super().body[3] == 0x03) + or (self.message_type == MessageType.notify1 and super().body[3] == 0x04) + or (self.message_type == MessageType.notify1 and super().body[3] == 0x3D) + ): self.set_body(ECGeneralMessageBody(super().body)) elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: self.mode = super().body[4] + (super().body[5] << 8) diff --git a/custom_components/midea_ac_lan/midea/devices/ed/device.py b/custom_components/midea_ac_lan/midea/devices/ed/device.py index 0af734b1..414be310 100644 --- a/custom_components/midea_ac_lan/midea/devices/ed/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ed/device.py @@ -1,14 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageEDResponse, - MessageNewSet, - MessageOldSet -) + +from .message import MessageEDResponse, MessageNewSet, MessageOldSet, MessageQuery + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -31,17 +29,17 @@ class DeviceAttributes(StrEnum): class MideaEDDevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -65,17 +63,16 @@ def __init__( DeviceAttributes.life1: None, DeviceAttributes.life2: None, DeviceAttributes.life3: None, - DeviceAttributes.child_lock: False - }) + DeviceAttributes.child_lock: False, + }, + ) self._device_class = 0 def _use_new_set(self): - return True # if (self.sub_type > 342 or self.sub_type == 340) else False + return True # if (self.sub_type > 342 or self.sub_type == 340) else False def build_query(self): - return [ - MessageQuery(self._protocol_version, self._device_class) - ] + return [MessageQuery(self._protocol_version, self._device_class)] def process_message(self, msg): message = MessageEDResponse(msg) @@ -92,10 +89,7 @@ def process_message(self, msg): def set_attribute(self, attr, value): message = None if self._use_new_set(): - if attr in [ - DeviceAttributes.power, - DeviceAttributes.child_lock - ]: + if attr in [DeviceAttributes.power, DeviceAttributes.child_lock]: message = MessageNewSet(self._protocol_version) else: if attr in []: diff --git a/custom_components/midea_ac_lan/midea/devices/ed/message.py b/custom_components/midea_ac_lan/midea/devices/ed/message.py index 1f6accee..3425414d 100644 --- a/custom_components/midea_ac_lan/midea/devices/ed/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ed/message.py @@ -1,10 +1,6 @@ from enum import IntEnum -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) + +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class NewSetTags(IntEnum): @@ -15,7 +11,9 @@ class NewSetTags(IntEnum): class EDNewSetParamPack: @staticmethod def pack(param, value, addition=0): - return bytearray([param & 0xFF, param >> 8, value, addition & 0xFF, addition >> 8]) + return bytearray( + [param & 0xFF, param >> 8, value, addition & 0xFF, addition >> 8] + ) class MessageEDBase(MessageRequest): @@ -24,7 +22,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xED, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -37,7 +35,8 @@ def __init__(self, protocol_version, device_class): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=device_class) + body_type=device_class, + ) @property def _body(self): @@ -49,7 +48,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x15) + body_type=0x15, + ) self.power = None self.lock = None @@ -61,16 +61,14 @@ def _body(self): pack_count += 1 payload.extend( EDNewSetParamPack.pack( - param=NewSetTags.power, # power - value=0x01 if self.power else 0x00 + param=NewSetTags.power, value=0x01 if self.power else 0x00 # power ) ) if self.lock is not None: pack_count += 1 payload.extend( EDNewSetParamPack.pack( - param=NewSetTags.lock, # lock - value=0x01 if self.lock else 0x00 + param=NewSetTags.lock, value=0x01 if self.lock else 0x00 # lock ) ) payload[1] = pack_count @@ -82,7 +80,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=None) + body_type=None, + ) @property def body(self): @@ -157,10 +156,17 @@ def __init__(self, body): self.child_lock = (body[data_offset + 5] & 0x01) > 0 self.power = (body[data_offset + 6] & 0x01) > 0 elif attr == 0x011: - self.water_consumption = float((body[data_offset + 3] + - (body[data_offset + 4] << 8) + - (body[data_offset + 5] << 16) + - (body[data_offset + 6] << 24))) / 1000 + self.water_consumption = ( + float( + ( + body[data_offset + 3] + + (body[data_offset + 4] << 8) + + (body[data_offset + 5] << 16) + + (body[data_offset + 6] << 24) + ) + ) + / 1000 + ) elif attr == 0x013: self.in_tds = body[data_offset + 3] + (body[data_offset + 4] << 8) self.out_tds = body[data_offset + 5] + (body[data_offset + 6] << 8) diff --git a/custom_components/midea_ac_lan/midea/devices/fa/device.py b/custom_components/midea_ac_lan/midea/devices/fa/device.py index 5462d689..ee2e3f74 100644 --- a/custom_components/midea_ac_lan/midea/devices/fa/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fa/device.py @@ -1,14 +1,13 @@ -import logging import json -from .message import ( - MessageQuery, - MessageFAResponse, - MessageSet -) +import logging + +from .message import MessageFAResponse, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -26,32 +25,45 @@ class DeviceAttributes(StrEnum): class MideaFADevice(MiedaDevice): - _oscillation_angles = [ - "Off", "30", "60", "90", "120", "180", "360" - ] - _tilting_angles = [ - "Off", "30", "60", "90", "120", "180", "360", "+60", "-60", "40" - ] + _oscillation_angles = ["Off", "30", "60", "90", "120", "180", "360"] + _tilting_angles = ["Off", "30", "60", "90", "120", "180", "360", "+60", "-60", "40"] _oscillation_modes = [ - "Off", "Oscillation", "Tilting", "Curve-W", "Curve-8", "Reserved", "Both" + "Off", + "Oscillation", + "Tilting", + "Curve-W", + "Curve-8", + "Reserved", + "Both", ] _modes = [ - "Normal", "Natural", "Sleep", "Comfort", "Silent", "Baby", - "Induction", "Circulation", "Strong", "Soft", "Customize", "Warm", "Smart" + "Normal", + "Natural", + "Sleep", + "Comfort", + "Silent", + "Baby", + "Induction", + "Circulation", + "Strong", + "Soft", + "Customize", + "Warm", + "Smart", ] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -73,7 +85,8 @@ def __init__( DeviceAttributes.oscillation_angle: None, DeviceAttributes.tilting_angle: None, DeviceAttributes.oscillation_mode: None, - }) + }, + ) self._default_speed_count = 3 self._speed_count = self._default_speed_count self.set_customize(customize) @@ -110,7 +123,9 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.oscillation_angle: if value < len(MideaFADevice._oscillation_angles): - self._attributes[status] = MideaFADevice._oscillation_angles[value] + self._attributes[status] = MideaFADevice._oscillation_angles[ + value + ] else: self._attributes[status] = None elif status == DeviceAttributes.tilting_angle: @@ -120,7 +135,9 @@ def process_message(self, msg): self._attributes[status] = None elif status == DeviceAttributes.oscillation_mode: if value < len(MideaFADevice._oscillation_modes): - self._attributes[status] = MideaFADevice._oscillation_modes[value] + self._attributes[status] = MideaFADevice._oscillation_modes[ + value + ] else: self._attributes[status] = None elif status == DeviceAttributes.mode: @@ -132,7 +149,10 @@ def process_message(self, msg): self._attributes[status] = value if not value: self._attributes[DeviceAttributes.fan_speed] = 0 - elif status == DeviceAttributes.fan_speed and not self._attributes[DeviceAttributes.power]: + elif ( + status == DeviceAttributes.fan_speed + and not self._attributes[DeviceAttributes.power] + ): self._attributes[status] = 0 else: self._attributes[status] = value @@ -148,20 +168,28 @@ def set_oscillation(self, attr, value): if value: message.oscillation_angle = 3 # 90 message.oscillation_mode = 1 # Oscillation - elif attr == DeviceAttributes.oscillation_mode and \ - (value in MideaFADevice._oscillation_modes or not value): + elif attr == DeviceAttributes.oscillation_mode and ( + value in MideaFADevice._oscillation_modes or not value + ): message = MessageSet(self._protocol_version, self.subtype) if value == "Off" or not value: message.oscillate = False else: message.oscillate = True - message.oscillation_mode = MideaFADevice._oscillation_modes.index(value) + message.oscillation_mode = MideaFADevice._oscillation_modes.index( + value + ) if value == "Oscillation": - if self._attributes[DeviceAttributes.oscillation_angle] == "Off": + if ( + self._attributes[DeviceAttributes.oscillation_angle] + == "Off" + ): message.oscillation_angle = 3 # 90 else: - message.oscillation_angle = MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] + message.oscillation_angle = ( + MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] + ) ) elif value == "Tilting": if self._attributes[DeviceAttributes.tilting_angle] == "Off": @@ -171,11 +199,16 @@ def set_oscillation(self, attr, value): self._attributes[DeviceAttributes.tilting_angle] ) else: - if self._attributes[DeviceAttributes.oscillation_angle] == "Off": + if ( + self._attributes[DeviceAttributes.oscillation_angle] + == "Off" + ): message.oscillation_angle = 3 # 90 else: - message.oscillation_angle = MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] + message.oscillation_angle = ( + MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] + ) ) if self._attributes[DeviceAttributes.tilting_angle] == "Off": message.tilting_angle = 3 # 90 @@ -183,8 +216,9 @@ def set_oscillation(self, attr, value): message.tilting_angle = MideaFADevice._tilting_angles.index( self._attributes[DeviceAttributes.tilting_angle] ) - elif attr == DeviceAttributes.oscillation_angle and \ - (value in MideaFADevice._oscillation_angles or not value): + elif attr == DeviceAttributes.oscillation_angle and ( + value in MideaFADevice._oscillation_angles or not value + ): message = MessageSet(self._protocol_version, self.subtype) if value == "Off" or not value: if self._attributes[DeviceAttributes.tilting_angle] == "Off": @@ -196,17 +230,22 @@ def set_oscillation(self, attr, value): self._attributes[DeviceAttributes.tilting_angle] ) else: - message.oscillation_angle = MideaFADevice._oscillation_angles.index(value) + message.oscillation_angle = MideaFADevice._oscillation_angles.index( + value + ) message.oscillate = True if self._attributes[DeviceAttributes.tilting_angle] == "Off": message.oscillation_mode = 1 - elif self._attributes[DeviceAttributes.oscillation_mode] == "Tilting": + elif ( + self._attributes[DeviceAttributes.oscillation_mode] == "Tilting" + ): message.oscillation_mode = 6 message.tilting_angle = MideaFADevice._tilting_angles.index( self._attributes[DeviceAttributes.tilting_angle] ) - elif attr == DeviceAttributes.tilting_angle and \ - (value in MideaFADevice._tilting_angles or not value): + elif attr == DeviceAttributes.tilting_angle and ( + value in MideaFADevice._tilting_angles or not value + ): message = MessageSet(self._protocol_version, self.subtype) if value == "Off" or not value: if self._attributes[DeviceAttributes.oscillation_angle] == "Off": @@ -214,18 +253,25 @@ def set_oscillation(self, attr, value): else: message.oscillate = True message.oscillation_mode = 1 - message.oscillation_angle = MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] + message.oscillation_angle = ( + MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] + ) ) else: message.tilting_angle = MideaFADevice._tilting_angles.index(value) message.oscillate = True if self._attributes[DeviceAttributes.oscillation_angle] == "Off": message.oscillation_mode = 2 - elif self._attributes[DeviceAttributes.oscillation_mode] == "Oscillation": + elif ( + self._attributes[DeviceAttributes.oscillation_mode] + == "Oscillation" + ): message.oscillation_mode = 6 - message.oscillation_angle = MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] + message.oscillation_angle = ( + MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] + ) ) return message @@ -235,11 +281,14 @@ def set_attribute(self, attr, value): DeviceAttributes.oscillate, DeviceAttributes.oscillation_mode, DeviceAttributes.oscillation_angle, - DeviceAttributes.tilting_angle + DeviceAttributes.tilting_angle, ]: message = self.set_oscillation(attr, value) - elif attr == DeviceAttributes.fan_speed and value > 0 and \ - not self._attributes[DeviceAttributes.power]: + elif ( + attr == DeviceAttributes.fan_speed + and value > 0 + and not self._attributes[DeviceAttributes.power] + ): message = MessageSet(self._protocol_version, self.subtype) message.fan_speed = value message.power = True diff --git a/custom_components/midea_ac_lan/midea/devices/fa/message.py b/custom_components/midea_ac_lan/midea/devices/fa/message.py index 16cd2e6f..69381737 100644 --- a/custom_components/midea_ac_lan/midea/devices/fa/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fa/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageFABase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xFA, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=None) + body_type=None, + ) @property def body(self): @@ -41,7 +37,8 @@ def __init__(self, protocol_version, subtype): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00) + body_type=0x00, + ) self._subtype = subtype self.power = None self.lock = None @@ -55,31 +52,84 @@ def __init__(self, protocol_version, subtype): @property def _body(self): if 1 <= self._subtype <= 10 or self._subtype == 161: - _body_return = bytearray([ - 0x00, 0x00, 0x00, 0x80, - 0x00, 0x00, 0x00, 0x80, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00 - ]) + _body_return = bytearray( + [ + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) if self._subtype != 10: _body_return[13] = 0xFF else: - _body_return = bytearray([ - 0x00, 0x00, 0x00, 0x80, - 0x00, 0x00, 0x00, 0x80, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00 - ]) + _body_return = bytearray( + [ + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) if self.power is not None: if self.power: _body_return[3] = 1 @@ -100,9 +150,13 @@ def _body(self): else: _body_return[7] = 0 if self.oscillation_angle is not None: - _body_return[7] = 1 | _body_return[7] | ((self.oscillation_angle << 4) & 0x70) + _body_return[7] = ( + 1 | _body_return[7] | ((self.oscillation_angle << 4) & 0x70) + ) if self.oscillation_mode is not None: - _body_return[7] = 1 | _body_return[7] | ((self.oscillation_mode << 1) & 0x0E) + _body_return[7] = ( + 1 | _body_return[7] | ((self.oscillation_mode << 1) & 0x0E) + ) if self.tilting_angle is not None and len(_body_return) > 24: _body_return[24] = self.tilting_angle return _body_return @@ -117,7 +171,7 @@ def __init__(self, body): else: self.child_lock = False self.power = (body[4] & 0x01) > 0 - mode = ((body[4] & 0x1E) >> 1) + mode = (body[4] & 0x1E) >> 1 if mode > 0: self.mode = mode - 1 fan_speed = body[5] @@ -134,6 +188,10 @@ def __init__(self, body): class MessageFAResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1]: + if self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify1, + ]: self.set_body(FAGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fb/device.py b/custom_components/midea_ac_lan/midea/devices/fb/device.py index 4ac3cf1e..01e84497 100644 --- a/custom_components/midea_ac_lan/midea/devices/fb/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fb/device.py @@ -1,13 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageFBResponse, - MessageSet -) + +from .message import MessageFBResponse, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -23,22 +22,30 @@ class DeviceAttributes(StrEnum): class MideaFBDevice(MiedaDevice): - _modes = {0x01: "Auto", 0x02: "ECO", 0x03: "Sleep", - 0x04: "Anti-freezing", 0x05: "Comfort", 0x06: "Constant-temperature", - 0x07: "Normal", 0x08: "Fast-heating", 0x10: "Standby"} + _modes = { + 0x01: "Auto", + 0x02: "ECO", + 0x03: "Sleep", + 0x04: "Anti-freezing", + 0x05: "Comfort", + 0x06: "Constant-temperature", + 0x07: "Normal", + 0x08: "Fast-heating", + 0x10: "Standby", + } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -58,7 +65,8 @@ def __init__( DeviceAttributes.target_temperature: None, DeviceAttributes.current_temperature: None, DeviceAttributes.child_lock: False, - }) + }, + ) @property def modes(self): diff --git a/custom_components/midea_ac_lan/midea/devices/fb/message.py b/custom_components/midea_ac_lan/midea/devices/fb/message.py index e6a75c6a..9aea4bdd 100644 --- a/custom_components/midea_ac_lan/midea/devices/fb/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fb/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageFBBase(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xFB, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=None) + body_type=None, + ) @property def body(self): @@ -41,7 +37,8 @@ def __init__(self, protocol_version, subtype): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00) + body_type=0x00, + ) self._subtype = subtype self.power = None self.mode = None @@ -53,24 +50,52 @@ def __init__(self, protocol_version, subtype): def body(self): power = 0 if self.power is None else (0x01 if self.power else 0x02) mode = 0 if self.mode is None else self.mode - heating_level = 0 if self.heating_level is None else \ - (int(self.heating_level if 1 <= self.heating_level <= 10 else 0) & 0xFF) - target_temperature = 0 if self.target_temperature is None else \ - (int((self.target_temperature + 41) if -40 <= self.target_temperature <= 50 else - (0x80 if self.target_temperature in [0x80, 87] else 0)) & 0xFF) - child_lock = 0xFF if self.child_lock is None else (0x01 if self.child_lock else 0x00) - _return_body = bytearray([ - power, - 0x00, 0x00, 0x00, - mode, - heating_level, - target_temperature, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, - child_lock, - 0x00 - ]) + heating_level = ( + 0 + if self.heating_level is None + else ( + int(self.heating_level if 1 <= self.heating_level <= 10 else 0) & 0xFF + ) + ) + target_temperature = ( + 0 + if self.target_temperature is None + else ( + int( + (self.target_temperature + 41) + if -40 <= self.target_temperature <= 50 + else (0x80 if self.target_temperature in [0x80, 87] else 0) + ) + & 0xFF + ) + ) + child_lock = ( + 0xFF if self.child_lock is None else (0x01 if self.child_lock else 0x00) + ) + _return_body = bytearray( + [ + power, + 0x00, + 0x00, + 0x00, + mode, + heating_level, + target_temperature, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + child_lock, + 0x00, + ] + ) if self._subtype > 5: _return_body += bytearray([0x00, 0x00, 0x00]) return _return_body @@ -100,6 +125,10 @@ def __init__(self, body): class MessageFBResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1]: + if self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify1, + ]: self.set_body(FBGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fc/device.py b/custom_components/midea_ac_lan/midea/devices/fc/device.py index 8cdf3b09..94e19f76 100644 --- a/custom_components/midea_ac_lan/midea/devices/fc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fc/device.py @@ -1,14 +1,13 @@ -import logging import json -from .message import ( - MessageQuery, - MessageFCResponse, - MessageSet -) +import logging + +from .message import MessageFCResponse, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -33,28 +32,29 @@ class DeviceAttributes(StrEnum): class MideaFCDevice(MiedaDevice): _modes = { - 0x00: "Standby", 0x10: "Auto", 0x20: "Manual", 0x30: "Sleep", 0x40: "Fast", 0x50: "Smoke" - } - _speeds = { - 1: "Auto", 4: "Standby", 39: "Low", 59: "Medium", 80: "High" - } - _screen_displays = { - 0: "Bright", 6: "Dim", 7: "Off" + 0x00: "Standby", + 0x10: "Auto", + 0x20: "Manual", + 0x30: "Sleep", + 0x40: "Fast", + 0x50: "Smoke", } + _speeds = {1: "Auto", 4: "Standby", 39: "Low", 59: "Medium", 80: "High"} + _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} _detect_modes = ["Off", "PM 2.5", "Methanal"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -82,7 +82,8 @@ def __init__( DeviceAttributes.prompt_tone: True, DeviceAttributes.filter1_life: None, DeviceAttributes.filter2_life: None, - }) + }, + ) self._standby_detect_default = [40, 20] self._standby_detect = self._standby_detect_default @@ -126,7 +127,9 @@ def process_message(self, msg): self._attributes[status] = None elif status == DeviceAttributes.screen_display: if value in MideaFCDevice._screen_displays.keys(): - self._attributes[status] = MideaFCDevice._screen_displays.get(value) + self._attributes[status] = MideaFCDevice._screen_displays.get( + value + ) else: self._attributes[status] = None elif status == DeviceAttributes.detect_mode: @@ -148,20 +151,40 @@ def make_message_set(self): message.anion = self._attributes[DeviceAttributes.anion] message.standby = self._attributes[DeviceAttributes.standby] message.screen_display = self._attributes[DeviceAttributes.screen_display] - message.detect_mode = 0 if self._attributes[DeviceAttributes.detect_mode] is None else \ - MideaFCDevice._detect_modes.index(self._attributes[DeviceAttributes.detect_mode]) - message.mode = 0x10 if self._attributes[DeviceAttributes.mode] is None else \ - list(MideaFCDevice._modes.keys())[list(MideaFCDevice._modes.values()).index( - self._attributes[DeviceAttributes.mode] - )] - message.fan_speed = 39 if self._attributes[DeviceAttributes.fan_speed] is None else \ - list(MideaFCDevice._speeds.keys())[list(MideaFCDevice._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - )] - message.screen_display = 0 if self._attributes[DeviceAttributes.screen_display] is None else \ - list(MideaFCDevice._screen_displays.keys())[list(MideaFCDevice._screen_displays.values()).index( - self._attributes[DeviceAttributes.screen_display] - )] + message.detect_mode = ( + 0 + if self._attributes[DeviceAttributes.detect_mode] is None + else MideaFCDevice._detect_modes.index( + self._attributes[DeviceAttributes.detect_mode] + ) + ) + message.mode = ( + 0x10 + if self._attributes[DeviceAttributes.mode] is None + else list(MideaFCDevice._modes.keys())[ + list(MideaFCDevice._modes.values()).index( + self._attributes[DeviceAttributes.mode] + ) + ] + ) + message.fan_speed = ( + 39 + if self._attributes[DeviceAttributes.fan_speed] is None + else list(MideaFCDevice._speeds.keys())[ + list(MideaFCDevice._speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + ) + ] + ) + message.screen_display = ( + 0 + if self._attributes[DeviceAttributes.screen_display] is None + else list(MideaFCDevice._screen_displays.keys())[ + list(MideaFCDevice._screen_displays.values()).index( + self._attributes[DeviceAttributes.screen_display] + ) + ] + ) message.standby_detect = self._standby_detect return message @@ -183,9 +206,9 @@ def set_attribute(self, attr, value): ] elif attr == DeviceAttributes.screen_display: if value in MideaFCDevice._screen_displays.values(): - message.screen_display = list(MideaFCDevice._screen_displays.keys())[ - list(MideaFCDevice._screen_displays.values()).index(value) - ] + message.screen_display = list( + MideaFCDevice._screen_displays.keys() + )[list(MideaFCDevice._screen_displays.values()).index(value)] elif not value: message.screen_display = 7 elif attr == DeviceAttributes.detect_mode: diff --git a/custom_components/midea_ac_lan/midea/devices/fc/message.py b/custom_components/midea_ac_lan/midea/devices/fc/message.py index d124b880..00d0bce0 100644 --- a/custom_components/midea_ac_lan/midea/devices/fc/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fc/message.py @@ -1,10 +1,5 @@ from ...core.crc8 import calculate -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageFCBase(MessageRequest): @@ -15,7 +10,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xFC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) MessageFCBase._message_serial += 1 if MessageFCBase._message_serial >= 254: @@ -38,17 +33,34 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41) + body_type=0x41, + ) @property def _body(self): - return bytearray([ - 0x00, 0x00, 0xFF, 0x03, - 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00 - ]) + return bytearray( + [ + 0x00, + 0x00, + 0xFF, + 0x03, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class MessageSet(MessageFCBase): @@ -56,7 +68,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x48) + body_type=0x48, + ) self.power = False self.mode = 0 self.fan_speed = 0 @@ -91,16 +104,30 @@ def _body(self): standby = 0x08 standby_detect_high = 0 standby_detect_low = 0 - return bytearray([ - power | prompt_tone | detect | 0x02, - self.mode, - self.fan_speed, - 0x00, 0x00, 0x00, 0x00, - child_lock, self.screen_display, anion, - 0x00, 0x00, 0x00, detect_mode, - standby, standby_detect_high, standby_detect_low, - 0x00, 0x00, 0x00, - ]) + return bytearray( + [ + power | prompt_tone | detect | 0x02, + self.mode, + self.fan_speed, + 0x00, + 0x00, + 0x00, + 0x00, + child_lock, + self.screen_display, + anion, + 0x00, + 0x00, + 0x00, + detect_mode, + standby, + standby_detect_high, + standby_detect_low, + 0x00, + 0x00, + 0x00, + ] + ) class FCGeneralMessageBody(MessageBody): @@ -171,8 +198,11 @@ def __init__(self, message): if self.body_type in [0xB0, 0xB1]: pass else: - if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1] and \ - self.body_type == 0xC8: + if ( + self.message_type + in [MessageType.query, MessageType.set, MessageType.notify1] + and self.body_type == 0xC8 + ): self.set_body(FCGeneralMessageBody(super().body)) elif self.message_type == MessageType.notify1 and self.body_type == 0xA0: self.set_body(FCNotifyMessageBody(super().body)) diff --git a/custom_components/midea_ac_lan/midea/devices/fd/device.py b/custom_components/midea_ac_lan/midea/devices/fd/device.py index 9db35daa..323b45b2 100644 --- a/custom_components/midea_ac_lan/midea/devices/fd/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fd/device.py @@ -1,13 +1,12 @@ import logging -from .message import ( - MessageQuery, - MessageFDResponse, - MessageSet -) + +from .message import MessageFDResponse, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -28,31 +27,45 @@ class DeviceAttributes(StrEnum): class MideaFDDevice(MiedaDevice): _modes = [ - "Manual", "Auto", "Continuous", "Living-Room", "Bed-Room", "Kitchen", "Sleep" + "Manual", + "Auto", + "Continuous", + "Living-Room", + "Bed-Room", + "Kitchen", + "Sleep", ] _speeds_old = { - 1: "Lowest", 40: "Low", 60: "Medium", 80: "High", 102: "Auto", 127: "Off" + 1: "Lowest", + 40: "Low", + 60: "Medium", + 80: "High", + 102: "Auto", + 127: "Off", } _speeds_new = { - 1: "Lowest", 39: "Low", 59: "Medium", 80: "High", 101: "Auto", 127: "Off" - } - _screen_displays = { - 0: "Bright", 6: "Dim", 7: "Off" + 1: "Lowest", + 39: "Low", + 59: "Medium", + 80: "High", + 101: "Auto", + 127: "Off", } + _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} _detect_modes = ["Off", "PM 2.5", "Methanal"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -76,7 +89,8 @@ def __init__( DeviceAttributes.mode: None, DeviceAttributes.screen_display: None, DeviceAttributes.disinfect: None, - }) + }, + ) if self.subtype > 5: self._speeds = MideaFDDevice._speeds_new else: @@ -121,7 +135,9 @@ def process_message(self, msg): self._attributes[status] = None elif status == DeviceAttributes.screen_display: if value in MideaFDDevice._screen_displays.keys(): - self._attributes[status] = MideaFDDevice._screen_displays.get(value) + self._attributes[status] = MideaFDDevice._screen_displays.get( + value + ) else: self._attributes[status] = None else: @@ -136,17 +152,29 @@ def make_message_set(self): message.screen_display = self._attributes[DeviceAttributes.screen_display] message.disinfect = self._attributes[DeviceAttributes.disinfect] if self._attributes[DeviceAttributes.mode] in MideaFDDevice._modes: - message.mode = MideaFDDevice._modes.index(self._attributes[DeviceAttributes.mode]) + 1 + message.mode = ( + MideaFDDevice._modes.index(self._attributes[DeviceAttributes.mode]) + 1 + ) else: message.mode = 1 - message.fan_speed = 40 if self._attributes[DeviceAttributes.fan_speed] is None else \ - list(self._speeds.keys())[list(self._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - )] - message.screen_display = 0 if self._attributes[DeviceAttributes.screen_display] is None else \ - list(MideaFDDevice._screen_displays.keys())[list(MideaFDDevice._screen_displays.values()).index( - self._attributes[DeviceAttributes.screen_display] - )] + message.fan_speed = ( + 40 + if self._attributes[DeviceAttributes.fan_speed] is None + else list(self._speeds.keys())[ + list(self._speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + ) + ] + ) + message.screen_display = ( + 0 + if self._attributes[DeviceAttributes.screen_display] is None + else list(MideaFDDevice._screen_displays.keys())[ + list(MideaFDDevice._screen_displays.values()).index( + self._attributes[DeviceAttributes.screen_display] + ) + ] + ) return message def set_attribute(self, attr, value): @@ -165,9 +193,9 @@ def set_attribute(self, attr, value): ] elif attr == DeviceAttributes.screen_display: if value in MideaFDDevice._screen_displays.values(): - message.screen_display = list(MideaFDDevice._screen_displays.keys())[ - list(MideaFDDevice._screen_displays.values()).index(value) - ] + message.screen_display = list( + MideaFDDevice._screen_displays.keys() + )[list(MideaFDDevice._screen_displays.values()).index(value)] elif not value: message.screen_display = 7 else: diff --git a/custom_components/midea_ac_lan/midea/devices/fd/message.py b/custom_components/midea_ac_lan/midea/devices/fd/message.py index c5fcfa55..68310684 100644 --- a/custom_components/midea_ac_lan/midea/devices/fd/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fd/message.py @@ -1,10 +1,5 @@ from ...core.crc8 import calculate -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class MessageFDBase(MessageRequest): @@ -15,7 +10,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xFD, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) MessageFDBase._message_serial += 1 if MessageFDBase._message_serial >= 254: @@ -38,17 +33,34 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41) + body_type=0x41, + ) @property def _body(self): - return bytearray([ - 0x81, 0x00, 0xFF, 0x03, - 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00 - ]) + return bytearray( + [ + 0x81, + 0x00, + 0xFF, + 0x03, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class MessageSet(MessageFDBase): @@ -56,7 +68,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x48) + body_type=0x48, + ) self.power = False self.fan_speed = 0 self.target_humidity = 50 @@ -70,20 +83,31 @@ def _body(self): power = 0x01 if self.power else 0x00 prompt_tone = 0x40 if self.prompt_tone else 0x00 disinfect = 0 if self.disinfect is None else (1 if self.disinfect else 2) - return bytearray([ - power | prompt_tone | 0x02, - 0x00, - self.fan_speed, - 0x00, 0x00, 0x00, - self.target_humidity, - 0x00, - self.screen_display, - self.mode, - 0x00, 0x00, 0x00, 0x00, - disinfect, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00 - ]) + return bytearray( + [ + power | prompt_tone | 0x02, + 0x00, + self.fan_speed, + 0x00, + 0x00, + 0x00, + self.target_humidity, + 0x00, + self.screen_display, + self.mode, + 0x00, + 0x00, + 0x00, + 0x00, + disinfect, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) class FDC8MessageBody(MessageBody): @@ -127,7 +151,11 @@ def __init__(self, body): class MessageFDResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1]: + if self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify1, + ]: if self.body_type in [0xB0, 0xB1]: pass elif self.body_type == 0xA0: @@ -135,5 +163,9 @@ def __init__(self, message): elif self.body_type == 0xC8: self.set_body(FDC8MessageBody(super().body)) self.set_attr() - if hasattr(self, "fan_speed") and self.fan_speed is not None and self.fan_speed < 5: + if ( + hasattr(self, "fan_speed") + and self.fan_speed is not None + and self.fan_speed < 5 + ): self.fan_speed = 1 diff --git a/custom_components/midea_ac_lan/midea/devices/x13/device.py b/custom_components/midea_ac_lan/midea/devices/x13/device.py index 049c6afe..ca8ad4b0 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/device.py @@ -1,14 +1,13 @@ -import logging import json -from .message import ( - MessageQuery, - MessageSet, - Message13Response -) +import logging + +from .message import Message13Response, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -26,17 +25,17 @@ class Midea13Device(MiedaDevice): _effects = ["Manual", "Living", "Reading", "Mildly", "Cinema", "Night"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -54,8 +53,9 @@ def __init__( DeviceAttributes.color_temperature: None, DeviceAttributes.rgb_color: None, DeviceAttributes.effect: None, - DeviceAttributes.power: False - }) + DeviceAttributes.power: False, + }, + ) self._color_temp_range = None self._default_color_temp_range = [2700, 6500] self.set_customize(customize) @@ -69,11 +69,17 @@ def color_temp_range(self): return self._color_temp_range def kelvin_to_midea(self, kelvin): - return round((kelvin - self._color_temp_range[0]) / (self._color_temp_range[1] - self._color_temp_range[0]) * 255) + return round( + (kelvin - self._color_temp_range[0]) + / (self._color_temp_range[1] - self._color_temp_range[0]) + * 255 + ) def midea_to_kelvin(self, midea): - return round((self._color_temp_range[1] - self._color_temp_range[0]) / 255 * midea) + \ - self._color_temp_range[0] + return ( + round((self._color_temp_range[1] - self._color_temp_range[0]) / 255 * midea) + + self._color_temp_range[0] + ) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -100,10 +106,12 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [DeviceAttributes.brightness, - DeviceAttributes.color_temperature, - DeviceAttributes.effect, - DeviceAttributes.power]: + if attr in [ + DeviceAttributes.brightness, + DeviceAttributes.color_temperature, + DeviceAttributes.effect, + DeviceAttributes.power, + ]: message = MessageSet(self._protocol_version) if attr == DeviceAttributes.effect and value in self._effects: setattr(message, str(attr), Midea13Device._effects.index(value)) diff --git a/custom_components/midea_ac_lan/midea/devices/x13/message.py b/custom_components/midea_ac_lan/midea/devices/x13/message.py index ce8e3699..fb208a11 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class Message13Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0x13, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,13 +20,12 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x24) + body_type=0x24, + ) @property def _body(self): - return bytearray([ - 0x00, 0x00, 0x00, 0x00 - ]) + return bytearray([0x00, 0x00, 0x00, 0x00]) class MessageSet(Message13Base): @@ -39,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00) + body_type=0x00, + ) self.brightness = None self.color_temperature = None self.effect = None @@ -71,11 +66,11 @@ def __init__(self, body): self.effect = self.read_byte(body, 3) - 1 if self.effect > 5: self.effect = 1 - ''' + """ self.rgb_color = [self.read_byte(body, 5), self.read_byte(body, 6), self.read_byte(body, 7)] - ''' + """ self.power = self.read_byte(body, 8) > 0 @@ -88,7 +83,7 @@ def __init__(self, body): class Message13Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.body_type == 0xa4: + if self.body_type == 0xA4: self.set_body(MessageMainLightBody(super().body)) elif self.message_type == MessageType.set and self.body_type > 0x80: self.set_body(MessageMainLightResponseBody(super().body)) diff --git a/custom_components/midea_ac_lan/midea/devices/x26/device.py b/custom_components/midea_ac_lan/midea/devices/x26/device.py index 5a4408f3..1a44c5e6 100644 --- a/custom_components/midea_ac_lan/midea/devices/x26/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x26/device.py @@ -1,14 +1,13 @@ import logging import math -from .message import ( - MessageQuery, - MessageSet, - Message26Response -) + +from .message import Message26Response, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -29,17 +28,17 @@ class Midea26Device(MiedaDevice): _directions = ["60", "70", "80", "90", "100", "110", "120", "Oscillate"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -59,8 +58,9 @@ def __init__( DeviceAttributes.direction: None, DeviceAttributes.current_humidity: None, DeviceAttributes.current_radar: None, - DeviceAttributes.current_temperature: None - }) + DeviceAttributes.current_temperature: None, + }, + ) self._fields = {} @staticmethod @@ -68,8 +68,11 @@ def _convert_to_midea_direction(direction): if direction == "Oscillate": result = 0xFD else: - result = Midea26Device._directions.index(direction) * 10 + 60 \ - if direction in Midea26Device._directions else 0xFD + result = ( + Midea26Device._directions.index(direction) * 10 + 60 + if direction in Midea26Device._directions + else 0xFD + ) return result @staticmethod @@ -111,21 +114,23 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [DeviceAttributes.main_light, - DeviceAttributes.night_light, - DeviceAttributes.mode, - DeviceAttributes.direction - ]: + if attr in [ + DeviceAttributes.main_light, + DeviceAttributes.night_light, + DeviceAttributes.mode, + DeviceAttributes.direction, + ]: message = MessageSet(self._protocol_version) message.fields = self._fields message.main_light = self._attributes[DeviceAttributes.main_light] message.night_light = self._attributes[DeviceAttributes.night_light] - message.mode = Midea26Device._modes.index(self._attributes[DeviceAttributes.mode]) - message.direction = self._convert_to_midea_direction(self._attributes[DeviceAttributes.direction]) - if attr in [ - DeviceAttributes.main_light, - DeviceAttributes.night_light - ]: + message.mode = Midea26Device._modes.index( + self._attributes[DeviceAttributes.mode] + ) + message.direction = self._convert_to_midea_direction( + self._attributes[DeviceAttributes.direction] + ) + if attr in [DeviceAttributes.main_light, DeviceAttributes.night_light]: message.main_light = False message.night_light = False setattr(message, str(attr), value) diff --git a/custom_components/midea_ac_lan/midea/devices/x26/message.py b/custom_components/midea_ac_lan/midea/devices/x26/message.py index f0afd96c..51026d3a 100644 --- a/custom_components/midea_ac_lan/midea/devices/x26/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x26/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class Message26Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0x26, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,12 +20,12 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): - return bytearray([ - ]) + return bytearray([]) class MessageSet(Message26Base): @@ -38,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01) + body_type=0x01, + ) self.fields = {} self.main_light = False self.night_light = False @@ -51,47 +47,53 @@ def read_field(self, field): @property def _body(self): - return bytearray([ - 1 if self.main_light else 0, - self.read_field("MAIN_LIGHT_BRIGHTNESS"), - 1 if self.night_light else 0, - self.read_field("NIGHT_LIGHT_BRIGHTNESS"), - self.read_field("RADAR_INDUCTION_ENABLE"), - self.read_field("RADAR_INDUCTION_CLOSING_TIME"), - self.read_field("LIGHT_INTENSITY_THRESHOLD"), - self.read_field("RADAR_SENSITIVITY"), - 1 if self.mode == 1 or self.mode == 2 else 0, - 0 if not (self.mode == 1 or self.mode == 2) else 55 if self.mode == 1 else 30, - self.read_field("HEATING_SPEED"), - self.direction, - 1 if self.mode == 3 else 0, - self.read_field("BATH_HEATING_TIME"), - self.read_field("BATH_TEMPERATURE"), - self.read_field("BATH_SPEED"), - self.direction, - 1 if self.mode == 5 else 0, - self.read_field("VENTILATION_SPEED"), - self.direction, - 1 if self.mode == 6 else 0, - self.read_field("DRYING_TIME"), - self.read_field("DRYING_TEMPERATURE"), - self.read_field("DRYING_SPEED"), - self.direction, - 1 if self.mode == 4 else 0, - self.read_field("BLOWING_SPEED"), - self.direction, - self.read_field("DELAY_ENABLE"), - self.read_field("DELAY_TIME"), - self.read_field("SOFT_WIND_ENABLE"), - self.read_field("SOFT_WIND_TIME"), - self.read_field("SOFT_WIND_TEMPERATURE"), - self.read_field("SOFT_WIND_SPEED"), - self.read_field("SOFT_WIND_DIRECTION"), - self.read_field("WINDLESS_ENABLE"), - self.read_field("ANION_ENABLE"), - self.read_field("SMELLY_ENABLE"), - self.read_field("SMELLY_THRESHOLD") - ]) + return bytearray( + [ + 1 if self.main_light else 0, + self.read_field("MAIN_LIGHT_BRIGHTNESS"), + 1 if self.night_light else 0, + self.read_field("NIGHT_LIGHT_BRIGHTNESS"), + self.read_field("RADAR_INDUCTION_ENABLE"), + self.read_field("RADAR_INDUCTION_CLOSING_TIME"), + self.read_field("LIGHT_INTENSITY_THRESHOLD"), + self.read_field("RADAR_SENSITIVITY"), + 1 if self.mode == 1 or self.mode == 2 else 0, + ( + 0 + if not (self.mode == 1 or self.mode == 2) + else 55 if self.mode == 1 else 30 + ), + self.read_field("HEATING_SPEED"), + self.direction, + 1 if self.mode == 3 else 0, + self.read_field("BATH_HEATING_TIME"), + self.read_field("BATH_TEMPERATURE"), + self.read_field("BATH_SPEED"), + self.direction, + 1 if self.mode == 5 else 0, + self.read_field("VENTILATION_SPEED"), + self.direction, + 1 if self.mode == 6 else 0, + self.read_field("DRYING_TIME"), + self.read_field("DRYING_TEMPERATURE"), + self.read_field("DRYING_SPEED"), + self.direction, + 1 if self.mode == 4 else 0, + self.read_field("BLOWING_SPEED"), + self.direction, + self.read_field("DELAY_ENABLE"), + self.read_field("DELAY_TIME"), + self.read_field("SOFT_WIND_ENABLE"), + self.read_field("SOFT_WIND_TIME"), + self.read_field("SOFT_WIND_TEMPERATURE"), + self.read_field("SOFT_WIND_SPEED"), + self.read_field("SOFT_WIND_DIRECTION"), + self.read_field("WINDLESS_ENABLE"), + self.read_field("ANION_ENABLE"), + self.read_field("SMELLY_ENABLE"), + self.read_field("SMELLY_THRESHOLD"), + ] + ) class Message26Body(MessageBody): @@ -168,6 +170,10 @@ def __init__(self, body): class Message26Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: + if ( + self.message_type + in [MessageType.set, MessageType.notify1, MessageType.query] + and self.body_type == 0x01 + ): self.set_body(Message26Body(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/x34/device.py b/custom_components/midea_ac_lan/midea/devices/x34/device.py index 6097128e..6e6d8a03 100644 --- a/custom_components/midea_ac_lan/midea/devices/x34/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x34/device.py @@ -1,15 +1,18 @@ import logging + from .message import ( - MessageQuery, + Message34Response, + MessageLock, MessagePower, + MessageQuery, MessageStorage, - MessageLock, - Message34Response ) + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -44,17 +47,17 @@ class DeviceAttributes(StrEnum): class Midea34Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -91,31 +94,32 @@ def __init__( DeviceAttributes.error_code: None, DeviceAttributes.softwater: 0, DeviceAttributes.wrong_operation: None, - DeviceAttributes.bright: 0 - }) + DeviceAttributes.bright: 0, + }, + ) self._modes = { - 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR - 0x1: "Auto", # BYTE_MODE_AUTO_WASH - 0x2: "Heavy", # BYTE_MODE_STRONG_WASH - 0x3: "Normal", # BYTE_MODE_STANDARD_WASH - 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH - 0x5: "Delicate", # BYTE_MODE_GLASS_WASH - 0x6: "Hour", # BYTE_MODE_HOUR_WASH - 0x7: "Quick", # BYTE_MODE_FAST_WASH - 0x8: "Rinse", # BYTE_MODE_SOAK_WASH - 0x9: "90min", # BYTE_MODE_90MIN_WASH - 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN - 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH - 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE - 0xD: "Germ", # BYTE_MODE_GERM ??? - 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH - 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM - 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH - 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH - 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH - 0x14: "Less Wash", # BYTE_MODE_LESS_WASH - 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH - 0x19: "Cloud Wash" # BYTE_MODE_CLOUD_WASH + 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR + 0x1: "Auto", # BYTE_MODE_AUTO_WASH + 0x2: "Heavy", # BYTE_MODE_STRONG_WASH + 0x3: "Normal", # BYTE_MODE_STANDARD_WASH + 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH + 0x5: "Delicate", # BYTE_MODE_GLASS_WASH + 0x6: "Hour", # BYTE_MODE_HOUR_WASH + 0x7: "Quick", # BYTE_MODE_FAST_WASH + 0x8: "Rinse", # BYTE_MODE_SOAK_WASH + 0x9: "90min", # BYTE_MODE_90MIN_WASH + 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN + 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH + 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE + 0xD: "Germ", # BYTE_MODE_GERM ??? + 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH + 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM + 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH + 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH + 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH + 0x14: "Less Wash", # BYTE_MODE_LESS_WASH + 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH + 0x19: "Cloud Wash", # BYTE_MODE_CLOUD_WASH } self._status = ["Off", "Idle", "Delay", "Running", "Error"] self._progress = ["Idle", "Pre-wash", "Wash", "Rinse", "Dry", "Complete"] diff --git a/custom_components/midea_ac_lan/midea/devices/x34/message.py b/custom_components/midea_ac_lan/midea/devices/x34/message.py index 729280f1..c107acd2 100644 --- a/custom_components/midea_ac_lan/midea/devices/x34/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x34/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class Message34Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0x34, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,7 +20,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00) + body_type=0x00, + ) @property def _body(self): @@ -37,16 +33,14 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x08) + body_type=0x08, + ) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray([ - power, - 0x00, 0x00, 0x00 - ]) + return bytearray([power, 0x00, 0x00, 0x00]) class MessageLock(Message34Base): @@ -54,7 +48,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x83) + body_type=0x83, + ) self.lock = False @property @@ -68,14 +63,18 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x81) + body_type=0x81, + ) self.storage = False @property def _body(self): storage = 0x01 if self.storage else 0x00 - return bytearray([0x00, 0x00, 0x00, storage]) + \ - bytearray([0xff] * 6) + bytearray([0x00] * 27) + return ( + bytearray([0x00, 0x00, 0x00, storage]) + + bytearray([0xFF] * 6) + + bytearray([0x00] * 27) + ) class Message34Body(MessageBody): @@ -85,9 +84,9 @@ def __init__(self, body): self.status = body[1] self.mode = body[2] self.additional = body[3] - self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close - self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage - self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage + self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close + self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage + self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage start_pause = (body[5] & 0x08) > 0 if start_pause: self.start = True @@ -115,7 +114,9 @@ def __init__(self, body): class Message34Response(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or \ - (self.message_type in [MessageType.query, MessageType.notify1] and self.body_type == 0): + if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or ( + self.message_type in [MessageType.query, MessageType.notify1] + and self.body_type == 0 + ): self.set_body(Message34Body(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/x40/device.py b/custom_components/midea_ac_lan/midea/devices/x40/device.py index b49a0bc2..d4280383 100644 --- a/custom_components/midea_ac_lan/midea/devices/x40/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x40/device.py @@ -1,14 +1,13 @@ import logging import math -from .message import ( - MessageQuery, - MessageSet, - Message40Response -) + +from .message import Message40Response, MessageQuery, MessageSet + try: from enum import StrEnum except ImportError: from ...backports.enum import StrEnum + from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -27,17 +26,17 @@ class Midea40Device(MiedaDevice): _directions = ["60", "70", "80", "90", "100", "Oscillate"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, ): super().__init__( name=name, @@ -56,8 +55,9 @@ def __init__( DeviceAttributes.direction: False, DeviceAttributes.ventilation: False, DeviceAttributes.smelly_sensor: False, - DeviceAttributes.current_temperature: None - }) + DeviceAttributes.current_temperature: None, + }, + ) self._fields = {} @property @@ -69,8 +69,11 @@ def _convert_to_midea_direction(direction): if direction == "Oscillate": result = 0xFD else: - result = Midea40Device._directions.index(direction) * 10 + 60 \ - if direction in Midea40Device._directions else 0xFD + result = ( + Midea40Device._directions.index(direction) * 10 + 60 + if direction in Midea40Device._directions + else 0xFD + ) return result @staticmethod @@ -102,18 +105,22 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [DeviceAttributes.light, - DeviceAttributes.fan_speed, - DeviceAttributes.direction, - DeviceAttributes.ventilation, - DeviceAttributes.smelly_sensor]: + if attr in [ + DeviceAttributes.light, + DeviceAttributes.fan_speed, + DeviceAttributes.direction, + DeviceAttributes.ventilation, + DeviceAttributes.smelly_sensor, + ]: message = MessageSet(self._protocol_version) message.fields = self._fields message.light = self._attributes[DeviceAttributes.light] message.ventilation = self._attributes[DeviceAttributes.ventilation] message.smelly_sensor = self._attributes[DeviceAttributes.smelly_sensor] message.fan_speed = self._attributes[DeviceAttributes.fan_speed] - message.direction = self._convert_to_midea_direction(self._attributes[DeviceAttributes.direction]) + message.direction = self._convert_to_midea_direction( + self._attributes[DeviceAttributes.direction] + ) if attr == DeviceAttributes.direction: message.direction = self._convert_to_midea_direction(value) elif attr == DeviceAttributes.ventilation and message.fan_speed == 2: diff --git a/custom_components/midea_ac_lan/midea/devices/x40/message.py b/custom_components/midea_ac_lan/midea/devices/x40/message.py index 71baa781..fba44d2e 100644 --- a/custom_components/midea_ac_lan/midea/devices/x40/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x40/message.py @@ -1,9 +1,4 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody -) +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType class Message40Base(MessageRequest): @@ -12,7 +7,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0x40, protocol_version=protocol_version, message_type=message_type, - body_type=body_type + body_type=body_type, ) @property @@ -25,12 +20,12 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01) + body_type=0x01, + ) @property def _body(self): - return bytearray([ - ]) + return bytearray([]) class MessageSet(Message40Base): @@ -38,7 +33,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01) + body_type=0x01, + ) self.fields = {} self.light = False self.fan_speed = 0 @@ -58,47 +54,49 @@ def _body(self): ventilation = 1 if self.ventilation else 0 direction = self.direction smelly_sensor = 1 if self.smelly_sensor else 0 - return bytearray([ - light, - self.read_field("MAIN_LIGHT_BRIGHTNESS"), - self.read_field("NIGHT_LIGHT_ENABLE"), - self.read_field("NIGHT_LIGHT_BRIGHTNESS"), - self.read_field("RADAR_INDUCTION_ENABLE"), - self.read_field("RADAR_INDUCTION_CLOSING_TIME"), - self.read_field("LIGHT_INTENSITY_THRESHOLD"), - self.read_field("RADAR_SENSITIVITY"), - self.read_field("HEATING_ENABLE"), - self.read_field("HEATING_TEMPERATURE"), - self.read_field("HEATING_SPEED"), - self.read_field("HEATING_DIRECTION"), - self.read_field("BATH_ENABLE"), - self.read_field("BATH_HEATING_TIME"), - self.read_field("BATH_TEMPERATURE"), - self.read_field("BATH_SPEED"), - self.read_field("BATH_DIRECTION"), - ventilation, - self.read_field("VENTILATION_SPEED"), - self.read_field("VENTILATION_DIRECTION"), - self.read_field("DRYING_ENABLE"), - self.read_field("DRYING_TIME"), - self.read_field("DRYING_TEMPERATURE"), - self.read_field("DRYING_SPEED"), - self.read_field("DRYING_DIRECTION"), - blow, - fan_speed, - direction, - self.read_field("DELAY_ENABLE"), - self.read_field("DELAY_TIME"), - self.read_field("SOFT_WIND_ENABLE"), - self.read_field("SOFT_WIND_TIME"), - self.read_field("SOFT_WIND_TEMPERATURE"), - self.read_field("SOFT_WIND_SPEED"), - self.read_field("SOFT_WIND_DIRECTION"), - self.read_field("WINDLESS_ENABLE"), - self.read_field("ANION_ENABLE"), - smelly_sensor, - self.read_field("SMELLY_THRESHOLD") - ]) + return bytearray( + [ + light, + self.read_field("MAIN_LIGHT_BRIGHTNESS"), + self.read_field("NIGHT_LIGHT_ENABLE"), + self.read_field("NIGHT_LIGHT_BRIGHTNESS"), + self.read_field("RADAR_INDUCTION_ENABLE"), + self.read_field("RADAR_INDUCTION_CLOSING_TIME"), + self.read_field("LIGHT_INTENSITY_THRESHOLD"), + self.read_field("RADAR_SENSITIVITY"), + self.read_field("HEATING_ENABLE"), + self.read_field("HEATING_TEMPERATURE"), + self.read_field("HEATING_SPEED"), + self.read_field("HEATING_DIRECTION"), + self.read_field("BATH_ENABLE"), + self.read_field("BATH_HEATING_TIME"), + self.read_field("BATH_TEMPERATURE"), + self.read_field("BATH_SPEED"), + self.read_field("BATH_DIRECTION"), + ventilation, + self.read_field("VENTILATION_SPEED"), + self.read_field("VENTILATION_DIRECTION"), + self.read_field("DRYING_ENABLE"), + self.read_field("DRYING_TIME"), + self.read_field("DRYING_TEMPERATURE"), + self.read_field("DRYING_SPEED"), + self.read_field("DRYING_DIRECTION"), + blow, + fan_speed, + direction, + self.read_field("DELAY_ENABLE"), + self.read_field("DELAY_TIME"), + self.read_field("SOFT_WIND_ENABLE"), + self.read_field("SOFT_WIND_TIME"), + self.read_field("SOFT_WIND_TEMPERATURE"), + self.read_field("SOFT_WIND_SPEED"), + self.read_field("SOFT_WIND_DIRECTION"), + self.read_field("WINDLESS_ENABLE"), + self.read_field("ANION_ENABLE"), + smelly_sensor, + self.read_field("SMELLY_THRESHOLD"), + ] + ) class Message40Body(MessageBody): @@ -157,6 +155,10 @@ def __init__(self, body): class Message40Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: + if ( + self.message_type + in [MessageType.set, MessageType.notify1, MessageType.query] + and self.body_type == 0x01 + ): self.set_body(Message40Body(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea_devices.py b/custom_components/midea_ac_lan/midea_devices.py index 8cce8b94..499f412a 100644 --- a/custom_components/midea_ac_lan/midea_devices.py +++ b/custom_components/midea_ac_lan/midea_devices.py @@ -1,19 +1,17 @@ +from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass from homeassistant.const import ( + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_MILLION, + PERCENTAGE, Platform, - UnitOfTime, - UnitOfTemperature, + UnitOfEnergy, UnitOfPower, - PERCENTAGE, + UnitOfTemperature, + UnitOfTime, UnitOfVolume, - UnitOfEnergy, - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - CONCENTRATION_PARTS_PER_MILLION ) -from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass -from .midea.devices.x26.device import DeviceAttributes as X26Attributes -from .midea.devices.x34.device import DeviceAttributes as X34Attributes -from .midea.devices.x40.device import DeviceAttributes as X40Attributes + from .midea.devices.a1.device import DeviceAttributes as A1Attributes from .midea.devices.ac.device import DeviceAttributes as ACAttributes from .midea.devices.b0.device import DeviceAttributes as B0Attributes @@ -44,18 +42,16 @@ from .midea.devices.fb.device import DeviceAttributes as FBAttributes from .midea.devices.fc.device import DeviceAttributes as FCAttributes from .midea.devices.fd.device import DeviceAttributes as FDAttributes - +from .midea.devices.x26.device import DeviceAttributes as X26Attributes +from .midea.devices.x34.device import DeviceAttributes as X34Attributes +from .midea.devices.x40.device import DeviceAttributes as X40Attributes MIDEA_DEVICES = { 0x13: { "name": "Light", "entities": { - "light": { - "type": Platform.LIGHT, - "icon": "mdi:lightbulb", - "default": True - } - } + "light": {"type": Platform.LIGHT, "icon": "mdi:lightbulb", "default": True} + }, }, 0x26: { "name": "Bathroom Master", @@ -65,43 +61,43 @@ "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, X26Attributes.current_humidity: { "type": Platform.SENSOR, "name": "Current Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, X26Attributes.current_radar: { "type": Platform.BINARY_SENSOR, "name": "Occupancy Status", - "device_class": BinarySensorDeviceClass.MOVING + "device_class": BinarySensorDeviceClass.MOVING, }, X26Attributes.main_light: { "type": Platform.SWITCH, "name": "Main Light", - "icon": "mdi:lightbulb" + "icon": "mdi:lightbulb", }, X26Attributes.night_light: { "type": Platform.SWITCH, "name": "Night Light", - "icon": "mdi:lightbulb" + "icon": "mdi:lightbulb", }, X26Attributes.mode: { "type": Platform.SELECT, "name": "Mode", "options": "preset_modes", - "icon": "mdi:fan" + "icon": "mdi:fan", }, X26Attributes.direction: { "type": Platform.SELECT, "name": "Direction", "options": "directions", - "icon": "mdi:arrow-split-vertical" - } - } + "icon": "mdi:arrow-split-vertical", + }, + }, }, 0x34: { "name": "Sink Dishwasher", @@ -110,81 +106,78 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR + "device_class": BinarySensorDeviceClass.DOOR, }, X34Attributes.rinse_aid: { "type": Platform.BINARY_SENSOR, "name": "Rinse Aid Shortage", "icon": "mdi:bottle-tonic", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, X34Attributes.salt: { "type": Platform.BINARY_SENSOR, "name": "Salt Shortage", "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, X34Attributes.humidity: { "type": Platform.SENSOR, "name": "Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, X34Attributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360" + "icon": "mdi:rotate-360", }, X34Attributes.status: { "type": Platform.SENSOR, "name": "Status", - "icon": "mdi:information" + "icon": "mdi:information", }, X34Attributes.storage_remaining: { "type": Platform.SENSOR, "name": "Storage Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.HOURS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, X34Attributes.temperature: { "type": Platform.SENSOR, "name": "Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, X34Attributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT - }, - X34Attributes.child_lock: { - "type": Platform.LOCK, - "name": "Child Lock" + "state_class": SensorStateClass.MEASUREMENT, }, + X34Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, X34Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, X34Attributes.storage: { "type": Platform.SWITCH, "name": "Storage", - "icon": "mdi:repeat-variant" + "icon": "mdi:repeat-variant", }, X34Attributes.mode: { "type": Platform.SENSOR, "name": "Working Mode", - "icon": "mdi:dishwasher" + "icon": "mdi:dishwasher", }, X34Attributes.error_code: { "type": Platform.SENSOR, "name": "Error Code", - "icon": "mdi:alert-box" + "icon": "mdi:alert-box", }, X34Attributes.softwater: { "type": Platform.SENSOR, @@ -194,47 +187,43 @@ X34Attributes.bright: { "type": Platform.SENSOR, "name": "Bright Level", - "icon": "mdi:star-four-points" - } - } + "icon": "mdi:star-four-points", + }, + }, }, 0x40: { "name": "Integrated Ceiling Fan", "entities": { - "fan": { - "type": Platform.FAN, - "icon": "mdi:fan", - "default": True - }, + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, X40Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, X40Attributes.light: { "type": Platform.SWITCH, "name": "Light", - "icon": "mdi:lightbulb" + "icon": "mdi:lightbulb", }, X40Attributes.ventilation: { "type": Platform.SWITCH, "name": "Ventilation", - "icon": "mdi:air-filter" + "icon": "mdi:air-filter", }, X40Attributes.smelly_sensor: { "type": Platform.SWITCH, "name": "Smelly Sensor", - "icon": "mdi:scent" + "icon": "mdi:scent", }, X40Attributes.direction: { "type": Platform.SELECT, "name": "Direction", "options": "directions", - "icon": "mdi:arrow-split-vertical" - } - } + "icon": "mdi:arrow-split-vertical", + }, + }, }, 0xA1: { "name": "Dehumidifier", @@ -242,72 +231,69 @@ "humidifier": { "type": Platform.HUMIDIFIER, "icon": "mdi:air-humidifier", - "default": True - }, - A1Attributes.child_lock: { - "type": Platform.LOCK, - "name": "Child Lock" + "default": True, }, + A1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, A1Attributes.anion: { "type": Platform.SWITCH, "name": "Anion", - "icon": "mdi:vanish" + "icon": "mdi:vanish", }, A1Attributes.prompt_tone: { "type": Platform.SWITCH, "name": "Prompt Tone", - "icon": "mdi:bell" + "icon": "mdi:bell", }, A1Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, A1Attributes.swing: { "type": Platform.SWITCH, "name": "swing", - "icon": "mdi:pan-horizontal" + "icon": "mdi:pan-horizontal", }, A1Attributes.fan_speed: { "type": Platform.SELECT, "name": "Fan Speed", "options": "fan_speeds", - "icon": "mdi:fan" + "icon": "mdi:fan", }, A1Attributes.water_level_set: { "type": Platform.SELECT, "name": "Water Level Setting", "options": "water_level_sets", - "icon": "mdi:cup-water" + "icon": "mdi:cup-water", }, A1Attributes.current_humidity: { "type": Platform.SENSOR, "name": "Current Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, A1Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, A1Attributes.tank: { "type": Platform.SENSOR, "name": "Tank", "icon": "mdi:cup-water", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, A1Attributes.tank_full: { "type": Platform.BINARY_SENSOR, "name": "Tank status", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM - } - } + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + }, }, 0xAC: { "name": "Air Conditioner", @@ -315,147 +301,143 @@ "climate": { "type": Platform.CLIMATE, "icon": "mdi:air-conditioner", - "default": True - }, - "fresh_air": { - "type": Platform.FAN, - "icon": "mdi:fan", - "name": "Fresh Air" + "default": True, }, + "fresh_air": {"type": Platform.FAN, "icon": "mdi:fan", "name": "Fresh Air"}, ACAttributes.aux_heating: { "type": Platform.SWITCH, "name": "Aux Heating", - "icon": "mdi:heat-wave" + "icon": "mdi:heat-wave", }, ACAttributes.boost_mode: { "type": Platform.SWITCH, "name": "Boost Mode", - "icon": "mdi:turbine" + "icon": "mdi:turbine", }, ACAttributes.breezeless: { "type": Platform.SWITCH, "name": "Breezeless", - "icon": "mdi:tailwind" + "icon": "mdi:tailwind", }, ACAttributes.comfort_mode: { "type": Platform.SWITCH, "name": "Comfort Mode", - "icon": "mdi:alpha-c-circle" + "icon": "mdi:alpha-c-circle", }, ACAttributes.dry: { "type": Platform.SWITCH, "name": "Dry", - "icon": "mdi:air-filter" + "icon": "mdi:air-filter", }, ACAttributes.eco_mode: { "type": Platform.SWITCH, "name": "ECO Mode", - "icon": "mdi:leaf-circle" + "icon": "mdi:leaf-circle", }, ACAttributes.frost_protect: { "type": Platform.SWITCH, "name": "Frost Protect", - "icon": "mdi:snowflake-alert" + "icon": "mdi:snowflake-alert", }, ACAttributes.indirect_wind: { "type": Platform.SWITCH, "name": "Indirect Wind", - "icon": "mdi:tailwind" + "icon": "mdi:tailwind", }, ACAttributes.natural_wind: { "type": Platform.SWITCH, "name": "Natural Wind", - "icon": "mdi:tailwind" + "icon": "mdi:tailwind", }, ACAttributes.prompt_tone: { "type": Platform.SWITCH, "name": "Prompt Tone", - "icon": "mdi:bell" + "icon": "mdi:bell", }, ACAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, ACAttributes.screen_display: { "type": Platform.SWITCH, "name": "Screen Display", - "icon": "mdi:television-ambient-light" + "icon": "mdi:television-ambient-light", }, ACAttributes.screen_display_alternate: { "type": Platform.SWITCH, "name": "Screen Display Alternate", - "icon": "mdi:television-ambient-light" + "icon": "mdi:television-ambient-light", }, ACAttributes.sleep_mode: { "type": Platform.SWITCH, "name": "Sleep Mode", - "icon": "mdi:power-sleep" + "icon": "mdi:power-sleep", }, ACAttributes.smart_eye: { "type": Platform.SWITCH, "name": "Smart Eye", - "icon": "mdi:eye" + "icon": "mdi:eye", }, ACAttributes.swing_horizontal: { "type": Platform.SWITCH, "name": "Swing Horizontal", - "icon": "mdi:arrow-split-vertical" + "icon": "mdi:arrow-split-vertical", }, ACAttributes.swing_vertical: { "type": Platform.SWITCH, "name": "Swing Vertical", - "icon": "mdi:arrow-split-horizontal" + "icon": "mdi:arrow-split-horizontal", }, ACAttributes.full_dust: { "type": Platform.BINARY_SENSOR, "name": "Full of Dust", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, ACAttributes.indoor_humidity: { "type": Platform.SENSOR, "name": "Indoor Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, ACAttributes.indoor_temperature: { "type": Platform.SENSOR, "name": "Indoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, ACAttributes.outdoor_temperature: { "type": Platform.SENSOR, "name": "Outdoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, ACAttributes.total_energy_consumption: { "type": Platform.SENSOR, "name": "Total Energy Consumption", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING + "state_class": SensorStateClass.TOTAL_INCREASING, }, ACAttributes.current_energy_consumption: { "type": Platform.SENSOR, "name": "Current Energy Consumption", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING + "state_class": SensorStateClass.TOTAL_INCREASING, }, ACAttributes.realtime_power: { "type": Platform.SENSOR, "name": "Realtime Power", "device_class": SensorDeviceClass.POWER, "unit": UnitOfPower.WATT, - "state_class": SensorStateClass.MEASUREMENT - } - } + "state_class": SensorStateClass.MEASUREMENT, + }, + }, }, 0xB0: { "name": "Microwave Oven", @@ -464,32 +446,32 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR + "device_class": BinarySensorDeviceClass.DOOR, }, B0Attributes.tank_ejected: { "type": Platform.BINARY_SENSOR, "name": "Tank Ejected", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B0Attributes.water_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Water Change Reminder", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B0Attributes.water_shortage: { "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B0Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, B0Attributes.status: { "type": Platform.SENSOR, @@ -501,9 +483,9 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT - } - } + "state_class": SensorStateClass.MEASUREMENT, + }, + }, }, 0xB1: { "name": "Electric Oven", @@ -512,32 +494,32 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR + "device_class": BinarySensorDeviceClass.DOOR, }, B1Attributes.tank_ejected: { "type": Platform.BINARY_SENSOR, "name": "Tank ejected", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B1Attributes.water_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Water Change Reminder", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B1Attributes.water_shortage: { "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B1Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, B1Attributes.status: { "type": Platform.SENSOR, @@ -549,9 +531,9 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT - } - } + "state_class": SensorStateClass.MEASUREMENT, + }, + }, }, 0xB3: { "name": "Dish Sterilizer", @@ -613,58 +595,58 @@ B3Attributes.top_compartment_status: { "type": Platform.SENSOR, "name": "Top Compartment Status", - "icon": "mdi:information" + "icon": "mdi:information", }, B3Attributes.top_compartment_temperature: { "type": Platform.SENSOR, "name": "Top Compartment Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, B3Attributes.top_compartment_remaining: { "type": Platform.SENSOR, "name": "Top Compartment Remaining", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, B3Attributes.middle_compartment_status: { "type": Platform.SENSOR, "name": "Middle Compartment Status", - "icon": "mdi:information" + "icon": "mdi:information", }, B3Attributes.middle_compartment_temperature: { "type": Platform.SENSOR, "name": "Middle Compartment Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, B3Attributes.middle_compartment_remaining: { "type": Platform.SENSOR, "name": "Middle Compartment Remaining", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, B3Attributes.bottom_compartment_status: { "type": Platform.SENSOR, "name": "Bottom Compartment Status", - "icon": "mdi:information" + "icon": "mdi:information", }, B3Attributes.bottom_compartment_temperature: { "type": Platform.SENSOR, "name": "Bottom Compartment Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, B3Attributes.bottom_compartment_remaining: { "type": Platform.SENSOR, "name": "Bottom Compartment Remaining", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT - } - } + "state_class": SensorStateClass.MEASUREMENT, + }, + }, }, 0xB4: { "name": "Toaster", @@ -673,32 +655,32 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR + "device_class": BinarySensorDeviceClass.DOOR, }, B4Attributes.tank_ejected: { "type": Platform.BINARY_SENSOR, "name": "Tank ejected", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B4Attributes.water_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Water Change Reminder", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B4Attributes.water_shortage: { "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B4Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, B4Attributes.status: { "type": Platform.SENSOR, @@ -710,47 +692,43 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT - } - } + "state_class": SensorStateClass.MEASUREMENT, + }, + }, }, 0xB6: { "name": "Range Hood", "entities": { - "fan": { - "type": Platform.FAN, - "icon": "mdi:fan", - "default": True - }, + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, B6Attributes.light: { "type": Platform.SWITCH, "name": "Light", - "icon": "mdi:lightbulb" + "icon": "mdi:lightbulb", }, B6Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, B6Attributes.cleaning_reminder: { "type": Platform.BINARY_SENSOR, "name": "Cleaning Reminder", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B6Attributes.oilcup_full: { "type": Platform.BINARY_SENSOR, "name": "Oil-cup Full", "icon": "mdi:cup", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, B6Attributes.fan_level: { "type": Platform.SENSOR, "name": "Fan level", "icon": "mdi:fan", - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, - } + }, }, 0xBF: { "name": "Microwave Steam Oven", @@ -759,32 +737,32 @@ "type": Platform.BINARY_SENSOR, "name": "Tank ejected", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, BFAttributes.water_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Water Change Reminder", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, BFAttributes.door: { "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR + "device_class": BinarySensorDeviceClass.DOOR, }, BFAttributes.water_shortage: { "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, BFAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, BFAttributes.status: { "type": Platform.SENSOR, @@ -796,9 +774,9 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT - } - } + "state_class": SensorStateClass.MEASUREMENT, + }, + }, }, 0xC2: { "name": "Toilet", @@ -806,58 +784,55 @@ C2Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, C2Attributes.sensor_light: { "type": Platform.SWITCH, "name": "Sensor Light", - "icon": "mdi:lightbulb" + "icon": "mdi:lightbulb", }, C2Attributes.foam_shield: { "type": Platform.SWITCH, "name": "Foam Shield", "icon": "mdi:chart-bubble", }, - C2Attributes.child_lock: { - "type": Platform.LOCK, - "name": "Child Lock" - }, + C2Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, C2Attributes.seat_status: { "type": Platform.BINARY_SENSOR, "name": "Seat Status", - "icon": "mdi:seat-legroom-normal" + "icon": "mdi:seat-legroom-normal", }, C2Attributes.lid_status: { "type": Platform.BINARY_SENSOR, "name": "Lid Status", - "icon": "mdi:toilet" + "icon": "mdi:toilet", }, C2Attributes.light_status: { "type": Platform.BINARY_SENSOR, "name": "Light Status", "icon": "mdi:lightbulb", - "device_class": BinarySensorDeviceClass.LIGHT + "device_class": BinarySensorDeviceClass.LIGHT, }, C2Attributes.water_temperature: { "type": Platform.SENSOR, "name": "Water Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, C2Attributes.seat_temperature: { "type": Platform.SENSOR, "name": "Seat Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, C2Attributes.filter_life: { "type": Platform.SENSOR, "name": "Filter Life", "icon": "mdi:toilet", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, C2Attributes.dry_level: { "type": Platform.NUMBER, @@ -865,7 +840,7 @@ "icon": "mdi:fire", "max": "max_dry_level", "min": 0, - "step": 1 + "step": 1, }, C2Attributes.water_temp_level: { "type": Platform.NUMBER, @@ -873,7 +848,7 @@ "icon": "mdi:fire", "max": "max_water_temp_level", "min": 0, - "step": 1 + "step": 1, }, C2Attributes.seat_temp_level: { "type": Platform.NUMBER, @@ -881,9 +856,9 @@ "icon": "mdi:fire", "max": "max_seat_temp_level", "min": 0, - "step": 1 - } - } + "step": 1, + }, + }, }, 0xC3: { "name": "Heat Pump Wi-Fi Controller", @@ -893,70 +868,70 @@ "icon": "mdi:air-conditioner", "name": "Zone1 Thermostat", "zone": 0, - "default": True + "default": True, }, "climate_zone2": { "type": Platform.CLIMATE, "icon": "mdi:air-conditioner", "name": "Zone2 Thermostat", "zone": 1, - "default": True + "default": True, }, "water_heater": { "type": Platform.WATER_HEATER, "icon": "mdi:heat-pump", "name": "Domestic hot water", - "default": True + "default": True, }, C3Attributes.disinfect: { "type": Platform.SWITCH, "name": "Disinfect", - "icon": "mdi:water-plus-outline" + "icon": "mdi:water-plus-outline", }, C3Attributes.dhw_power: { "type": Platform.SWITCH, "name": "DHW Power", - "icon": "mdi:power" + "icon": "mdi:power", }, C3Attributes.eco_mode: { "type": Platform.SWITCH, "name": "ECO Mode", - "icon": "mdi:leaf-circle" + "icon": "mdi:leaf-circle", }, C3Attributes.fast_dhw: { "type": Platform.SWITCH, "name": "Fast DHW", - "icon": "mdi:rotate-orbit" + "icon": "mdi:rotate-orbit", }, C3Attributes.silent_mode: { "type": Platform.SWITCH, "name": "Silent Mode", - "icon": "mdi:fan-remove" + "icon": "mdi:fan-remove", }, C3Attributes.tbh: { "type": Platform.SWITCH, "name": "TBH", - "icon": "mdi:water-boiler" + "icon": "mdi:water-boiler", }, C3Attributes.zone1_curve: { "type": Platform.SWITCH, "name": "Zone1 Curve", - "icon": "mdi:chart-bell-curve-cumulative" + "icon": "mdi:chart-bell-curve-cumulative", }, C3Attributes.zone2_curve: { "type": Platform.SWITCH, "name": "Zone2 Curve", - "icon": "mdi:chart-bell-curve-cumulative" + "icon": "mdi:chart-bell-curve-cumulative", }, C3Attributes.zone1_power: { "type": Platform.SWITCH, "name": "Zone1 Power", - "icon": "mdi:power" + "icon": "mdi:power", }, C3Attributes.zone2_power: { "type": Platform.SWITCH, "name": "Zone2 Power", - "icon": "mdi:power" + "icon": "mdi:power", }, C3Attributes.zone1_water_temp_mode: { "type": Platform.BINARY_SENSOR, @@ -985,14 +960,14 @@ C3Attributes.error_code: { "type": Platform.SENSOR, "name": "Error Code", - "icon": "mdi:alpha-e-circle" + "icon": "mdi:alpha-e-circle", }, C3Attributes.tank_actual_temperature: { "type": Platform.SENSOR, "name": "Tank Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, C3Attributes.status_dhw: { "type": Platform.BINARY_SENSOR, @@ -1023,23 +998,23 @@ "name": "Total energy consumption", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING + "state_class": SensorStateClass.TOTAL_INCREASING, }, C3Attributes.total_produced_energy: { "type": Platform.SENSOR, "name": "Total produced energy", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING + "state_class": SensorStateClass.TOTAL_INCREASING, }, C3Attributes.outdoor_temperature: { "type": Platform.SENSOR, "name": "Outdoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT - } - } + "state_class": SensorStateClass.MEASUREMENT, + }, + }, }, 0xCA: { "name": "Refrigerator", @@ -1048,112 +1023,112 @@ "type": Platform.BINARY_SENSOR, "name": "Bar Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR + "device_class": BinarySensorDeviceClass.DOOR, }, CAAttributes.bar_door_overtime: { "type": Platform.BINARY_SENSOR, "name": "Bar Door Overtime", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, CAAttributes.flex_zone_door: { "type": Platform.BINARY_SENSOR, "name": "Flex Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR + "device_class": BinarySensorDeviceClass.DOOR, }, CAAttributes.flex_zone_door_overtime: { "type": Platform.BINARY_SENSOR, "name": "Flex Zone Door", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, CAAttributes.freezer_door: { "type": Platform.BINARY_SENSOR, "name": "Freezer Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR + "device_class": BinarySensorDeviceClass.DOOR, }, CAAttributes.freezer_door_overtime: { "type": Platform.BINARY_SENSOR, "name": "Freezer Door Overtime", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, CAAttributes.refrigerator_door: { "type": Platform.BINARY_SENSOR, "name": "Refrigerator Door", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, CAAttributes.refrigerator_door_overtime: { "type": Platform.BINARY_SENSOR, "name": "Refrigerator Door Overtime", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, CAAttributes.flex_zone_actual_temp: { "type": Platform.SENSOR, "name": "Flex Zone Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CAAttributes.flex_zone_setting_temp: { "type": Platform.SENSOR, "name": "Flex Zone Setting Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CAAttributes.freezer_actual_temp: { "type": Platform.SENSOR, "name": "Freezer Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CAAttributes.freezer_setting_temp: { "type": Platform.SENSOR, "name": "Freezer Setting Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CAAttributes.energy_consumption: { "type": Platform.SENSOR, "name": "Energy Consumption", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING + "state_class": SensorStateClass.TOTAL_INCREASING, }, CAAttributes.refrigerator_actual_temp: { "type": Platform.SENSOR, "name": "Refrigerator Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CAAttributes.refrigerator_setting_temp: { "type": Platform.SENSOR, "name": "Refrigerator Setting Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CAAttributes.right_flex_zone_actual_temp: { "type": Platform.SENSOR, "name": "Right Flex Zone Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CAAttributes.right_flex_zone_setting_temp: { "type": Platform.SENSOR, "name": "Right Flex Zone Setting Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, }, }, @@ -1163,46 +1138,46 @@ "climate": { "type": Platform.CLIMATE, "icon": "hass:air-conditioner", - "default": True + "default": True, }, CCAttributes.aux_heating: { "type": Platform.SWITCH, "name": "Aux Heating", - "icon": "mdi:heat-wave" + "icon": "mdi:heat-wave", }, CCAttributes.eco_mode: { "type": Platform.SWITCH, "name": "ECO Mode", - "icon": "mdi:leaf-circle" + "icon": "mdi:leaf-circle", }, CCAttributes.night_light: { "type": Platform.SWITCH, "name": "Night Light", - "icon": "mdi:lightbulb" + "icon": "mdi:lightbulb", }, CCAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, CCAttributes.sleep_mode: { "type": Platform.SWITCH, "name": "Sleep Mode", - "icon": "mdi:power-sleep" + "icon": "mdi:power-sleep", }, CCAttributes.swing: { "type": Platform.SWITCH, "name": "Swing", - "icon": "mdi:arrow-split-horizontal" + "icon": "mdi:arrow-split-horizontal", }, CCAttributes.indoor_temperature: { "type": Platform.SENSOR, "name": "Indoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, - } + }, }, 0xCD: { "name": "Heat Pump Water Heater", @@ -1210,132 +1185,125 @@ "water_heater": { "type": Platform.WATER_HEATER, "icon": "mdi:heat-pump", - "default": True + "default": True, }, CDAttributes.compressor_status: { "type": Platform.BINARY_SENSOR, "name": "Compressor Status", "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, CDAttributes.compressor_temperature: { "type": Platform.SENSOR, "name": "Compressor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CDAttributes.condenser_temperature: { "type": Platform.SENSOR, "name": "Condenser Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CDAttributes.outdoor_temperature: { "type": Platform.SENSOR, "name": "Outdoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CDAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" - } - } + "icon": "mdi:power", + }, + }, }, 0xCE: { "name": "Fresh Air Appliance", "entities": { - "fan": { - "type": Platform.FAN, - "icon": "mdi:fan", - "default": True - }, + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, CEAttributes.filter_cleaning_reminder: { "type": Platform.BINARY_SENSOR, "name": "Filter Cleaning Reminder", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, CEAttributes.filter_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Filter Change Reminder", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, CEAttributes.current_humidity: { "type": Platform.SENSOR, "name": "Current Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CEAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CEAttributes.co2: { "type": Platform.SENSOR, "name": "Carbon Dioxide", "device_class": SensorDeviceClass.CO2, "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CEAttributes.hcho: { "type": Platform.SENSOR, "name": "Methanal", "icon": "mdi:molecule", "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, CEAttributes.pm25: { "type": Platform.SENSOR, "name": "PM 2.5", "device_class": SensorDeviceClass.PM25, "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT - }, - CEAttributes.child_lock: { - "type": Platform.LOCK, - "name": "Child Lock" + "state_class": SensorStateClass.MEASUREMENT, }, + CEAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, CEAttributes.aux_heating: { "type": Platform.SWITCH, "name": "Aux Heating", - "icon": "mdi:heat-wave" + "icon": "mdi:heat-wave", }, CEAttributes.eco_mode: { "type": Platform.SWITCH, "name": "ECO Mode", - "icon": "mdi:leaf-circle" + "icon": "mdi:leaf-circle", }, CEAttributes.link_to_ac: { "type": Platform.SWITCH, "name": "Link to AC", - "icon": "mdi:link" + "icon": "mdi:link", }, CEAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, CEAttributes.powerful_purify: { "type": Platform.SWITCH, "name": "Powerful Purification", - "icon": "mdi:turbine" + "icon": "mdi:turbine", }, CEAttributes.sleep_mode: { "type": Platform.SWITCH, "name": "Sleep Mode", - "icon": "mdi:power-sleep" + "icon": "mdi:power-sleep", }, - } + }, }, 0xCF: { "name": "Heat Pump", @@ -1343,26 +1311,26 @@ "climate": { "type": Platform.CLIMATE, "icon": "hass:air-conditioner", - "default": True + "default": True, }, CFAttributes.aux_heating: { "type": Platform.SWITCH, "name": "Aux Heating", - "icon": "mdi:heat-wave" + "icon": "mdi:heat-wave", }, CFAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, CFAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, - } + }, }, 0xDA: { "name": "Top Load Washer", @@ -1372,90 +1340,90 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, DAAttributes.wash_time: { "type": Platform.SENSOR, "name": "wash time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, DAAttributes.soak_time: { "type": Platform.SENSOR, "name": "soak time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, DAAttributes.dehydration_time: { "type": Platform.SENSOR, "name": "dehydration time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, DAAttributes.dehydration_speed: { "type": Platform.SENSOR, "name": "dehydration speed", - "icon": "mdi:speedometer" + "icon": "mdi:speedometer", }, DAAttributes.error_code: { "type": Platform.SENSOR, "name": "error code", - "icon": "mdi:washing-machine-alert" + "icon": "mdi:washing-machine-alert", }, DAAttributes.rinse_count: { "type": Platform.SENSOR, "name": "rinse count", - "icon": "mdi:water-sync" + "icon": "mdi:water-sync", }, DAAttributes.rinse_level: { "type": Platform.SENSOR, "name": "rinse level", - "icon": "mdi:hydraulic-oil-level" + "icon": "mdi:hydraulic-oil-level", }, DAAttributes.wash_level: { "type": Platform.SENSOR, "name": "rinse count", - "icon": "mdi:hydraulic-oil-level" + "icon": "mdi:hydraulic-oil-level", }, DAAttributes.wash_strength: { "type": Platform.SENSOR, "name": "wash strength", - "icon": "mdi:network-strength-4-cog" + "icon": "mdi:network-strength-4-cog", }, DAAttributes.softener: { "type": Platform.SENSOR, "name": "softener", - "icon": "mdi:tshirt-crew" + "icon": "mdi:tshirt-crew", }, DAAttributes.detergent: { "type": Platform.SENSOR, "name": "detergent", - "icon": "mdi:spray-bottle" + "icon": "mdi:spray-bottle", }, DAAttributes.program: { "type": Platform.SENSOR, "name": "Program", - "icon": "mdi:progress-wrench" + "icon": "mdi:progress-wrench", }, DAAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360" + "icon": "mdi:rotate-360", }, DAAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, DAAttributes.start: { "type": Platform.SWITCH, "name": "Start", - "icon": "mdi:motion-play-outline" + "icon": "mdi:motion-play-outline", }, - } + }, }, 0xDB: { "name": "Front Load Washer", @@ -1465,24 +1433,24 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, DBAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360" + "icon": "mdi:rotate-360", }, DBAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, DBAttributes.start: { "type": Platform.SWITCH, "name": "Start", - "icon": "mdi:motion-play-outline" + "icon": "mdi:motion-play-outline", }, - } + }, }, 0xDC: { "name": "Clothes Dryer", @@ -1492,24 +1460,24 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, DCAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360" + "icon": "mdi:rotate-360", }, DCAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, DCAttributes.start: { "type": Platform.SWITCH, "name": "Start", - "icon": "mdi:motion-play-outline" + "icon": "mdi:motion-play-outline", }, - } + }, }, 0xE1: { "name": "Dishwasher", @@ -1518,81 +1486,78 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR + "device_class": BinarySensorDeviceClass.DOOR, }, E1Attributes.rinse_aid: { "type": Platform.BINARY_SENSOR, "name": "Rinse Aid Shortage", "icon": "mdi:bottle-tonic", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, E1Attributes.salt: { "type": Platform.BINARY_SENSOR, "name": "Salt Shortage", "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, E1Attributes.humidity: { "type": Platform.SENSOR, "name": "Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E1Attributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360" + "icon": "mdi:rotate-360", }, E1Attributes.status: { "type": Platform.SENSOR, "name": "Status", - "icon": "mdi:information" + "icon": "mdi:information", }, E1Attributes.storage_remaining: { "type": Platform.SENSOR, "name": "Storage Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.HOURS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E1Attributes.temperature: { "type": Platform.SENSOR, "name": "Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E1Attributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT - }, - E1Attributes.child_lock: { - "type": Platform.LOCK, - "name": "Child Lock" + "state_class": SensorStateClass.MEASUREMENT, }, + E1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, E1Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, E1Attributes.storage: { "type": Platform.SWITCH, "name": "Storage", - "icon": "mdi:repeat-variant" + "icon": "mdi:repeat-variant", }, E1Attributes.mode: { "type": Platform.SENSOR, "name": "Working Mode", - "icon": "mdi:dishwasher" + "icon": "mdi:dishwasher", }, E1Attributes.error_code: { "type": Platform.SENSOR, "name": "Error Code", - "icon": "mdi:alert-box" + "icon": "mdi:alert-box", }, E1Attributes.softwater: { "type": Platform.SENSOR, @@ -1602,9 +1567,9 @@ E1Attributes.bright: { "type": Platform.SENSOR, "name": "Bright Level", - "icon": "mdi:star-four-points" - } - } + "icon": "mdi:star-four-points", + }, + }, }, 0xE2: { "name": "Electric Water Heater", @@ -1612,70 +1577,70 @@ "water_heater": { "type": Platform.WATER_HEATER, "icon": "mdi:meter-electric-outline", - "default": True + "default": True, }, E2Attributes.heating: { "type": Platform.BINARY_SENSOR, "name": "Heating", "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, E2Attributes.keep_warm: { "type": Platform.BINARY_SENSOR, "name": "Keep Warm", "icon": "mdi:menu", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, E2Attributes.protection: { "type": Platform.BINARY_SENSOR, "name": "Protection", "icon": "mdi:shield-check", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, E2Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E2Attributes.heating_time_remaining: { "type": Platform.SENSOR, "name": "Heating Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E2Attributes.heating_power: { "type": Platform.SENSOR, "name": "Heating Power", "device_class": SensorDeviceClass.POWER, "unit": UnitOfPower.WATT, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E2Attributes.water_consumption: { "type": Platform.SENSOR, "name": "Water Consumption", "icon": "mdi:water", "unit": UnitOfVolume.LITERS, - "state_class": SensorStateClass.TOTAL_INCREASING + "state_class": SensorStateClass.TOTAL_INCREASING, }, E2Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, E2Attributes.variable_heating: { "type": Platform.SWITCH, "name": "Variable Heating", - "icon": "mdi:waves" + "icon": "mdi:waves", }, E2Attributes.whole_tank_heating: { "type": Platform.SWITCH, "name": "Whole Tank Heating", - "icon": "mdi:restore" - } - } + "icon": "mdi:restore", + }, + }, }, 0xE3: { "name": "Gas Water Heater", @@ -1683,48 +1648,48 @@ "water_heater": { "type": Platform.WATER_HEATER, "icon": "mdi:meter-gas", - "default": True + "default": True, }, E3Attributes.burning_state: { "type": Platform.BINARY_SENSOR, "name": "Burning State", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, E3Attributes.protection: { "type": Platform.BINARY_SENSOR, "name": "Protection", "icon": "mdi:shield-check", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, E3Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E3Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, E3Attributes.smart_volume: { "type": Platform.SWITCH, "name": "Smart Volume", - "icon": "mdi:recycle" + "icon": "mdi:recycle", }, E3Attributes.zero_cold_water: { "type": Platform.SWITCH, "name": "Zero Cold Water", - "icon": "mdi:restore" + "icon": "mdi:restore", }, E3Attributes.zero_cold_pulse: { "type": Platform.SWITCH, "name": "Zero Cold Water (Pulse)", - "icon": "mdi:restore-alert" + "icon": "mdi:restore-alert", }, - } + }, }, 0xE6: { "name": "Gas Boilers", @@ -1734,52 +1699,52 @@ "icon": "mdi:meter-gas", "name": "Heating", "use": 0, - "default": True + "default": True, }, "water_heater_bathing": { "type": Platform.WATER_HEATER, "icon": "mdi:meter-gas", "name": "Bathing", "use": 1, - "default": True + "default": True, }, E6Attributes.heating_working: { "type": Platform.BINARY_SENSOR, "name": "Heating Working Status", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, E6Attributes.bathing_working: { "type": Platform.BINARY_SENSOR, "name": "Bathing Working Status", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, E6Attributes.heating_leaving_temperature: { "type": Platform.SENSOR, "name": "Heating Leaving Water Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E6Attributes.bathing_leaving_temperature: { "type": Platform.SENSOR, "name": "Bathing Leaving Water Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E6Attributes.main_power: { "type": Platform.SWITCH, "name": "Main Power", - "icon": "mdi:power" + "icon": "mdi:power", }, E6Attributes.heating_power: { "type": Platform.SWITCH, "name": "Heating Power", - "icon": "mdi:heating-coil" - } - } + "icon": "mdi:heating-coil", + }, + }, }, 0xE8: { "name": "Electric Slow Cooker", @@ -1793,50 +1758,49 @@ "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM + "device_class": BinarySensorDeviceClass.PROBLEM, }, E8Attributes.status: { "type": Platform.SENSOR, "name": "Status", - "icon": "mdi:information" + "icon": "mdi:information", }, E8Attributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E8Attributes.keep_warm_remaining: { "type": Platform.SENSOR, "name": "Keep Warm Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E8Attributes.working_time: { "type": Platform.SENSOR, "name": "Working Time", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E8Attributes.target_temperature: { "type": Platform.SENSOR, "name": "Target Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, E8Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, - - } + }, }, 0xEA: { "name": "Electric Rice Cooker", @@ -1845,53 +1809,53 @@ "type": Platform.BINARY_SENSOR, "name": "Cooking", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, EAAttributes.keep_warm: { "type": Platform.BINARY_SENSOR, "name": "Keep Warm", "icon": "mdi:menu", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, EAAttributes.bottom_temperature: { "type": Platform.SENSOR, "name": "Bottom Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EAAttributes.keep_warm_time: { "type": Platform.SENSOR, "name": "Keep Warm Time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EAAttributes.mode: { "type": Platform.SENSOR, "name": "Mode", - "icon": "mdi:orbit" + "icon": "mdi:orbit", }, EAAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360" + "icon": "mdi:rotate-360", }, EAAttributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EAAttributes.top_temperature: { "type": Platform.SENSOR, "name": "Top Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, - } + }, }, 0xEC: { "name": "Electric Pressure Cooker", @@ -1900,172 +1864,162 @@ "type": Platform.BINARY_SENSOR, "name": "Cooking", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, ECAttributes.with_pressure: { "type": Platform.BINARY_SENSOR, "name": "With Pressure", "icon": "mdi:information", - "device_class": BinarySensorDeviceClass.RUNNING + "device_class": BinarySensorDeviceClass.RUNNING, }, ECAttributes.bottom_temperature: { "type": Platform.SENSOR, "name": "Bottom Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, ECAttributes.keep_warm_time: { "type": Platform.SENSOR, "name": "Keep Warm Time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, ECAttributes.mode: { "type": Platform.SENSOR, "name": "Mode", - "icon": "mdi:orbit" + "icon": "mdi:orbit", }, ECAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360" + "icon": "mdi:rotate-360", }, ECAttributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, ECAttributes.top_temperature: { "type": Platform.SENSOR, "name": "Top Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, - } + }, }, 0xED: { "name": "Water Drinking Appliance", "entities": { - EDAttributes.child_lock: { - "type": Platform.LOCK, - "name": "Child Lock" - }, + EDAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, EDAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, EDAttributes.filter1: { "type": Platform.SENSOR, "name": "Filter1 Available Days", "icon": "mdi:air-filter", "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EDAttributes.filter2: { "type": Platform.SENSOR, "name": "Filter2 Available Days", "icon": "mdi:air-filter", "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EDAttributes.filter3: { "type": Platform.SENSOR, "name": "Filter3 Available Days", "icon": "mdi:air-filter", "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EDAttributes.life1: { "type": Platform.SENSOR, "name": "Filter1 Life Level", "icon": "mdi:percent", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EDAttributes.life2: { "type": Platform.SENSOR, "name": "Filter2 Life Level", "icon": "mdi:percent", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EDAttributes.life3: { "type": Platform.SENSOR, "name": "Filter3 Life Level", "icon": "mdi:percent", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EDAttributes.in_tds: { "type": Platform.SENSOR, "name": "In TDS", "icon": "mdi:water", "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EDAttributes.out_tds: { "type": Platform.SENSOR, "name": "Out TDS", "icon": "mdi:water-plus", "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, EDAttributes.water_consumption: { "type": Platform.SENSOR, "name": "Water Consumption", "icon": "mdi:water-pump", "unit": UnitOfVolume.LITERS, - "state_class": SensorStateClass.TOTAL_INCREASING - } - } + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + }, }, 0xFA: { "name": "Fan", "entities": { - "fan": { - "type": Platform.FAN, - "icon": "mdi:fan", - "default": True - }, + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, FAAttributes.oscillation_mode: { "type": Platform.SELECT, "name": "Oscillation Mode", "options": "oscillation_modes", - "icon": "mdi:swap-horizontal-variant" + "icon": "mdi:swap-horizontal-variant", }, FAAttributes.oscillation_angle: { "type": Platform.SELECT, "name": "Oscillation Angle", "options": "oscillation_angles", - "icon": "mdi:pan-horizontal" + "icon": "mdi:pan-horizontal", }, FAAttributes.tilting_angle: { "type": Platform.SELECT, "name": "Tilting Angle", "options": "tilting_angles", - "icon": "mdi:pan-vertical" - }, - FAAttributes.child_lock: { - "type": Platform.LOCK, - "name": "Child Lock" + "icon": "mdi:pan-vertical", }, + FAAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, FAAttributes.oscillate: { "type": Platform.SWITCH, "name": "Oscillate", - "icon": "mdi:swap-horizontal-bold" + "icon": "mdi:swap-horizontal-bold", }, FAAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, - } + }, }, 0xFB: { "name": "Electric Heater", @@ -2073,121 +2027,115 @@ "climate": { "type": Platform.CLIMATE, "icon": "mdi:air-conditioner", - "default": True - }, - FBAttributes.child_lock: { - "type": Platform.LOCK, - "name": "Child Lock" + "default": True, }, + FBAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, FBAttributes.heating_level: { "type": Platform.NUMBER, "name": "Heating Level", "icon": "mdi:fire", "max": 10, "min": 1, - "step": 1 + "step": 1, }, FBAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, FBAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, - } + }, }, 0xFC: { "name": "Air Purifier", "entities": { - FCAttributes.child_lock: { - "type": Platform.LOCK, - "name": "Child Lock" - }, + FCAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, FCAttributes.anion: { "type": Platform.SWITCH, "name": "Anion", - "icon": "mdi:vanish" + "icon": "mdi:vanish", }, FCAttributes.prompt_tone: { "type": Platform.SWITCH, "name": "Prompt Tone", - "icon": "mdi:bell" + "icon": "mdi:bell", }, FCAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, FCAttributes.standby: { "type": Platform.SWITCH, "name": "Standby", - "icon": "mdi:smoke-detector-variant" + "icon": "mdi:smoke-detector-variant", }, FCAttributes.detect_mode: { "type": Platform.SELECT, "name": "Detect Mode", "options": "detect_modes", - "icon": "mdi:smoke-detector-variant" + "icon": "mdi:smoke-detector-variant", }, FCAttributes.mode: { "type": Platform.SELECT, "name": "Mode", "options": "modes", - "icon": "mdi:rotate-360" + "icon": "mdi:rotate-360", }, FCAttributes.fan_speed: { "type": Platform.SELECT, "name": "Fan Speed", "options": "fan_speeds", - "icon": "mdi:fan" + "icon": "mdi:fan", }, FCAttributes.screen_display: { "type": Platform.SELECT, "name": "Screen Display", "options": "screen_displays", - "icon": "mdi:television-ambient-light" + "icon": "mdi:television-ambient-light", }, FCAttributes.pm25: { "type": Platform.SENSOR, "name": "PM 2.5", "device_class": SensorDeviceClass.PM25, "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, FCAttributes.tvoc: { "type": Platform.SENSOR, "name": "TVOC", "icon": "mdi:heat-wave", "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, FCAttributes.hcho: { "type": Platform.SENSOR, "name": "Methanal", "icon": "mdi:molecule", "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, FCAttributes.filter1_life: { "type": Platform.SENSOR, "name": "Filter1 Life Level", "icon": "mdi:air-filter", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, FCAttributes.filter2_life: { "type": Platform.SENSOR, "name": "Filter2 Life Level", "icon": "mdi:air-filter", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT - } - } + "state_class": SensorStateClass.MEASUREMENT, + }, + }, }, 0xFD: { "name": "Humidifier", @@ -2195,49 +2143,49 @@ Platform.HUMIDIFIER: { "type": Platform.HUMIDIFIER, "icon": "mdi:air-humidifier", - "default": True + "default": True, }, FDAttributes.disinfect: { "type": Platform.SWITCH, "name": "Disinfect", - "icon": "mdi:water-plus-outline" + "icon": "mdi:water-plus-outline", }, FDAttributes.prompt_tone: { "type": Platform.SWITCH, "name": "Prompt Tone", - "icon": "mdi:bell" + "icon": "mdi:bell", }, FDAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power" + "icon": "mdi:power", }, FDAttributes.fan_speed: { "type": Platform.SELECT, "name": "Fan Speed", "options": "fan_speeds", - "icon": "mdi:fan" + "icon": "mdi:fan", }, FDAttributes.screen_display: { "type": Platform.SELECT, "name": "Screen Display", "options": "screen_displays", - "icon": "mdi:television-ambient-light" + "icon": "mdi:television-ambient-light", }, FDAttributes.current_humidity: { "type": Platform.SENSOR, "name": "Current Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT + "state_class": SensorStateClass.MEASUREMENT, }, FDAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT - } - } + "state_class": SensorStateClass.MEASUREMENT, + }, + }, }, } diff --git a/custom_components/midea_ac_lan/midea_entity.py b/custom_components/midea_ac_lan/midea_entity.py index 2970811d..2b82abc9 100644 --- a/custom_components/midea_ac_lan/midea_entity.py +++ b/custom_components/midea_ac_lan/midea_entity.py @@ -1,8 +1,10 @@ +import logging + from homeassistant.helpers.entity import Entity + from .const import DOMAIN from .midea_devices import MIDEA_DEVICES -import logging _LOGGER = logging.getLogger(__name__) @@ -25,10 +27,10 @@ def device_info(self): return { "manufacturer": "Midea", "model": f"{MIDEA_DEVICES[self._device.device_type]['name']} " - f"{self._device.model}" - f" ({self._device.subtype})", + f"{self._device.model}" + f" ({self._device.subtype})", "identifiers": {(DOMAIN, self._device.device_id)}, - "name": self._device_name + "name": self._device_name, } @property @@ -41,8 +43,11 @@ def should_poll(self): @property def name(self): - return f"{self._device_name} {self._config.get('name')}" if "name" in self._config \ + return ( + f"{self._device_name} {self._config.get('name')}" + if "name" in self._config else self._device_name + ) @property def available(self): @@ -57,4 +62,6 @@ def update_state(self, status): try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) diff --git a/custom_components/midea_ac_lan/number.py b/custom_components/midea_ac_lan/number.py index 360e7a5c..32ab1ad3 100644 --- a/custom_components/midea_ac_lan/number.py +++ b/custom_components/midea_ac_lan/number.py @@ -1,23 +1,15 @@ -from .midea_entity import MideaEntity -from .midea_devices import MIDEA_DEVICES from homeassistant.components.number import NumberEntity -from homeassistant.const import ( - Platform, - CONF_DEVICE_ID, - CONF_SWITCHES -) -from .const import ( - DOMAIN, - DEVICES, -) +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get( - CONF_SWITCHES, [] - ) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) numbers = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.NUMBER and entity_key in extra_switches: @@ -35,24 +27,39 @@ def __init__(self, device, entity_key: str): @property def native_min_value(self): - return self._min_value if isinstance(self._min_value, int) else \ - self._device.get_attribute(attr=self._min_value) \ - if self._device.get_attribute(attr=self._min_value) else \ - getattr(self._device, self._min_value) + return ( + self._min_value + if isinstance(self._min_value, int) + else ( + self._device.get_attribute(attr=self._min_value) + if self._device.get_attribute(attr=self._min_value) + else getattr(self._device, self._min_value) + ) + ) @property def native_max_value(self): - return self._max_value if isinstance(self._max_value, int) else \ - self._device.get_attribute(attr=self._max_value) \ - if self._device.get_attribute(attr=self._max_value) else \ - getattr(self._device, self._max_value) + return ( + self._max_value + if isinstance(self._max_value, int) + else ( + self._device.get_attribute(attr=self._max_value) + if self._device.get_attribute(attr=self._max_value) + else getattr(self._device, self._max_value) + ) + ) @property def native_step(self): - return self._step_value if isinstance(self._step_value, int) else \ - self._device.get_attribute(attr=self._step_value) \ - if self._device.get_attribute(attr=self._step_value) else \ - getattr(self._device, self._step_value) + return ( + self._step_value + if isinstance(self._step_value, int) + else ( + self._device.get_attribute(attr=self._step_value) + if self._device.get_attribute(attr=self._step_value) + else getattr(self._device, self._step_value) + ) + ) @property def native_value(self): diff --git a/custom_components/midea_ac_lan/select.py b/custom_components/midea_ac_lan/select.py index b9336690..9ca0430c 100644 --- a/custom_components/midea_ac_lan/select.py +++ b/custom_components/midea_ac_lan/select.py @@ -1,23 +1,15 @@ -from .midea_entity import MideaEntity -from .midea_devices import MIDEA_DEVICES from homeassistant.components.select import SelectEntity -from homeassistant.const import ( - Platform, - CONF_DEVICE_ID, - CONF_SWITCHES -) -from .const import ( - DOMAIN, - DEVICES, -) +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get( - CONF_SWITCHES, [] - ) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) selects = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.SELECT and entity_key in extra_switches: diff --git a/custom_components/midea_ac_lan/sensor.py b/custom_components/midea_ac_lan/sensor.py index 77d524de..ed088358 100644 --- a/custom_components/midea_ac_lan/sensor.py +++ b/custom_components/midea_ac_lan/sensor.py @@ -1,23 +1,15 @@ -from .midea_entity import MideaEntity -from .midea_devices import MIDEA_DEVICES from homeassistant.components.sensor import SensorEntity -from homeassistant.const import ( - Platform, - CONF_DEVICE_ID, - CONF_SENSORS -) -from .const import ( - DOMAIN, - DEVICES -) +from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_sensors = config_entry.options.get( - CONF_SENSORS, [] - ) + extra_sensors = config_entry.options.get(CONF_SENSORS, []) sensors = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.SENSOR and entity_key in extra_sensors: diff --git a/custom_components/midea_ac_lan/services.yaml b/custom_components/midea_ac_lan/services.yaml index bfb99b49..20bc9bdc 100644 --- a/custom_components/midea_ac_lan/services.yaml +++ b/custom_components/midea_ac_lan/services.yaml @@ -1,3 +1,4 @@ +--- set_attribute: fields: device_id: diff --git a/custom_components/midea_ac_lan/switch.py b/custom_components/midea_ac_lan/switch.py index e57042bf..b312edfb 100644 --- a/custom_components/midea_ac_lan/switch.py +++ b/custom_components/midea_ac_lan/switch.py @@ -1,23 +1,15 @@ -from .midea_entity import MideaEntity -from .midea_devices import MIDEA_DEVICES +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform from homeassistant.helpers.entity import ToggleEntity -from homeassistant.const import ( - Platform, - CONF_DEVICE_ID, - CONF_SWITCHES -) -from .const import ( - DOMAIN, - DEVICES, -) + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get( - CONF_SWITCHES, [] - ) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) switches = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.SWITCH and entity_key in extra_switches: diff --git a/custom_components/midea_ac_lan/water_heater.py b/custom_components/midea_ac_lan/water_heater.py index eea9bbcc..13d2a9e6 100644 --- a/custom_components/midea_ac_lan/water_heater.py +++ b/custom_components/midea_ac_lan/water_heater.py @@ -1,31 +1,29 @@ import functools as ft +import logging from homeassistant.components.water_heater import ( WaterHeaterEntity, WaterHeaterEntityFeature, ) from homeassistant.const import ( - Platform, - UnitOfTemperature, - PRECISION_WHOLE, - PRECISION_HALVES, ATTR_TEMPERATURE, CONF_DEVICE_ID, CONF_SWITCHES, - STATE_ON, + PRECISION_HALVES, + PRECISION_WHOLE, STATE_OFF, + STATE_ON, + Platform, + UnitOfTemperature, ) -from .const import ( - DOMAIN, - DEVICES -) -from .midea.devices.e6.device import DeviceAttributes as E6Attributes + +from .const import DEVICES, DOMAIN from .midea.devices.c3.device import DeviceAttributes as C3Attributes from .midea.devices.cd.device import DeviceAttributes as CDAttributes +from .midea.devices.e6.device import DeviceAttributes as E6Attributes from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity -import logging _LOGGER = logging.getLogger(__name__) E2_TEMPERATURE_MAX = 75 @@ -37,12 +35,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get( - CONF_SWITCHES, [] - ) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.WATER_HEATER and (config.get("default") or entity_key in extra_switches): + if config["type"] == Platform.WATER_HEATER and ( + config.get("default") or entity_key in extra_switches + ): if device.device_type == 0xE2: devs.append(MideaE2WaterHeater(device, entity_key)) elif device.device_type == 0xE3: @@ -98,7 +96,11 @@ def temperature_unit(self): @property def current_operation(self): - return self._device.get_attribute("mode") if self._device.get_attribute("power") else STATE_OFF + return ( + self._device.get_attribute("mode") + if self._device.get_attribute("power") + else STATE_OFF + ) @property def current_temperature(self): @@ -137,7 +139,9 @@ def update_state(self, status): try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) class MideaE2WaterHeater(MideaWaterHeater): @@ -176,7 +180,11 @@ def __init__(self, device, entity_key): @property def state(self): - return STATE_ON if self._device.get_attribute(C3Attributes.dhw_power) else STATE_OFF + return ( + STATE_ON + if self._device.get_attribute(C3Attributes.dhw_power) + else STATE_OFF + ) @property def current_temperature(self): @@ -225,20 +233,28 @@ def __init__(self, device, entity_key, use): super().__init__(device, entity_key) self._use = use self._power_attr = MideaE6WaterHeater._powers[self._use] - self._current_temperature_attr = MideaE6WaterHeater._current_temperatures[self._use] - self._target_temperature_attr = MideaE6WaterHeater._target_temperatures[self._use] + self._current_temperature_attr = MideaE6WaterHeater._current_temperatures[ + self._use + ] + self._target_temperature_attr = MideaE6WaterHeater._target_temperatures[ + self._use + ] @property def state(self): if self._use == 0: # for heating - return STATE_ON if \ - self._device.get_attribute(E6Attributes.main_power) and \ - self._device.get_attribute(E6Attributes.heating_power) \ + return ( + STATE_ON + if self._device.get_attribute(E6Attributes.main_power) + and self._device.get_attribute(E6Attributes.heating_power) else STATE_OFF + ) else: # for bathing - return STATE_ON if \ - self._device.get_attribute(E6Attributes.main_power) \ + return ( + STATE_ON + if self._device.get_attribute(E6Attributes.main_power) else STATE_OFF + ) @property def current_temperature(self): @@ -275,8 +291,10 @@ def __init__(self, device, entity_key): @property def supported_features(self): - return WaterHeaterEntityFeature.TARGET_TEMPERATURE | \ - WaterHeaterEntityFeature.OPERATION_MODE + return ( + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.OPERATION_MODE + ) @property def min_temp(self): diff --git a/scripts/run.sh b/scripts/run.sh index 1c529a66..035649a3 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -6,8 +6,8 @@ cd "$(dirname "$0")/.." # Create config dir if not present if [[ ! -d "${PWD}/config" ]]; then - mkdir -p "${PWD}/config" - hass --config "${PWD}/config" --script ensure_config + mkdir -p "${PWD}/config" + hass --config "${PWD}/config" --script ensure_config fi # Set the path to custom_components From 03802ab582b6d099214d82a5e8a808c9378dedff Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Tue, 14 May 2024 15:43:46 +0800 Subject: [PATCH 06/14] update with superlinter error --- custom_components/midea_ac_lan/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py index f3ac5a6f..795a11dd 100644 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -58,10 +58,14 @@ async def update_listener(hass, config_entry): dev.set_refresh_interval(refresh_interval) -async def async_setup(hass: HomeAssistant): +async def async_setup(hass: HomeAssistant, config: dict): """ async_setup """ + if config.get(DOMAIN) is None: + # We get her if the integration is set up using config flow + return True + hass.data.setdefault(DOMAIN, {}) attributes = [] for device_entities in MIDEA_DEVICES.values(): From f51e66ecda302eb8d9473637fc3e336a6b1a7645 Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Tue, 14 May 2024 16:08:57 +0800 Subject: [PATCH 07/14] convert CRLF to LF for all the files --- LICENSE | 2 +- README.md | 350 +- README_hans.md | 332 +- custom_components/midea_ac_lan/__init__.py | 452 +- .../midea_ac_lan/binary_sensor.py | 90 +- custom_components/midea_ac_lan/climate.py | 1612 +++--- custom_components/midea_ac_lan/config_flow.py | 1240 ++--- custom_components/midea_ac_lan/const.py | 50 +- custom_components/midea_ac_lan/fan.py | 724 +-- custom_components/midea_ac_lan/humidifier.py | 218 +- custom_components/midea_ac_lan/lock.py | 68 +- custom_components/midea_ac_lan/manifest.json | 34 +- .../midea_ac_lan/midea/backports/enum.py | 70 +- .../midea_ac_lan/midea/core/cloud.py | 1334 ++--- .../midea_ac_lan/midea/core/crc8.py | 540 +- .../midea_ac_lan/midea/core/device.py | 846 ++-- .../midea_ac_lan/midea/core/discover.py | 616 +-- .../midea_ac_lan/midea/core/message.py | 544 +- .../midea_ac_lan/midea/core/packet_builder.py | 188 +- .../midea_ac_lan/midea/core/security.py | 528 +- .../midea_ac_lan/midea/devices/__init__.py | 100 +- .../midea_ac_lan/midea/devices/a1/device.py | 368 +- .../midea_ac_lan/midea/devices/a1/message.py | 444 +- .../midea_ac_lan/midea/devices/ac/device.py | 750 +-- .../midea_ac_lan/midea/devices/ac/message.py | 1442 +++--- .../midea_ac_lan/midea/devices/b6/device.py | 340 +- .../midea_ac_lan/midea/devices/b6/message.py | 438 +- .../midea_ac_lan/midea/devices/c3/device.py | 576 +-- .../midea_ac_lan/midea/devices/c3/message.py | 370 +- .../midea_ac_lan/midea/devices/ca/device.py | 202 +- .../midea_ac_lan/midea/devices/ca/message.py | 242 +- .../midea_ac_lan/midea/devices/cc/device.py | 400 +- .../midea_ac_lan/midea/devices/cc/message.py | 286 +- .../midea_ac_lan/midea/devices/ce/message.py | 270 +- .../midea_ac_lan/midea/devices/cf/device.py | 200 +- .../midea_ac_lan/midea/devices/cf/message.py | 176 +- .../midea_ac_lan/midea/devices/da/device.py | 342 +- .../midea_ac_lan/midea/devices/da/message.py | 202 +- .../midea_ac_lan/midea/devices/db/device.py | 194 +- .../midea_ac_lan/midea/devices/db/message.py | 226 +- .../midea_ac_lan/midea/devices/dc/device.py | 192 +- .../midea_ac_lan/midea/devices/dc/message.py | 178 +- .../midea_ac_lan/midea/devices/e1/device.py | 344 +- .../midea_ac_lan/midea/devices/e1/message.py | 244 +- .../midea_ac_lan/midea/devices/e2/device.py | 282 +- .../midea_ac_lan/midea/devices/e2/message.py | 300 +- .../midea_ac_lan/midea/devices/e3/device.py | 284 +- .../midea_ac_lan/midea/devices/e3/message.py | 346 +- .../midea_ac_lan/midea/devices/ea/device.py | 394 +- .../midea_ac_lan/midea/devices/ea/message.py | 232 +- .../midea_ac_lan/midea/devices/ec/device.py | 422 +- .../midea_ac_lan/midea/devices/ec/message.py | 148 +- .../midea_ac_lan/midea/devices/ed/device.py | 206 +- .../midea_ac_lan/midea/devices/ed/message.py | 400 +- .../midea_ac_lan/midea/devices/fa/device.py | 654 +-- .../midea_ac_lan/midea/devices/fa/message.py | 394 +- .../midea_ac_lan/midea/devices/fb/device.py | 218 +- .../midea_ac_lan/midea/devices/fb/message.py | 268 +- .../midea_ac_lan/midea/devices/fc/device.py | 476 +- .../midea_ac_lan/midea/devices/fc/message.py | 418 +- .../midea_ac_lan/midea/devices/fd/device.py | 414 +- .../midea_ac_lan/midea/devices/fd/message.py | 342 +- .../midea_ac_lan/midea_devices.py | 4382 ++++++++--------- .../midea_ac_lan/midea_entity.py | 134 +- custom_components/midea_ac_lan/number.py | 138 +- custom_components/midea_ac_lan/select.py | 70 +- custom_components/midea_ac_lan/sensor.py | 80 +- custom_components/midea_ac_lan/services.yaml | 36 +- custom_components/midea_ac_lan/switch.py | 60 +- .../midea_ac_lan/translations/en.json | 238 +- .../midea_ac_lan/translations/zh-Hans.json | 236 +- .../midea_ac_lan/water_heater.py | 610 +-- doc/A1.md | 140 +- doc/A1_hans.md | 140 +- doc/AC.md | 224 +- doc/AC_hans.md | 210 +- doc/B6.md | 66 +- doc/B6_hans.md | 66 +- doc/C3.md | 136 +- doc/C3_hans.md | 136 +- doc/CA.md | 58 +- doc/CA_hans.md | 58 +- doc/CC.md | 102 +- doc/CC_hans.md | 104 +- doc/CF.md | 84 +- doc/CF_hans.md | 82 +- doc/DA.md | 74 +- doc/DA_hans.md | 74 +- doc/DB.md | 74 +- doc/DB_hans.md | 72 +- doc/DC.md | 74 +- doc/DC_hans.md | 72 +- doc/E1.md | 98 +- doc/E1_hans.md | 98 +- doc/E2.md | 124 +- doc/E2_hans.md | 110 +- doc/E3.md | 118 +- doc/E3_hans.md | 118 +- doc/EA.md | 48 +- doc/EA_hans.md | 48 +- doc/EC.md | 48 +- doc/EC_hans.md | 48 +- doc/ED.md | 54 +- doc/ED_hans.md | 54 +- doc/FA.md | 170 +- doc/FA_hans.md | 170 +- doc/FC.md | 180 +- doc/FC_hans.md | 178 +- doc/FD.md | 142 +- doc/FD_hans.md | 142 +- 110 files changed, 17270 insertions(+), 17270 deletions(-) diff --git a/LICENSE b/LICENSE index 0387ca43..0401ec35 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -MIT License +# MIT License Copyright (c) 2021 George Zhao diff --git a/README.md b/README.md index 0539c9ad..9e220823 100644 --- a/README.md +++ b/README.md @@ -1,175 +1,175 @@ -# Midea AC LAN - -[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) -[![Donate](https://img.shields.io/badge/donate-BuyMeCoffee-yellow.svg)](https://www.buymeacoffee.com/georgezhao2010) -[![Stable](https://img.shields.io/github/v/release/CyrielRct/midea_ac_lan)](https://github.com/CyrielRct/midea_ac_lan/releases/latest) -[![Super-Linter](https://github.com///actions/workflows//badge.svg)](https://github.com/marketplace/actions/super-linter) - -> :warning: **This is a fork of midea_ac_lan done by Georgezhao**: As the project is in a vegetative state we have done a fork and merged some pending fixes. -I'm trying to get in touch with the maintainer at the moment to find a solution, and at the same time we're looking for people available to maintain the project, which is actively used by the community. please contact me if you can help ! - -English | [简体中文](README_hans.md) - -Control your Midea M-Smart appliances via local area network. - -- Automated device discover and configuration based Home Assistant config flow UI. -- Extra sensors and switches. -- Synchronize status with the appliance by long TCP connection in time. - -This component inspired from the repository at [@mac-zhou](https://github.com/mac-zhou/midea-msmart) which provides similar functionality for Midea air conditioners. This component include verbatim or adapted portions of the code from his great projects. - -Thanks also to [@NeoAcheron](https://github.com/NeoAcheron/midea-ac-py). - -⭐If this component is helpful for you, please star it, it encourages me a lot. - -***❗Note: Home Assistant 2023.1 or higher required for this integration*** - -## Supported brands - -![ariston](brands/ariston.png) ![beverly](brands/beverly.png) ![bugu](brands/bugu.png) \ -![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) \ -![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) \ -![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) \ -![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) \ -![wahin](brands/wahin.png) - -And more. - -## Supported appliances - -| Type | Name | Documents | -|------|----------------------------|--------------------| -| 13 | Light | [13.md](doc/13.md) | -| 26 | Bathroom Master | [26.md](doc/26.md) | -| 34 | Sink Dishwasher | [34.md](doc/34.md) | -| 40 | Integrated Ceiling Fan | [40.md](doc/40.md) | -| A1 | Dehumidifier | [A1.md](doc/A1.md) | -| AC | Air Conditioner | [AC.md](doc/AC.md) | -| B0 | Microwave Oven | [B0.md](doc/B0.md) | -| B1 | Electric Oven | [B1.md](doc/B1.md) | -| B3 | Dish Sterilizer | [B3.md](doc/B3.md) | -| B4 | Toaster | [B4.md](doc/B4.md) | -| B6 | Range Hood | [B6.md](doc/B6.md) | -| BF | Microwave Steam Oven | [BF.md](doc/BF.md) | -| C2 | Toilet | [C2.md](doc/C2.md) | -| C3 | Heat Pump Wi-Fi Controller | [C3.md](doc/C3.md) | -| CA | Refrigerator | [CA.md](doc/CA.md) | -| CC | MDV Wi-Fi Controller | [CC.md](doc/CC.md) | -| CD | Heat Pump Water Heater | [CC.md](doc/CD.md) | -| CE | Fresh Air Appliance | [CE.md](doc/CE.md) | -| CF | Heat Pump | [CF.md](doc/CF.md) | -| DA | Top Load Washer | [DA.md](doc/DA.md) | -| DB | Front Load Washer | [DB.md](doc/DB.md) | -| DC | Clothes Dryer | [DC.md](doc/DC.md) | -| E1 | Dishwasher | [E1.md](doc/E1.md) | -| E2 | Electric Water Heater | [E2.md](doc/E2.md) | -| E3 | Gas Water Heater | [E3.md](doc/E3.md) | -| E6 | Gas Stove | [E6.md](doc/E6.md) | -| E8 | Electric Slow Cooker | [E8.md](doc/E8.md) | -| EA | Electric Rice Cooker | [EA.md](doc/EA.md) | -| EC | Electric Pressure Cooker | [EC.md](doc/EC.md) | -| ED | Water Drinking Appliance | [ED.md](doc/ED.md) | -| FA | Fan | [FA.md](doc/FA.md) | -| FB | Electric Heater | [FB.md](doc/FB.md) | -| FC | Air Purifier | [FC.md](doc/FC.md) | -| FD | Humidifier | [FD.md](doc/FD.md) | - -## Installation - -Search 'Midea AC LAN' in HACS and install, or copy all files in `custom_components/midea_ac_lan` from [Latest Release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest) to your `/custom_components/midea_ac_lan` in Home Assistant manually. - -Restart Home Assistant. - -## Add device - -***❗Note: First, set a static IP address for your appliance in the router, in case the IP address of the appliance changes after set-up.*** - -After installation, search and add component Midea AC LAN in Home Assistant integrations page. - -Or click [![Configuration](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start?domain=midea_ac_lan) - -***❗Note: During the configuration process, you may be asked to enter your Midea account and password. It's necessary to retrieve appliance information (Token and Key) from Midea cloud server. After all appliances configured, you can remove the Midea account configuration without affecting the use of the appliance.*** - -After the account is configured, Click 'ADD DEVICE' once more to add new device. You could repeat the above action to add multiple devices. - -### Discover automatically - -Using this option, the component could auto-discover and list Midea M-Smart appliances in network or specified IP address, select one and add it in. - -You can also use an IP address to search within a specified network, such as `192.168.1.255`. - -***❗Note: Discovery automatically requires your appliances and your Home Assistant must be in the same sub-network. Otherwise, devices may not be auto-discovered. Check this by yourself.*** - -### Configure manually - -If you already know following information, you could add the appliance manually. - -- Appliance code -- Appliance type (one of [Supported appliances](README.md#supported-appliances)) -- IP address -- Port (default 6444) -- Protocol version -- Token -- Key - -### List all appliances only - -Using this option, you can list all discoverable Midea M-Smart devices on the network, along with their IDs, types, SNs, and other information. - -***❗Note: For certain reasons, not all supported devices may be listed here.*** - -## Configure - -Configure can be found in `Settings -> Devices & Services -> Midea AC LAN -> Devices -> CONFIGURE`. -You can re-set the IP address when device IP changed. -You can also add extra sensor and switch entities or customize your own device. - -### IP address - -Set the IP address of device. You can reset this when your device IP is changed. - -### Refresh interval - -Set the interval for actively refreshing the status of a single device (the unit is second) (30 by default and 0 means not refresh actively) -Mostly the status update of Midea devices relies on the active information notification of the device, \ -in which condition the status update in HA still works normally even if the refresh interval is set to be "0". \ -This component will also actively query the device status at regular intervals, and the default time is 30 seconds. \ -Some devices do not have active information notifications when their status changed, so synchronization with the status in HA will be slower. \ -If you are very concerned about the synchronization speed of the status, you can try to set a shorter status refresh interval. - -***❗Note: shorter refresh interval may mean more power consumption*** - -### Extra sensor and switch entities - -After configuration, one of few main entity (e.g. climate entity) may be generated . If you want to make the attributes to extra sensor and switch entities, click CONFIGURE in Midea AC LAN integration card to choose (if your devices supported). - -### Customize - -Some types of device have their own configuration items, if your device does not work properly, you may need to customize it. Refer to the device documentation for specific information. - -The format of customizations must be JSON. - -If multiple customization items need to be configured, the settings must comply with the JSON format. - -Example - -```json -{"refresh_interval": 15, "fan_speed": 100} -``` - -## Debug - -Turn on the debug log out,config in configuration.yaml - -```yaml -logger: - default: warn - logs: - custom_components.midea_ac_lan: debug -``` - -## Support my works - -If you like this integration, why do not you support my works by buying me a coffee? - -[![Buy me a coffee](https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png)](https://www.buymeacoffee.com/georgezhao2010) +# Midea AC LAN + +[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) +[![Donate](https://img.shields.io/badge/donate-BuyMeCoffee-yellow.svg)](https://www.buymeacoffee.com/georgezhao2010) +[![Stable](https://img.shields.io/github/v/release/CyrielRct/midea_ac_lan)](https://github.com/CyrielRct/midea_ac_lan/releases/latest) +[![Super-Linter](https://github.com///actions/workflows//badge.svg)](https://github.com/marketplace/actions/super-linter) + +> :warning: **This is a fork of midea_ac_lan done by Georgezhao**: As the project is in a vegetative state we have done a fork and merged some pending fixes. +I'm trying to get in touch with the maintainer at the moment to find a solution, and at the same time we're looking for people available to maintain the project, which is actively used by the community. please contact me if you can help ! + +English | [简体中文](README_hans.md) + +Control your Midea M-Smart appliances via local area network. + +- Automated device discover and configuration based Home Assistant config flow UI. +- Extra sensors and switches. +- Synchronize status with the appliance by long TCP connection in time. + +This component inspired from the repository at [@mac-zhou](https://github.com/mac-zhou/midea-msmart) which provides similar functionality for Midea air conditioners. This component include verbatim or adapted portions of the code from his great projects. + +Thanks also to [@NeoAcheron](https://github.com/NeoAcheron/midea-ac-py). + +⭐If this component is helpful for you, please star it, it encourages me a lot. + +***❗Note: Home Assistant 2023.1 or higher required for this integration*** + +## Supported brands + +![ariston](brands/ariston.png) ![beverly](brands/beverly.png) ![bugu](brands/bugu.png) \ +![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) \ +![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) \ +![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) \ +![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) \ +![wahin](brands/wahin.png) + +And more. + +## Supported appliances + +| Type | Name | Documents | +|------|----------------------------|--------------------| +| 13 | Light | [13.md](doc/13.md) | +| 26 | Bathroom Master | [26.md](doc/26.md) | +| 34 | Sink Dishwasher | [34.md](doc/34.md) | +| 40 | Integrated Ceiling Fan | [40.md](doc/40.md) | +| A1 | Dehumidifier | [A1.md](doc/A1.md) | +| AC | Air Conditioner | [AC.md](doc/AC.md) | +| B0 | Microwave Oven | [B0.md](doc/B0.md) | +| B1 | Electric Oven | [B1.md](doc/B1.md) | +| B3 | Dish Sterilizer | [B3.md](doc/B3.md) | +| B4 | Toaster | [B4.md](doc/B4.md) | +| B6 | Range Hood | [B6.md](doc/B6.md) | +| BF | Microwave Steam Oven | [BF.md](doc/BF.md) | +| C2 | Toilet | [C2.md](doc/C2.md) | +| C3 | Heat Pump Wi-Fi Controller | [C3.md](doc/C3.md) | +| CA | Refrigerator | [CA.md](doc/CA.md) | +| CC | MDV Wi-Fi Controller | [CC.md](doc/CC.md) | +| CD | Heat Pump Water Heater | [CC.md](doc/CD.md) | +| CE | Fresh Air Appliance | [CE.md](doc/CE.md) | +| CF | Heat Pump | [CF.md](doc/CF.md) | +| DA | Top Load Washer | [DA.md](doc/DA.md) | +| DB | Front Load Washer | [DB.md](doc/DB.md) | +| DC | Clothes Dryer | [DC.md](doc/DC.md) | +| E1 | Dishwasher | [E1.md](doc/E1.md) | +| E2 | Electric Water Heater | [E2.md](doc/E2.md) | +| E3 | Gas Water Heater | [E3.md](doc/E3.md) | +| E6 | Gas Stove | [E6.md](doc/E6.md) | +| E8 | Electric Slow Cooker | [E8.md](doc/E8.md) | +| EA | Electric Rice Cooker | [EA.md](doc/EA.md) | +| EC | Electric Pressure Cooker | [EC.md](doc/EC.md) | +| ED | Water Drinking Appliance | [ED.md](doc/ED.md) | +| FA | Fan | [FA.md](doc/FA.md) | +| FB | Electric Heater | [FB.md](doc/FB.md) | +| FC | Air Purifier | [FC.md](doc/FC.md) | +| FD | Humidifier | [FD.md](doc/FD.md) | + +## Installation + +Search 'Midea AC LAN' in HACS and install, or copy all files in `custom_components/midea_ac_lan` from [Latest Release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest) to your `/custom_components/midea_ac_lan` in Home Assistant manually. + +Restart Home Assistant. + +## Add device + +***❗Note: First, set a static IP address for your appliance in the router, in case the IP address of the appliance changes after set-up.*** + +After installation, search and add component Midea AC LAN in Home Assistant integrations page. + +Or click [![Configuration](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start?domain=midea_ac_lan) + +***❗Note: During the configuration process, you may be asked to enter your Midea account and password. It's necessary to retrieve appliance information (Token and Key) from Midea cloud server. After all appliances configured, you can remove the Midea account configuration without affecting the use of the appliance.*** + +After the account is configured, Click 'ADD DEVICE' once more to add new device. You could repeat the above action to add multiple devices. + +### Discover automatically + +Using this option, the component could auto-discover and list Midea M-Smart appliances in network or specified IP address, select one and add it in. + +You can also use an IP address to search within a specified network, such as `192.168.1.255`. + +***❗Note: Discovery automatically requires your appliances and your Home Assistant must be in the same sub-network. Otherwise, devices may not be auto-discovered. Check this by yourself.*** + +### Configure manually + +If you already know following information, you could add the appliance manually. + +- Appliance code +- Appliance type (one of [Supported appliances](README.md#supported-appliances)) +- IP address +- Port (default 6444) +- Protocol version +- Token +- Key + +### List all appliances only + +Using this option, you can list all discoverable Midea M-Smart devices on the network, along with their IDs, types, SNs, and other information. + +***❗Note: For certain reasons, not all supported devices may be listed here.*** + +## Configure + +Configure can be found in `Settings -> Devices & Services -> Midea AC LAN -> Devices -> CONFIGURE`. +You can re-set the IP address when device IP changed. +You can also add extra sensor and switch entities or customize your own device. + +### IP address + +Set the IP address of device. You can reset this when your device IP is changed. + +### Refresh interval + +Set the interval for actively refreshing the status of a single device (the unit is second) (30 by default and 0 means not refresh actively) +Mostly the status update of Midea devices relies on the active information notification of the device, \ +in which condition the status update in HA still works normally even if the refresh interval is set to be "0". \ +This component will also actively query the device status at regular intervals, and the default time is 30 seconds. \ +Some devices do not have active information notifications when their status changed, so synchronization with the status in HA will be slower. \ +If you are very concerned about the synchronization speed of the status, you can try to set a shorter status refresh interval. + +***❗Note: shorter refresh interval may mean more power consumption*** + +### Extra sensor and switch entities + +After configuration, one of few main entity (e.g. climate entity) may be generated . If you want to make the attributes to extra sensor and switch entities, click CONFIGURE in Midea AC LAN integration card to choose (if your devices supported). + +### Customize + +Some types of device have their own configuration items, if your device does not work properly, you may need to customize it. Refer to the device documentation for specific information. + +The format of customizations must be JSON. + +If multiple customization items need to be configured, the settings must comply with the JSON format. + +Example + +```json +{"refresh_interval": 15, "fan_speed": 100} +``` + +## Debug + +Turn on the debug log out,config in configuration.yaml + +```yaml +logger: + default: warn + logs: + custom_components.midea_ac_lan: debug +``` + +## Support my works + +If you like this integration, why do not you support my works by buying me a coffee? + +[![Buy me a coffee](https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png)](https://www.buymeacoffee.com/georgezhao2010) diff --git a/README_hans.md b/README_hans.md index 5e1ae2bd..407c6ccf 100644 --- a/README_hans.md +++ b/README_hans.md @@ -1,166 +1,166 @@ -# Midea AC LAN - -[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) -[![Donate](https://img.shields.io/badge/donate-BuyMeCoffee-yellow.svg)](https://www.buymeacoffee.com/georgezhao2010) -[![Stable](https://img.shields.io/github/v/release/CyrielRct/midea_ac_lan)](https://github.com/CyrielRct/midea_ac_lan/releases/latest) - -[English](README.md) | 简体中文 - -通过本地局域网控制你的美的M-Smart设备 - -- 通过Home Assistant UI完成设备的自动搜索和配置. -- 生成额外的传感器和开关方便进行设备控制. -- 与设备保持TCP长连接以便实时同步设备状态. - -本集成部分技术来源来自 [@mac-zhou](https://github.com/mac-zhou/midea-msmart), 他的美的midea-msmart提供了类似的功能。 该组件包括来自他的项目中经过改写的部分代码。 - -同时感谢[@NeoAcheron](https://github.com/NeoAcheron/midea-ac-py) - -⭐如果本集成对你有所帮助, 请不吝为它点个星, 这将是对我的极大激励。 - -***❗注意: 本集成需要Home Assistant 2023.1或更高版本*** - -## 已支持的品牌 - -![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) \ -![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) \ -![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) \ -![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) \ -![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) - -以及更多。 - -## 已支持的设备 - -| 类型 | 名称 | 文档 | -|----|-------------------|------------------------------| -| 13 | 灯 | [13_hans.md](doc/13_hans.md) | -| 26 | 浴霸 | [26_hans.md](doc/26_hans.md) | -| 34 | 水槽式洗碗机 | [34_hans.md](doc/34_hans.md) | -| 40 | 凉霸 | [40_hans.md](doc/40_hans.md) | -| A1 | 除湿器 | [A1_hans.md](doc/A1_hans.md) | -| AC | 空调器 | [AC_hans.md](doc/AC_hans.md) | -| B0 | 微波炉 | [B0_hans.md](doc/B0_hans.md) | -| B1 | 电烤箱 | [B1_hans.md](doc/B1_hans.md) | -| B3 | 消毒碗柜 | [B3_hans.md](doc/B3_hans.md) | -| B4 | 小烤箱 | [B4_hans.md](doc/B4_hans.md) | -| B6 | 油烟机 | [B6_hans.md](doc/B6_hans.md) | -| BF | 微蒸烤一体机 | [BF_hans.md](doc/BF_hans.md) | -| C2 | 智能马桶 | [C2_hans.md](doc/C2_hans.md) | -| C3 | 热泵空调Wi-Fi线控器 | [C3_hans.md](doc/C3_hans.md) | -| CA | 冰箱 | [CA_hans.md](doc/CA_hans.md) | -| CC | 中央空调(风管机)Wi-Fi线控器 | [CC_hans.md](doc/CC_hans.md) | -| CD | 空气能热水器 | [CD_hans.md](doc/CD_hans.md) | -| CE | 新风设备 | [CE_hans.md](doc/CE_hans.md) | -| CF | 中央空调暖家(水机) | [CF_hans.md](doc/CF_hans.md) | -| DA | 波轮洗衣机 | [DA_hans.md](doc/DA_hans.md) | -| DB | 滚筒洗衣机 | [DB_hans.md](doc/DB_hans.md) | -| DC | 干衣机 | [DC_hans.md](doc/DC_hans.md) | -| E1 | 洗碗机 | [E1_hans.md](doc/E1_hans.md) | -| E2 | 电热水器 | [E2_hans.md](doc/E2_hans.md) | -| E3 | 燃气热水器 | [E3_hans.md](doc/E3_hans.md) | -| E6 | 壁挂炉 | [E6_hans.md](doc/E6_hans.md) | -| E8 | 慢炖锅 | [E8_hans.md](doc/E8_hans.md) | -| EA | 电饭煲 | [EA_hans.md](doc/EA_hans.md) | -| EC | 电压力锅 | [EC_hans.md](doc/EC_hans.md) | -| ED | 饮用水设备 | [ED_hans.md](doc/ED_hans.md) | -| FA | 电风扇 | [FA_hans.md](doc/FA_hans.md) | -| FB | 电取暖器 | [FB_hans.md](doc/FB_hans.md) | -| FC | 空气净化器 | [FC_hans.md](doc/FC_hans.md) | -| FD | 加湿器 | [FD_hans.md](doc/FD_hans.md) | - -## 安装 - -在HACS中搜索'Midea AC LAN'并安装, 或者从[Latest release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest)下载最新的Release版本, 将其中的`custom_components/midea_ac_lan`放到你的Home Assistant的`custom_components/midea_ac_lan`中。 - -重启Home Assistant - -## 添加设备 - -***❗注意: 首先, 在路由器上为你的设备设置一个静态IP地址, 以防设置后设备的IP地址发生改变。*** - -安装之后, 在Home Assistant的集成界面搜索添加集成Midea AC LAN, 如果需要添加多台设备, 多次添加本集成并执行自动配置即可。 - -或者直接点击 [![Configuration](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start?domain=midea_ac_lan) - -***❗注意: 配置过程中, 可能会要求输入你的美的账号及密码, 这是因为需要去美的云服务器获取设备的信息 (Token and Key)。在完成所有设备配置后, 可以删除美的账户配置, 不影响设备的使用*** - -完成美的账户配置之后, 点击'添加设备'进行设备添加。你可以多次重复操作以添加多台设备。 - -### Discover automatically / 自动搜索 - -使用此选项, 组件会列出网络上或者指定IP地址上的设备, 选择一个并进行添加。 - -你也可以使用IP地址在指定网络中搜索, 比如`192.168.1.255` - -***❗注意: 自动配置要求设备必须与HA在同一网段, 否则可能搜索不到设备, 请自行确认这点*** - -### Configure manually / 手动配置 - -如果之前你已经通过其它集成手工配置过设备, 并知道以下信息, 也可以进行手动配置 - -- 设备ID -- 设备类型 ([已支持的设备](README_hans.md#%E5%B7%B2%E6%94%AF%E6%8C%81%E7%9A%84%E8%AE%BE%E5%A4%87)之一) -- IP地址 -- 端口(默认为6444) -- 协议版本 -- Token -- Key - -### List all appliances only / 仅列出所有设备 - -使用此选择, 可以列出网络中所有可以被搜索到的美的M-Smart设备, 以及他们的ID, 类型, SN等信息 - -***❗注意: 出于某些原因, 可能不是所有受支持的设备都能于此列出*** - -## 配置 - -集成配置位于`配置 -> 设备与服务 -> Midea AC LAN -> 设备 -> 选项`。 -在配置中, 你可以在设备IP改变后重新指定IP地址, 也可以增加扩展的传感器或开关等实体或者自定义你的设备 - -### IP地址 - -指定设备的IP地址。当你的设备IP地址变动后, 可以重新设定它 - -### 刷新间隔 - -指定单台设备的主动状态刷新间隔 (单位为秒) (默认值为30, 设0代表不进行主动刷新) - -大部分的美的设备在自身状态改变时会主动发送通知, 在这种情况下, 即使将改值设为0, 也不会影响HA中的设备状态更新。组件默认每间隔30秒会进行主动刷新。部分设备没有状态改变时主动通知的机制, 状态同步就会表现得很慢, 这种情况下可以尝试更短的主动状态更新间隔。 - -***❗注意: 更小的更新间隔意味着更多的能源消耗*** - -## 额外的传感器及开关实体 - -配置完成后, 可能会默认生成一个或几个主要实体(比如climate实体)。如果需要其它属性生成为扩展的传感器及开关实体, 在Midea AC LAN集成卡片上点击'选项', 并选择要生成的传感器及开关(如果你的设备支持该属性)。 - -### 自定义 - -某些类型的设备有它们自己的自定义项, 可以通过自定义项定制设备的特点。如果你的设备无法正常工作, 也许你需要自定义它。请参阅设备文档以获取具体信息。 - -自定义的格式必须是JSON。 - -如果要设置多个自定义项, 请注意遵循JSON的格式要求 - -示例 - -```json -{"refresh_interval": 15, "fan_speed": 100} -``` - -## 调试 - -要打开调试日志输出, 在configuration.yaml中做如下配置 - -```yaml -logger: - default: warn - logs: - custom_components.midea_ac_lan: debug -``` - -## 支持我的工作 - -如果喜欢这个集成, 可以使用支付宝或者微信支付赞助我来支持我的工作。 - -![alipay](doc/images/alipay.png) ![wechatpay](doc/images/wechatpay.png) +# Midea AC LAN + +[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) +[![Donate](https://img.shields.io/badge/donate-BuyMeCoffee-yellow.svg)](https://www.buymeacoffee.com/georgezhao2010) +[![Stable](https://img.shields.io/github/v/release/CyrielRct/midea_ac_lan)](https://github.com/CyrielRct/midea_ac_lan/releases/latest) + +[English](README.md) | 简体中文 + +通过本地局域网控制你的美的M-Smart设备 + +- 通过Home Assistant UI完成设备的自动搜索和配置. +- 生成额外的传感器和开关方便进行设备控制. +- 与设备保持TCP长连接以便实时同步设备状态. + +本集成部分技术来源来自 [@mac-zhou](https://github.com/mac-zhou/midea-msmart), 他的美的midea-msmart提供了类似的功能。 该组件包括来自他的项目中经过改写的部分代码。 + +同时感谢[@NeoAcheron](https://github.com/NeoAcheron/midea-ac-py) + +⭐如果本集成对你有所帮助, 请不吝为它点个星, 这将是对我的极大激励。 + +***❗注意: 本集成需要Home Assistant 2023.1或更高版本*** + +## 已支持的品牌 + +![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) \ +![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) \ +![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) \ +![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) \ +![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) + +以及更多。 + +## 已支持的设备 + +| 类型 | 名称 | 文档 | +|----|-------------------|------------------------------| +| 13 | 灯 | [13_hans.md](doc/13_hans.md) | +| 26 | 浴霸 | [26_hans.md](doc/26_hans.md) | +| 34 | 水槽式洗碗机 | [34_hans.md](doc/34_hans.md) | +| 40 | 凉霸 | [40_hans.md](doc/40_hans.md) | +| A1 | 除湿器 | [A1_hans.md](doc/A1_hans.md) | +| AC | 空调器 | [AC_hans.md](doc/AC_hans.md) | +| B0 | 微波炉 | [B0_hans.md](doc/B0_hans.md) | +| B1 | 电烤箱 | [B1_hans.md](doc/B1_hans.md) | +| B3 | 消毒碗柜 | [B3_hans.md](doc/B3_hans.md) | +| B4 | 小烤箱 | [B4_hans.md](doc/B4_hans.md) | +| B6 | 油烟机 | [B6_hans.md](doc/B6_hans.md) | +| BF | 微蒸烤一体机 | [BF_hans.md](doc/BF_hans.md) | +| C2 | 智能马桶 | [C2_hans.md](doc/C2_hans.md) | +| C3 | 热泵空调Wi-Fi线控器 | [C3_hans.md](doc/C3_hans.md) | +| CA | 冰箱 | [CA_hans.md](doc/CA_hans.md) | +| CC | 中央空调(风管机)Wi-Fi线控器 | [CC_hans.md](doc/CC_hans.md) | +| CD | 空气能热水器 | [CD_hans.md](doc/CD_hans.md) | +| CE | 新风设备 | [CE_hans.md](doc/CE_hans.md) | +| CF | 中央空调暖家(水机) | [CF_hans.md](doc/CF_hans.md) | +| DA | 波轮洗衣机 | [DA_hans.md](doc/DA_hans.md) | +| DB | 滚筒洗衣机 | [DB_hans.md](doc/DB_hans.md) | +| DC | 干衣机 | [DC_hans.md](doc/DC_hans.md) | +| E1 | 洗碗机 | [E1_hans.md](doc/E1_hans.md) | +| E2 | 电热水器 | [E2_hans.md](doc/E2_hans.md) | +| E3 | 燃气热水器 | [E3_hans.md](doc/E3_hans.md) | +| E6 | 壁挂炉 | [E6_hans.md](doc/E6_hans.md) | +| E8 | 慢炖锅 | [E8_hans.md](doc/E8_hans.md) | +| EA | 电饭煲 | [EA_hans.md](doc/EA_hans.md) | +| EC | 电压力锅 | [EC_hans.md](doc/EC_hans.md) | +| ED | 饮用水设备 | [ED_hans.md](doc/ED_hans.md) | +| FA | 电风扇 | [FA_hans.md](doc/FA_hans.md) | +| FB | 电取暖器 | [FB_hans.md](doc/FB_hans.md) | +| FC | 空气净化器 | [FC_hans.md](doc/FC_hans.md) | +| FD | 加湿器 | [FD_hans.md](doc/FD_hans.md) | + +## 安装 + +在HACS中搜索'Midea AC LAN'并安装, 或者从[Latest release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest)下载最新的Release版本, 将其中的`custom_components/midea_ac_lan`放到你的Home Assistant的`custom_components/midea_ac_lan`中。 + +重启Home Assistant + +## 添加设备 + +***❗注意: 首先, 在路由器上为你的设备设置一个静态IP地址, 以防设置后设备的IP地址发生改变。*** + +安装之后, 在Home Assistant的集成界面搜索添加集成Midea AC LAN, 如果需要添加多台设备, 多次添加本集成并执行自动配置即可。 + +或者直接点击 [![Configuration](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start?domain=midea_ac_lan) + +***❗注意: 配置过程中, 可能会要求输入你的美的账号及密码, 这是因为需要去美的云服务器获取设备的信息 (Token and Key)。在完成所有设备配置后, 可以删除美的账户配置, 不影响设备的使用*** + +完成美的账户配置之后, 点击'添加设备'进行设备添加。你可以多次重复操作以添加多台设备。 + +### Discover automatically / 自动搜索 + +使用此选项, 组件会列出网络上或者指定IP地址上的设备, 选择一个并进行添加。 + +你也可以使用IP地址在指定网络中搜索, 比如`192.168.1.255` + +***❗注意: 自动配置要求设备必须与HA在同一网段, 否则可能搜索不到设备, 请自行确认这点*** + +### Configure manually / 手动配置 + +如果之前你已经通过其它集成手工配置过设备, 并知道以下信息, 也可以进行手动配置 + +- 设备ID +- 设备类型 ([已支持的设备](README_hans.md#%E5%B7%B2%E6%94%AF%E6%8C%81%E7%9A%84%E8%AE%BE%E5%A4%87)之一) +- IP地址 +- 端口(默认为6444) +- 协议版本 +- Token +- Key + +### List all appliances only / 仅列出所有设备 + +使用此选择, 可以列出网络中所有可以被搜索到的美的M-Smart设备, 以及他们的ID, 类型, SN等信息 + +***❗注意: 出于某些原因, 可能不是所有受支持的设备都能于此列出*** + +## 配置 + +集成配置位于`配置 -> 设备与服务 -> Midea AC LAN -> 设备 -> 选项`。 +在配置中, 你可以在设备IP改变后重新指定IP地址, 也可以增加扩展的传感器或开关等实体或者自定义你的设备 + +### IP地址 + +指定设备的IP地址。当你的设备IP地址变动后, 可以重新设定它 + +### 刷新间隔 + +指定单台设备的主动状态刷新间隔 (单位为秒) (默认值为30, 设0代表不进行主动刷新) + +大部分的美的设备在自身状态改变时会主动发送通知, 在这种情况下, 即使将改值设为0, 也不会影响HA中的设备状态更新。组件默认每间隔30秒会进行主动刷新。部分设备没有状态改变时主动通知的机制, 状态同步就会表现得很慢, 这种情况下可以尝试更短的主动状态更新间隔。 + +***❗注意: 更小的更新间隔意味着更多的能源消耗*** + +## 额外的传感器及开关实体 + +配置完成后, 可能会默认生成一个或几个主要实体(比如climate实体)。如果需要其它属性生成为扩展的传感器及开关实体, 在Midea AC LAN集成卡片上点击'选项', 并选择要生成的传感器及开关(如果你的设备支持该属性)。 + +### 自定义 + +某些类型的设备有它们自己的自定义项, 可以通过自定义项定制设备的特点。如果你的设备无法正常工作, 也许你需要自定义它。请参阅设备文档以获取具体信息。 + +自定义的格式必须是JSON。 + +如果要设置多个自定义项, 请注意遵循JSON的格式要求 + +示例 + +```json +{"refresh_interval": 15, "fan_speed": 100} +``` + +## 调试 + +要打开调试日志输出, 在configuration.yaml中做如下配置 + +```yaml +logger: + default: warn + logs: + custom_components.midea_ac_lan: debug +``` + +## 支持我的工作 + +如果喜欢这个集成, 可以使用支付宝或者微信支付赞助我来支持我的工作。 + +![alipay](doc/images/alipay.png) ![wechatpay](doc/images/wechatpay.png) diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py index 795a11dd..e5cfbf22 100644 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -1,226 +1,226 @@ -""" -__init__.py -""" - -import logging - -import homeassistant.helpers.config_validation as cv -import voluptuous as vol -from homeassistant.const import ( - CONF_CUSTOMIZE, - CONF_DEVICE_ID, - CONF_IP_ADDRESS, - CONF_NAME, - CONF_PORT, - CONF_PROTOCOL, - CONF_TOKEN, - CONF_TYPE, -) -from homeassistant.core import HomeAssistant - -from .const import ( - ALL_PLATFORM, - CONF_ACCOUNT, - CONF_KEY, - CONF_MODEL, - CONF_REFRESH_INTERVAL, - CONF_SUBTYPE, - DEVICES, - DOMAIN, - EXTRA_SWITCH, -) -from .midea.devices import async_device_selector -from .midea_devices import MIDEA_DEVICES - -_LOGGER = logging.getLogger(__name__) - - -async def update_listener(hass, config_entry): - """ - update_listener - """ - for platform in ALL_PLATFORM: - await hass.config_entries.async_forward_entry_unload(config_entry, platform) - for platform in ALL_PLATFORM: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, platform) - ) - device_id = config_entry.data.get(CONF_DEVICE_ID) - customize = config_entry.options.get(CONF_CUSTOMIZE, "") - ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) - refresh_interval = config_entry.options.get(CONF_REFRESH_INTERVAL, None) - dev = hass.data[DOMAIN][DEVICES].get(device_id) - if dev: - dev.set_customize(customize) - if ip_address is not None: - dev.set_ip_address(ip_address) - if refresh_interval is not None: - dev.set_refresh_interval(refresh_interval) - - -async def async_setup(hass: HomeAssistant, config: dict): - """ - async_setup - """ - if config.get(DOMAIN) is None: - # We get her if the integration is set up using config flow - return True - - hass.data.setdefault(DOMAIN, {}) - attributes = [] - for device_entities in MIDEA_DEVICES.values(): - for attribute_name, attribute in device_entities.get("entities").items(): - if ( - attribute.get("type") in EXTRA_SWITCH - and attribute_name.value not in attributes - ): - attributes.append(attribute_name.value) - - def service_set_attribute(service): - """ - service_set_attribute - """ - device_id = service.data.get("device_id") - attr = service.data.get("attribute") - value = service.data.get("value") - dev = hass.data[DOMAIN][DEVICES].get(device_id) - if dev: - if attr == "fan_speed" and value == "auto": - value = 102 - item = MIDEA_DEVICES.get(dev.device_type).get("entities").get(attr) - if ( - item - and (item.get("type") in EXTRA_SWITCH) - or ( - dev.device_type == 0xAC - and attr == "fan_speed" - and value in range(0, 103) - ) - ): - dev.set_attribute(attr=attr, value=value) - else: - _LOGGER.error( - f"Appliance [{device_id}] has no attribute {attr} or value is invalid" - ) - - def service_send_command(service): - """ - service_send_command - """ - device_id = service.data.get("device_id") - cmd_type = service.data.get("cmd_type") - cmd_body = service.data.get("cmd_body") - try: - cmd_body = bytearray.fromhex(cmd_body) - except ValueError: - _LOGGER.error( - f"Appliance [{device_id}] invalid cmd_body, a hexadecimal string required" - ) - return - dev = hass.data[DOMAIN][DEVICES].get(device_id) - if dev: - dev.send_command(cmd_type, cmd_body) - - hass.services.async_register( - DOMAIN, - "set_attribute", - service_set_attribute, - schema=vol.Schema( - { - vol.Required("device_id"): vol.Coerce(int), - vol.Required("attribute"): vol.In(attributes), - vol.Required("value"): vol.Any(int, cv.boolean, str), - } - ), - ) - - hass.services.async_register( - DOMAIN, - "send_command", - service_send_command, - schema=vol.Schema( - { - vol.Required("device_id"): vol.Coerce(int), - vol.Required("cmd_type"): vol.In([2, 3]), - vol.Required("cmd_body"): str, - } - ), - ) - return True - - -async def async_setup_entry(hass: HomeAssistant, config_entry): - """ - async_setup_entry - """ - device_type = config_entry.data.get(CONF_TYPE) - if device_type == CONF_ACCOUNT: - return True - name = config_entry.data.get(CONF_NAME) - device_id = config_entry.data.get(CONF_DEVICE_ID) - if name is None: - name = f"{device_id}" - if device_type is None: - device_type = 0xAC - token = config_entry.data.get(CONF_TOKEN) - key = config_entry.data.get(CONF_KEY) - ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) - if ip_address is None: - ip_address = config_entry.data.get(CONF_IP_ADDRESS) - refresh_interval = config_entry.options.get(CONF_REFRESH_INTERVAL) - port = config_entry.data.get(CONF_PORT) - model = config_entry.data.get(CONF_MODEL) - subtype = config_entry.data.get(CONF_SUBTYPE, 0) - protocol = config_entry.data.get(CONF_PROTOCOL) - customize = config_entry.options.get(CONF_CUSTOMIZE) - if protocol == 3 and (key is None or key is None): - _LOGGER.error("For V3 devices, the key and the token is required.") - return False - device = await async_device_selector( - hass=hass, - name=name, - device_id=device_id, - device_type=device_type, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - customize=customize, - ) - if refresh_interval is not None: - device.set_refresh_interval(refresh_interval) - if device: - device.open() - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if DEVICES not in hass.data[DOMAIN]: - hass.data[DOMAIN][DEVICES] = {} - hass.data[DOMAIN][DEVICES][device_id] = device - for platform in ALL_PLATFORM: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, platform) - ) - config_entry.add_update_listener(update_listener) - return True - return False - - -async def async_unload_entry(hass: HomeAssistant, config_entry): - """ - async_unload_entry - """ - device_type = config_entry.data.get(CONF_TYPE) - if device_type == CONF_ACCOUNT: - return True - device_id = config_entry.data.get(CONF_DEVICE_ID) - if device_id is not None: - dm = hass.data[DOMAIN][DEVICES].get(device_id) - if dm is not None: - dm.close() - hass.data[DOMAIN][DEVICES].pop(device_id) - for platform in ALL_PLATFORM: - await hass.config_entries.async_forward_entry_unload(config_entry, platform) - return True +""" +__init__.py +""" + +import logging + +import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from homeassistant.const import ( + CONF_CUSTOMIZE, + CONF_DEVICE_ID, + CONF_IP_ADDRESS, + CONF_NAME, + CONF_PORT, + CONF_PROTOCOL, + CONF_TOKEN, + CONF_TYPE, +) +from homeassistant.core import HomeAssistant + +from .const import ( + ALL_PLATFORM, + CONF_ACCOUNT, + CONF_KEY, + CONF_MODEL, + CONF_REFRESH_INTERVAL, + CONF_SUBTYPE, + DEVICES, + DOMAIN, + EXTRA_SWITCH, +) +from .midea.devices import async_device_selector +from .midea_devices import MIDEA_DEVICES + +_LOGGER = logging.getLogger(__name__) + + +async def update_listener(hass, config_entry): + """ + update_listener + """ + for platform in ALL_PLATFORM: + await hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in ALL_PLATFORM: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) + device_id = config_entry.data.get(CONF_DEVICE_ID) + customize = config_entry.options.get(CONF_CUSTOMIZE, "") + ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) + refresh_interval = config_entry.options.get(CONF_REFRESH_INTERVAL, None) + dev = hass.data[DOMAIN][DEVICES].get(device_id) + if dev: + dev.set_customize(customize) + if ip_address is not None: + dev.set_ip_address(ip_address) + if refresh_interval is not None: + dev.set_refresh_interval(refresh_interval) + + +async def async_setup(hass: HomeAssistant, config: dict): + """ + async_setup + """ + if config.get(DOMAIN) is None: + # We get her if the integration is set up using config flow + return True + + hass.data.setdefault(DOMAIN, {}) + attributes = [] + for device_entities in MIDEA_DEVICES.values(): + for attribute_name, attribute in device_entities.get("entities").items(): + if ( + attribute.get("type") in EXTRA_SWITCH + and attribute_name.value not in attributes + ): + attributes.append(attribute_name.value) + + def service_set_attribute(service): + """ + service_set_attribute + """ + device_id = service.data.get("device_id") + attr = service.data.get("attribute") + value = service.data.get("value") + dev = hass.data[DOMAIN][DEVICES].get(device_id) + if dev: + if attr == "fan_speed" and value == "auto": + value = 102 + item = MIDEA_DEVICES.get(dev.device_type).get("entities").get(attr) + if ( + item + and (item.get("type") in EXTRA_SWITCH) + or ( + dev.device_type == 0xAC + and attr == "fan_speed" + and value in range(0, 103) + ) + ): + dev.set_attribute(attr=attr, value=value) + else: + _LOGGER.error( + f"Appliance [{device_id}] has no attribute {attr} or value is invalid" + ) + + def service_send_command(service): + """ + service_send_command + """ + device_id = service.data.get("device_id") + cmd_type = service.data.get("cmd_type") + cmd_body = service.data.get("cmd_body") + try: + cmd_body = bytearray.fromhex(cmd_body) + except ValueError: + _LOGGER.error( + f"Appliance [{device_id}] invalid cmd_body, a hexadecimal string required" + ) + return + dev = hass.data[DOMAIN][DEVICES].get(device_id) + if dev: + dev.send_command(cmd_type, cmd_body) + + hass.services.async_register( + DOMAIN, + "set_attribute", + service_set_attribute, + schema=vol.Schema( + { + vol.Required("device_id"): vol.Coerce(int), + vol.Required("attribute"): vol.In(attributes), + vol.Required("value"): vol.Any(int, cv.boolean, str), + } + ), + ) + + hass.services.async_register( + DOMAIN, + "send_command", + service_send_command, + schema=vol.Schema( + { + vol.Required("device_id"): vol.Coerce(int), + vol.Required("cmd_type"): vol.In([2, 3]), + vol.Required("cmd_body"): str, + } + ), + ) + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry): + """ + async_setup_entry + """ + device_type = config_entry.data.get(CONF_TYPE) + if device_type == CONF_ACCOUNT: + return True + name = config_entry.data.get(CONF_NAME) + device_id = config_entry.data.get(CONF_DEVICE_ID) + if name is None: + name = f"{device_id}" + if device_type is None: + device_type = 0xAC + token = config_entry.data.get(CONF_TOKEN) + key = config_entry.data.get(CONF_KEY) + ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) + if ip_address is None: + ip_address = config_entry.data.get(CONF_IP_ADDRESS) + refresh_interval = config_entry.options.get(CONF_REFRESH_INTERVAL) + port = config_entry.data.get(CONF_PORT) + model = config_entry.data.get(CONF_MODEL) + subtype = config_entry.data.get(CONF_SUBTYPE, 0) + protocol = config_entry.data.get(CONF_PROTOCOL) + customize = config_entry.options.get(CONF_CUSTOMIZE) + if protocol == 3 and (key is None or key is None): + _LOGGER.error("For V3 devices, the key and the token is required.") + return False + device = await async_device_selector( + hass=hass, + name=name, + device_id=device_id, + device_type=device_type, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + customize=customize, + ) + if refresh_interval is not None: + device.set_refresh_interval(refresh_interval) + if device: + device.open() + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + if DEVICES not in hass.data[DOMAIN]: + hass.data[DOMAIN][DEVICES] = {} + hass.data[DOMAIN][DEVICES][device_id] = device + for platform in ALL_PLATFORM: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) + config_entry.add_update_listener(update_listener) + return True + return False + + +async def async_unload_entry(hass: HomeAssistant, config_entry): + """ + async_unload_entry + """ + device_type = config_entry.data.get(CONF_TYPE) + if device_type == CONF_ACCOUNT: + return True + device_id = config_entry.data.get(CONF_DEVICE_ID) + if device_id is not None: + dm = hass.data[DOMAIN][DEVICES].get(device_id) + if dm is not None: + dm.close() + hass.data[DOMAIN][DEVICES].pop(device_id) + for platform in ALL_PLATFORM: + await hass.config_entries.async_forward_entry_unload(config_entry, platform) + return True diff --git a/custom_components/midea_ac_lan/binary_sensor.py b/custom_components/midea_ac_lan/binary_sensor.py index e5db6364..5bfcfca1 100644 --- a/custom_components/midea_ac_lan/binary_sensor.py +++ b/custom_components/midea_ac_lan/binary_sensor.py @@ -1,45 +1,45 @@ -""" -binary_sensor.py -""" - -from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """ - async_setup_entry - """ - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_sensors = config_entry.options.get(CONF_SENSORS, []) - binary_sensors = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.BINARY_SENSOR and entity_key in extra_sensors: - sensor = MideaSensor(device, entity_key) - binary_sensors.append(sensor) - async_add_entities(binary_sensors) - - -class MideaSensor(MideaEntity, BinarySensorEntity): - """ - MideaSensor - """ - - @property - def device_class(self): - """ - device_class - """ - return self._config.get("device_class") - - @property - def is_on(self): - """ - is_on - """ - return self._device.get_attribute(self._entity_key) +""" +binary_sensor.py +""" + +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """ + async_setup_entry + """ + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_sensors = config_entry.options.get(CONF_SENSORS, []) + binary_sensors = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.BINARY_SENSOR and entity_key in extra_sensors: + sensor = MideaSensor(device, entity_key) + binary_sensors.append(sensor) + async_add_entities(binary_sensors) + + +class MideaSensor(MideaEntity, BinarySensorEntity): + """ + MideaSensor + """ + + @property + def device_class(self): + """ + device_class + """ + return self._config.get("device_class") + + @property + def is_on(self): + """ + is_on + """ + return self._device.get_attribute(self._entity_key) diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py index d982701d..6ae9dddf 100644 --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -1,806 +1,806 @@ -import logging - -from homeassistant.components.climate import ( - ATTR_HVAC_MODE, - FAN_AUTO, - FAN_HIGH, - FAN_LOW, - FAN_MEDIUM, - PRESET_AWAY, - PRESET_BOOST, - PRESET_COMFORT, - PRESET_ECO, - PRESET_NONE, - PRESET_SLEEP, - SWING_BOTH, - SWING_HORIZONTAL, - SWING_OFF, - SWING_ON, - SWING_VERTICAL, - ClimateEntity, - ClimateEntityFeature, - HVACMode, -) -from homeassistant.const import ( - ATTR_TEMPERATURE, - CONF_DEVICE_ID, - CONF_SWITCHES, - MAJOR_VERSION, - MINOR_VERSION, - PRECISION_HALVES, - PRECISION_WHOLE, - Platform, - UnitOfTemperature, -) - -from .const import DEVICES, DOMAIN -from .midea.devices.ac.device import DeviceAttributes as ACAttributes -from .midea.devices.c3.device import DeviceAttributes as C3Attributes -from .midea.devices.cc.device import DeviceAttributes as CCAttributes -from .midea.devices.cf.device import DeviceAttributes as CFAttributes -from .midea.devices.fb.device import DeviceAttributes as FBAttributes -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - -_LOGGER = logging.getLogger(__name__) - - -TEMPERATURE_MAX = 30 -TEMPERATURE_MIN = 17 - -FAN_SILENT = "Silent" -FAN_FULL_SPEED = "Full" - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """ - async_setup_entry - """ - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) - devs = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.CLIMATE and ( - config.get("default") or entity_key in extra_switches - ): - if device.device_type == 0xAC: - devs.append(MideaACClimate(device, entity_key)) - elif device.device_type == 0xCC: - devs.append(MideaCCClimate(device, entity_key)) - elif device.device_type == 0xCF: - devs.append(MideaCFClimate(device, entity_key)) - elif device.device_type == 0xC3: - devs.append(MideaC3Climate(device, entity_key, config["zone"])) - elif device.device_type == 0xFB: - devs.append(MideaFBClimate(device, entity_key)) - async_add_entities(devs) - - -class MideaClimate(MideaEntity, ClimateEntity): - """ - MideaClimate - """ - - # https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded - _enable_turn_on_off_backwards_compatibility: bool = ( - False # maybe remove after 2025.1 - ) - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - - @property - def supported_features(self): - """ - supported_features - """ - features = ( - ClimateEntityFeature.TARGET_TEMPERATURE - | ClimateEntityFeature.FAN_MODE - | ClimateEntityFeature.PRESET_MODE - | ClimateEntityFeature.SWING_MODE - ) - if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): - features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON - return features - - @property - def min_temp(self): - """ - min_temp - """ - return TEMPERATURE_MIN - - @property - def max_temp(self): - """ - max_temp - """ - return TEMPERATURE_MAX - - @property - def temperature_unit(self): - """ - temperature_unit - """ - return UnitOfTemperature.CELSIUS - - @property - def target_temperature_low(self): - """ - target_temperature_low - """ - return TEMPERATURE_MIN - - @property - def target_temperature_high(self): - """ - target_temperature_high - """ - return TEMPERATURE_MAX - - @property - def hvac_modes(self): - """ - hvac_modes - """ - return self._modes - - @property - def swing_modes(self): - """ - swing_modes - """ - return self._swing_modes - - @property - def is_on(self) -> bool: - """ - is_on - """ - return self.hvac_mode != HVACMode.OFF - - @property - def hvac_mode(self) -> str: - """ - hvac_mode - """ - if self._device.get_attribute("power"): - return self._modes[self._device.get_attribute("mode")] - else: - return HVACMode.OFF - - @property - def target_temperature(self): - """ - target_temperature - """ - return self._device.get_attribute("target_temperature") - - @property - def current_temperature(self): - """ - current_temperature - """ - return self._device.get_attribute("indoor_temperature") - - @property - def preset_modes(self): - """ - preset_modes - """ - return self._preset_modes - - @property - def preset_mode(self): - """ - preset_mode - """ - if self._device.get_attribute("comfort_mode"): - mode = PRESET_COMFORT - elif self._device.get_attribute("eco_mode"): - mode = PRESET_ECO - elif self._device.get_attribute("boost_mode"): - mode = PRESET_BOOST - elif self._device.get_attribute("sleep_mode"): - mode = PRESET_SLEEP - elif self._device.get_attribute("frost_protect"): - mode = PRESET_AWAY - else: - mode = PRESET_NONE - return mode - - @property - def extra_state_attributes(self) -> dict: - """ - extra_state_attributes - """ - return self._device.attributes - - def turn_on(self): - """ - turn_on - """ - self._device.set_attribute(attr="power", value=True) - - def turn_off(self): - """ - turn_off - """ - self._device.set_attribute(attr="power", value=False) - - def set_temperature(self, **kwargs) -> None: - """ - set_temperature - """ - if ATTR_TEMPERATURE not in kwargs: - return - temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 - hvac_mode = kwargs.get(ATTR_HVAC_MODE) - if hvac_mode == HVACMode.OFF: - self.turn_off() - else: - try: - mode = self._modes.index(hvac_mode.lower()) if hvac_mode else None - self._device.set_target_temperature( - target_temperature=temperature, mode=mode - ) - except ValueError as e: - _LOGGER.error(f"set_temperature {e}, kwargs = {kwargs}") - - def set_hvac_mode(self, hvac_mode: str) -> None: - """ - set_hvac_mode - """ - hvac_mode = hvac_mode.lower() - if hvac_mode == HVACMode.OFF: - self.turn_off() - else: - self._device.set_attribute(attr="mode", value=self._modes.index(hvac_mode)) - - def set_preset_mode(self, preset_mode: str) -> None: - """ - set_preset_mode - """ - old_mode = self.preset_mode - preset_mode = preset_mode.lower() - if preset_mode == PRESET_AWAY: - self._device.set_attribute(attr="frost_protect", value=True) - elif preset_mode == PRESET_COMFORT: - self._device.set_attribute(attr="comfort_mode", value=True) - elif preset_mode == PRESET_SLEEP: - self._device.set_attribute(attr="sleep_mode", value=True) - elif preset_mode == PRESET_ECO: - self._device.set_attribute(attr="eco_mode", value=True) - elif preset_mode == PRESET_BOOST: - self._device.set_attribute(attr="boost_mode", value=True) - elif old_mode == PRESET_AWAY: - self._device.set_attribute(attr="frost_protect", value=False) - elif old_mode == PRESET_COMFORT: - self._device.set_attribute(attr="comfort_mode", value=False) - elif old_mode == PRESET_SLEEP: - self._device.set_attribute(attr="sleep_mode", value=False) - elif old_mode == PRESET_ECO: - self._device.set_attribute(attr="eco_mode", value=False) - elif old_mode == PRESET_BOOST: - self._device.set_attribute(attr="boost_mode", value=False) - - def update_state(self, status): - """ - update_state - """ - try: - self.schedule_update_ha_state() - except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) - - -class MideaACClimate(MideaClimate): - """ - MideaACClimate - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - self._modes = [ - HVACMode.OFF, - HVACMode.AUTO, - HVACMode.COOL, - HVACMode.DRY, - HVACMode.HEAT, - HVACMode.FAN_ONLY, - ] - self._fan_speeds = { - FAN_SILENT.capitalize(): 20, - FAN_LOW.capitalize(): 40, - FAN_MEDIUM.capitalize(): 60, - FAN_HIGH.capitalize(): 80, - FAN_FULL_SPEED.capitalize(): 100, - FAN_AUTO.capitalize(): 102, - } - self._swing_modes = [ - SWING_OFF.capitalize(), - SWING_VERTICAL.capitalize(), - SWING_HORIZONTAL.capitalize(), - SWING_BOTH.capitalize(), - ] - self._preset_modes = [ - PRESET_NONE, - PRESET_COMFORT, - PRESET_ECO, - PRESET_BOOST, - PRESET_SLEEP, - PRESET_AWAY, - ] - - @property - def fan_modes(self): - """ - fan_modes - """ - return list(self._fan_speeds.keys()) - - @property - def fan_mode(self) -> str: - """ - fan_mode - """ - fan_speed = self._device.get_attribute(ACAttributes.fan_speed) - if fan_speed > 100: - return FAN_AUTO.capitalize() - elif fan_speed > 80: - return FAN_FULL_SPEED.capitalize() - elif fan_speed > 60: - return FAN_HIGH.capitalize() - elif fan_speed > 40: - return FAN_MEDIUM.capitalize() - elif fan_speed > 20: - return FAN_LOW.capitalize() - else: - return FAN_SILENT.capitalize() - - @property - def target_temperature_step(self): - """ - target_temperature_step - """ - return ( - PRECISION_WHOLE if self._device.temperature_step == 1 else PRECISION_HALVES - ) - - @property - def swing_mode(self): - """ - swing_mode - """ - swing_mode = ( - 1 if self._device.get_attribute(ACAttributes.swing_vertical) else 0 - ) + (2 if self._device.get_attribute(ACAttributes.swing_horizontal) else 0) - return self._swing_modes[swing_mode] - - @property - def outdoor_temperature(self): - """ - outdoor_temperature - """ - return self._device.get_attribute(ACAttributes.outdoor_temperature) - - def set_fan_mode(self, fan_mode: str) -> None: - """ - set_fan_mode - """ - fan_speed = self._fan_speeds.get(fan_mode.capitalize()) - if fan_speed: - self._device.set_attribute(attr=ACAttributes.fan_speed, value=fan_speed) - - def set_swing_mode(self, swing_mode: str) -> None: - """ - set_swing_mode - """ - swing = self._swing_modes.index(swing_mode.capitalize()) - swing_vertical = swing & 1 > 0 - swing_horizontal = swing & 2 > 0 - self._device.set_swing( - swing_vertical=swing_vertical, swing_horizontal=swing_horizontal - ) - - -class MideaCCClimate(MideaClimate): - """ - MideaCCClimate - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - self._modes = [ - HVACMode.OFF, - HVACMode.FAN_ONLY, - HVACMode.DRY, - HVACMode.HEAT, - HVACMode.COOL, - HVACMode.AUTO, - ] - self._swing_modes = [SWING_OFF.capitalize(), SWING_ON.capitalize()] - self._preset_modes = [PRESET_NONE, PRESET_SLEEP, PRESET_ECO] - - @property - def fan_modes(self): - """ - fan_modes - """ - return self._device.fan_modes - - @property - def fan_mode(self) -> str: - """ - fan_mode - """ - return self._device.get_attribute(CCAttributes.fan_speed) - - @property - def target_temperature_step(self): - """ - target_temperature_step - """ - return self._device.get_attribute(CCAttributes.temperature_precision) - - @property - def swing_mode(self): - """ - swing_mode - """ - return ( - SWING_ON.capitalize() - if self._device.get_attribute(CCAttributes.swing) - else SWING_OFF.capitalize() - ) - - def set_fan_mode(self, fan_mode: str) -> None: - """ - set_fan_mode - """ - self._device.set_attribute(attr=CCAttributes.fan_speed, value=fan_mode) - - def set_swing_mode(self, swing_mode: str) -> None: - """ - set_swing_mode - """ - self._device.set_attribute( - attr=CCAttributes.swing, - value=swing_mode.capitalize() == SWING_ON.capitalize(), - ) - - -class MideaCFClimate(MideaClimate): - """ - MideaCFClimate - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] - - @property - def supported_features(self): - """ - supported_features - """ - features = ClimateEntityFeature.TARGET_TEMPERATURE - if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): - features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON - return features - - @property - def target_temperature_step(self): - """ - target_temperature_step - """ - return PRECISION_WHOLE - - @property - def min_temp(self): - """ - min_temp - """ - return self._device.get_attribute(CFAttributes.min_temperature) - - @property - def max_temp(self): - """ - max_temp - """ - return self._device.get_attribute(CFAttributes.max_temperature) - - @property - def target_temperature_low(self): - """ - target_temperature_low - """ - return self._device.get_attribute(CFAttributes.min_temperature) - - @property - def target_temperature_high(self): - """ - target_temperature_high - """ - return self._device.get_attribute(CFAttributes.max_temperature) - - @property - def current_temperature(self): - """ - current_temperature - """ - return self._device.get_attribute(CFAttributes.current_temperature) - - -class MideaC3Climate(MideaClimate): - """ - MideaC3Climate - """ - - _powers = [ - C3Attributes.zone1_power, - C3Attributes.zone2_power, - ] - - def __init__(self, device, entity_key, zone): - """ - __init__ - """ - super().__init__(device, entity_key) - self._zone = zone - self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] - self._power_attr = MideaC3Climate._powers[self._zone] - - @property - def supported_features(self): - """ - supported_features - """ - features = ClimateEntityFeature.TARGET_TEMPERATURE - if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): - features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON - return features - - @property - def target_temperature_step(self): - """ - target_temperature_step - """ - return ( - PRECISION_WHOLE - if self._device.get_attribute(C3Attributes.zone_temp_type)[self._zone] - else PRECISION_HALVES - ) - - @property - def min_temp(self): - """ - min_temp - """ - return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] - - @property - def max_temp(self): - """ - max_temp - """ - return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] - - @property - def target_temperature_low(self): - """ - target_temperature_low - """ - return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] - - @property - def target_temperature_high(self): - """ - target_temperature_high - """ - return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] - - def turn_on(self): - """ - turn_on - """ - self._device.set_attribute(attr=self._power_attr, value=True) - - def turn_off(self): - """ - turn_off - """ - self._device.set_attribute(attr=self._power_attr, value=False) - - @property - def hvac_mode(self) -> str: - """ - hvac_mode - """ - if self._device.get_attribute(self._power_attr): - return self._modes[self._device.get_attribute(C3Attributes.mode)] - else: - return HVACMode.OFF - - @property - def target_temperature(self): - """ - target_temperature - """ - return self._device.get_attribute(C3Attributes.target_temperature)[self._zone] - - @property - def current_temperature(self): - """ - current_temperature - """ - return None - - def set_temperature(self, **kwargs) -> None: - """ - set_temperature - """ - if ATTR_TEMPERATURE not in kwargs: - return - temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 - hvac_mode = kwargs.get(ATTR_HVAC_MODE) - if hvac_mode == HVACMode.OFF: - self.turn_off() - else: - try: - mode = self._modes.index(hvac_mode.lower()) if hvac_mode else None - self._device.set_target_temperature( - zone=self._zone, target_temperature=temperature, mode=mode - ) - except ValueError as e: - _LOGGER.error(f"set_temperature {e}, kwargs = {kwargs}") - - def set_hvac_mode(self, hvac_mode: str) -> None: - """ - set_hvac_mode - """ - hvac_mode = hvac_mode.lower() - if hvac_mode == HVACMode.OFF: - self.turn_off() - else: - self._device.set_mode(self._zone, self._modes.index(hvac_mode)) - - -class MideaFBClimate(MideaClimate): - """ - MideaFBClimate - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - self._modes = [HVACMode.OFF, HVACMode.HEAT] - self._preset_modes = self._device.modes - - @property - def supported_features(self): - """ - supported_features - """ - features = ( - ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE - ) - if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): - features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON - return features - - @property - def target_temperature_step(self): - """ - target_temperature_step - """ - return PRECISION_WHOLE - - @property - def preset_modes(self): - """ - preset_modes - """ - return self._preset_modes - - @property - def preset_mode(self): - """ - preset_mode - """ - return self._device.get_attribute(attr=FBAttributes.mode) - - @property - def min_temp(self): - """ - min_temp - """ - return 5 - - @property - def max_temp(self): - """ - max_temp - """ - return 35 - - @property - def target_temperature_low(self): - """ - target_temperature_low - """ - return 5 - - @property - def target_temperature_high(self): - """ - target_temperature_high - """ - return 35 - - @property - def hvac_mode(self) -> str: - """ - hvac_mode - """ - return ( - HVACMode.HEAT - if self._device.get_attribute(attr=FBAttributes.power) - else HVACMode.OFF - ) - - @property - def current_temperature(self): - """ - current_temperature - """ - return self._device.get_attribute(FBAttributes.current_temperature) - - def set_temperature(self, **kwargs) -> None: - """ - set_temperature - """ - if ATTR_TEMPERATURE not in kwargs: - return - temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 - hvac_mode = kwargs.get(ATTR_HVAC_MODE) - if hvac_mode == HVACMode.OFF: - self.turn_off() - else: - self._device.set_attribute( - attr=FBAttributes.target_temperature, value=temperature - ) - - def set_hvac_mode(self, hvac_mode: str) -> None: - """ - set_hvac_mode - """ - hvac_mode = hvac_mode.lower() - if hvac_mode == HVACMode.OFF: - self.turn_off() - else: - self.turn_on() - - def set_preset_mode(self, preset_mode: str) -> None: - """ - set_preset_mode - """ - self._device.set_attribute(attr=FBAttributes.mode, value=preset_mode) +import logging + +from homeassistant.components.climate import ( + ATTR_HVAC_MODE, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + PRESET_NONE, + PRESET_SLEEP, + SWING_BOTH, + SWING_HORIZONTAL, + SWING_OFF, + SWING_ON, + SWING_VERTICAL, + ClimateEntity, + ClimateEntityFeature, + HVACMode, +) +from homeassistant.const import ( + ATTR_TEMPERATURE, + CONF_DEVICE_ID, + CONF_SWITCHES, + MAJOR_VERSION, + MINOR_VERSION, + PRECISION_HALVES, + PRECISION_WHOLE, + Platform, + UnitOfTemperature, +) + +from .const import DEVICES, DOMAIN +from .midea.devices.ac.device import DeviceAttributes as ACAttributes +from .midea.devices.c3.device import DeviceAttributes as C3Attributes +from .midea.devices.cc.device import DeviceAttributes as CCAttributes +from .midea.devices.cf.device import DeviceAttributes as CFAttributes +from .midea.devices.fb.device import DeviceAttributes as FBAttributes +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + +_LOGGER = logging.getLogger(__name__) + + +TEMPERATURE_MAX = 30 +TEMPERATURE_MIN = 17 + +FAN_SILENT = "Silent" +FAN_FULL_SPEED = "Full" + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """ + async_setup_entry + """ + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + devs = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.CLIMATE and ( + config.get("default") or entity_key in extra_switches + ): + if device.device_type == 0xAC: + devs.append(MideaACClimate(device, entity_key)) + elif device.device_type == 0xCC: + devs.append(MideaCCClimate(device, entity_key)) + elif device.device_type == 0xCF: + devs.append(MideaCFClimate(device, entity_key)) + elif device.device_type == 0xC3: + devs.append(MideaC3Climate(device, entity_key, config["zone"])) + elif device.device_type == 0xFB: + devs.append(MideaFBClimate(device, entity_key)) + async_add_entities(devs) + + +class MideaClimate(MideaEntity, ClimateEntity): + """ + MideaClimate + """ + + # https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded + _enable_turn_on_off_backwards_compatibility: bool = ( + False # maybe remove after 2025.1 + ) + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + + @property + def supported_features(self): + """ + supported_features + """ + features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.SWING_MODE + ) + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + return features + + @property + def min_temp(self): + """ + min_temp + """ + return TEMPERATURE_MIN + + @property + def max_temp(self): + """ + max_temp + """ + return TEMPERATURE_MAX + + @property + def temperature_unit(self): + """ + temperature_unit + """ + return UnitOfTemperature.CELSIUS + + @property + def target_temperature_low(self): + """ + target_temperature_low + """ + return TEMPERATURE_MIN + + @property + def target_temperature_high(self): + """ + target_temperature_high + """ + return TEMPERATURE_MAX + + @property + def hvac_modes(self): + """ + hvac_modes + """ + return self._modes + + @property + def swing_modes(self): + """ + swing_modes + """ + return self._swing_modes + + @property + def is_on(self) -> bool: + """ + is_on + """ + return self.hvac_mode != HVACMode.OFF + + @property + def hvac_mode(self) -> str: + """ + hvac_mode + """ + if self._device.get_attribute("power"): + return self._modes[self._device.get_attribute("mode")] + else: + return HVACMode.OFF + + @property + def target_temperature(self): + """ + target_temperature + """ + return self._device.get_attribute("target_temperature") + + @property + def current_temperature(self): + """ + current_temperature + """ + return self._device.get_attribute("indoor_temperature") + + @property + def preset_modes(self): + """ + preset_modes + """ + return self._preset_modes + + @property + def preset_mode(self): + """ + preset_mode + """ + if self._device.get_attribute("comfort_mode"): + mode = PRESET_COMFORT + elif self._device.get_attribute("eco_mode"): + mode = PRESET_ECO + elif self._device.get_attribute("boost_mode"): + mode = PRESET_BOOST + elif self._device.get_attribute("sleep_mode"): + mode = PRESET_SLEEP + elif self._device.get_attribute("frost_protect"): + mode = PRESET_AWAY + else: + mode = PRESET_NONE + return mode + + @property + def extra_state_attributes(self) -> dict: + """ + extra_state_attributes + """ + return self._device.attributes + + def turn_on(self): + """ + turn_on + """ + self._device.set_attribute(attr="power", value=True) + + def turn_off(self): + """ + turn_off + """ + self._device.set_attribute(attr="power", value=False) + + def set_temperature(self, **kwargs) -> None: + """ + set_temperature + """ + if ATTR_TEMPERATURE not in kwargs: + return + temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 + hvac_mode = kwargs.get(ATTR_HVAC_MODE) + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + try: + mode = self._modes.index(hvac_mode.lower()) if hvac_mode else None + self._device.set_target_temperature( + target_temperature=temperature, mode=mode + ) + except ValueError as e: + _LOGGER.error(f"set_temperature {e}, kwargs = {kwargs}") + + def set_hvac_mode(self, hvac_mode: str) -> None: + """ + set_hvac_mode + """ + hvac_mode = hvac_mode.lower() + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + self._device.set_attribute(attr="mode", value=self._modes.index(hvac_mode)) + + def set_preset_mode(self, preset_mode: str) -> None: + """ + set_preset_mode + """ + old_mode = self.preset_mode + preset_mode = preset_mode.lower() + if preset_mode == PRESET_AWAY: + self._device.set_attribute(attr="frost_protect", value=True) + elif preset_mode == PRESET_COMFORT: + self._device.set_attribute(attr="comfort_mode", value=True) + elif preset_mode == PRESET_SLEEP: + self._device.set_attribute(attr="sleep_mode", value=True) + elif preset_mode == PRESET_ECO: + self._device.set_attribute(attr="eco_mode", value=True) + elif preset_mode == PRESET_BOOST: + self._device.set_attribute(attr="boost_mode", value=True) + elif old_mode == PRESET_AWAY: + self._device.set_attribute(attr="frost_protect", value=False) + elif old_mode == PRESET_COMFORT: + self._device.set_attribute(attr="comfort_mode", value=False) + elif old_mode == PRESET_SLEEP: + self._device.set_attribute(attr="sleep_mode", value=False) + elif old_mode == PRESET_ECO: + self._device.set_attribute(attr="eco_mode", value=False) + elif old_mode == PRESET_BOOST: + self._device.set_attribute(attr="boost_mode", value=False) + + def update_state(self, status): + """ + update_state + """ + try: + self.schedule_update_ha_state() + except Exception as e: + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) + + +class MideaACClimate(MideaClimate): + """ + MideaACClimate + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + self._modes = [ + HVACMode.OFF, + HVACMode.AUTO, + HVACMode.COOL, + HVACMode.DRY, + HVACMode.HEAT, + HVACMode.FAN_ONLY, + ] + self._fan_speeds = { + FAN_SILENT.capitalize(): 20, + FAN_LOW.capitalize(): 40, + FAN_MEDIUM.capitalize(): 60, + FAN_HIGH.capitalize(): 80, + FAN_FULL_SPEED.capitalize(): 100, + FAN_AUTO.capitalize(): 102, + } + self._swing_modes = [ + SWING_OFF.capitalize(), + SWING_VERTICAL.capitalize(), + SWING_HORIZONTAL.capitalize(), + SWING_BOTH.capitalize(), + ] + self._preset_modes = [ + PRESET_NONE, + PRESET_COMFORT, + PRESET_ECO, + PRESET_BOOST, + PRESET_SLEEP, + PRESET_AWAY, + ] + + @property + def fan_modes(self): + """ + fan_modes + """ + return list(self._fan_speeds.keys()) + + @property + def fan_mode(self) -> str: + """ + fan_mode + """ + fan_speed = self._device.get_attribute(ACAttributes.fan_speed) + if fan_speed > 100: + return FAN_AUTO.capitalize() + elif fan_speed > 80: + return FAN_FULL_SPEED.capitalize() + elif fan_speed > 60: + return FAN_HIGH.capitalize() + elif fan_speed > 40: + return FAN_MEDIUM.capitalize() + elif fan_speed > 20: + return FAN_LOW.capitalize() + else: + return FAN_SILENT.capitalize() + + @property + def target_temperature_step(self): + """ + target_temperature_step + """ + return ( + PRECISION_WHOLE if self._device.temperature_step == 1 else PRECISION_HALVES + ) + + @property + def swing_mode(self): + """ + swing_mode + """ + swing_mode = ( + 1 if self._device.get_attribute(ACAttributes.swing_vertical) else 0 + ) + (2 if self._device.get_attribute(ACAttributes.swing_horizontal) else 0) + return self._swing_modes[swing_mode] + + @property + def outdoor_temperature(self): + """ + outdoor_temperature + """ + return self._device.get_attribute(ACAttributes.outdoor_temperature) + + def set_fan_mode(self, fan_mode: str) -> None: + """ + set_fan_mode + """ + fan_speed = self._fan_speeds.get(fan_mode.capitalize()) + if fan_speed: + self._device.set_attribute(attr=ACAttributes.fan_speed, value=fan_speed) + + def set_swing_mode(self, swing_mode: str) -> None: + """ + set_swing_mode + """ + swing = self._swing_modes.index(swing_mode.capitalize()) + swing_vertical = swing & 1 > 0 + swing_horizontal = swing & 2 > 0 + self._device.set_swing( + swing_vertical=swing_vertical, swing_horizontal=swing_horizontal + ) + + +class MideaCCClimate(MideaClimate): + """ + MideaCCClimate + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + self._modes = [ + HVACMode.OFF, + HVACMode.FAN_ONLY, + HVACMode.DRY, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.AUTO, + ] + self._swing_modes = [SWING_OFF.capitalize(), SWING_ON.capitalize()] + self._preset_modes = [PRESET_NONE, PRESET_SLEEP, PRESET_ECO] + + @property + def fan_modes(self): + """ + fan_modes + """ + return self._device.fan_modes + + @property + def fan_mode(self) -> str: + """ + fan_mode + """ + return self._device.get_attribute(CCAttributes.fan_speed) + + @property + def target_temperature_step(self): + """ + target_temperature_step + """ + return self._device.get_attribute(CCAttributes.temperature_precision) + + @property + def swing_mode(self): + """ + swing_mode + """ + return ( + SWING_ON.capitalize() + if self._device.get_attribute(CCAttributes.swing) + else SWING_OFF.capitalize() + ) + + def set_fan_mode(self, fan_mode: str) -> None: + """ + set_fan_mode + """ + self._device.set_attribute(attr=CCAttributes.fan_speed, value=fan_mode) + + def set_swing_mode(self, swing_mode: str) -> None: + """ + set_swing_mode + """ + self._device.set_attribute( + attr=CCAttributes.swing, + value=swing_mode.capitalize() == SWING_ON.capitalize(), + ) + + +class MideaCFClimate(MideaClimate): + """ + MideaCFClimate + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] + + @property + def supported_features(self): + """ + supported_features + """ + features = ClimateEntityFeature.TARGET_TEMPERATURE + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + return features + + @property + def target_temperature_step(self): + """ + target_temperature_step + """ + return PRECISION_WHOLE + + @property + def min_temp(self): + """ + min_temp + """ + return self._device.get_attribute(CFAttributes.min_temperature) + + @property + def max_temp(self): + """ + max_temp + """ + return self._device.get_attribute(CFAttributes.max_temperature) + + @property + def target_temperature_low(self): + """ + target_temperature_low + """ + return self._device.get_attribute(CFAttributes.min_temperature) + + @property + def target_temperature_high(self): + """ + target_temperature_high + """ + return self._device.get_attribute(CFAttributes.max_temperature) + + @property + def current_temperature(self): + """ + current_temperature + """ + return self._device.get_attribute(CFAttributes.current_temperature) + + +class MideaC3Climate(MideaClimate): + """ + MideaC3Climate + """ + + _powers = [ + C3Attributes.zone1_power, + C3Attributes.zone2_power, + ] + + def __init__(self, device, entity_key, zone): + """ + __init__ + """ + super().__init__(device, entity_key) + self._zone = zone + self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] + self._power_attr = MideaC3Climate._powers[self._zone] + + @property + def supported_features(self): + """ + supported_features + """ + features = ClimateEntityFeature.TARGET_TEMPERATURE + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + return features + + @property + def target_temperature_step(self): + """ + target_temperature_step + """ + return ( + PRECISION_WHOLE + if self._device.get_attribute(C3Attributes.zone_temp_type)[self._zone] + else PRECISION_HALVES + ) + + @property + def min_temp(self): + """ + min_temp + """ + return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] + + @property + def max_temp(self): + """ + max_temp + """ + return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] + + @property + def target_temperature_low(self): + """ + target_temperature_low + """ + return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] + + @property + def target_temperature_high(self): + """ + target_temperature_high + """ + return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] + + def turn_on(self): + """ + turn_on + """ + self._device.set_attribute(attr=self._power_attr, value=True) + + def turn_off(self): + """ + turn_off + """ + self._device.set_attribute(attr=self._power_attr, value=False) + + @property + def hvac_mode(self) -> str: + """ + hvac_mode + """ + if self._device.get_attribute(self._power_attr): + return self._modes[self._device.get_attribute(C3Attributes.mode)] + else: + return HVACMode.OFF + + @property + def target_temperature(self): + """ + target_temperature + """ + return self._device.get_attribute(C3Attributes.target_temperature)[self._zone] + + @property + def current_temperature(self): + """ + current_temperature + """ + return None + + def set_temperature(self, **kwargs) -> None: + """ + set_temperature + """ + if ATTR_TEMPERATURE not in kwargs: + return + temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 + hvac_mode = kwargs.get(ATTR_HVAC_MODE) + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + try: + mode = self._modes.index(hvac_mode.lower()) if hvac_mode else None + self._device.set_target_temperature( + zone=self._zone, target_temperature=temperature, mode=mode + ) + except ValueError as e: + _LOGGER.error(f"set_temperature {e}, kwargs = {kwargs}") + + def set_hvac_mode(self, hvac_mode: str) -> None: + """ + set_hvac_mode + """ + hvac_mode = hvac_mode.lower() + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + self._device.set_mode(self._zone, self._modes.index(hvac_mode)) + + +class MideaFBClimate(MideaClimate): + """ + MideaFBClimate + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + self._modes = [HVACMode.OFF, HVACMode.HEAT] + self._preset_modes = self._device.modes + + @property + def supported_features(self): + """ + supported_features + """ + features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + return features + + @property + def target_temperature_step(self): + """ + target_temperature_step + """ + return PRECISION_WHOLE + + @property + def preset_modes(self): + """ + preset_modes + """ + return self._preset_modes + + @property + def preset_mode(self): + """ + preset_mode + """ + return self._device.get_attribute(attr=FBAttributes.mode) + + @property + def min_temp(self): + """ + min_temp + """ + return 5 + + @property + def max_temp(self): + """ + max_temp + """ + return 35 + + @property + def target_temperature_low(self): + """ + target_temperature_low + """ + return 5 + + @property + def target_temperature_high(self): + """ + target_temperature_high + """ + return 35 + + @property + def hvac_mode(self) -> str: + """ + hvac_mode + """ + return ( + HVACMode.HEAT + if self._device.get_attribute(attr=FBAttributes.power) + else HVACMode.OFF + ) + + @property + def current_temperature(self): + """ + current_temperature + """ + return self._device.get_attribute(FBAttributes.current_temperature) + + def set_temperature(self, **kwargs) -> None: + """ + set_temperature + """ + if ATTR_TEMPERATURE not in kwargs: + return + temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 + hvac_mode = kwargs.get(ATTR_HVAC_MODE) + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + self._device.set_attribute( + attr=FBAttributes.target_temperature, value=temperature + ) + + def set_hvac_mode(self, hvac_mode: str) -> None: + """ + set_hvac_mode + """ + hvac_mode = hvac_mode.lower() + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + self.turn_on() + + def set_preset_mode(self, preset_mode: str) -> None: + """ + set_preset_mode + """ + self._device.set_attribute(attr=FBAttributes.mode, value=preset_mode) diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py index 29cdc967..812f0135 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -1,620 +1,620 @@ -""" -config_flow.py -""" - -import os - -import voluptuous as vol - -try: - from homeassistant.helpers.json import save_json -except ImportError: - from homeassistant.util.json import save_json - -import logging - -import homeassistant.helpers.config_validation as cv -from homeassistant import config_entries -from homeassistant.const import ( - CONF_CUSTOMIZE, - CONF_DEVICE, - CONF_DEVICE_ID, - CONF_IP_ADDRESS, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_PROTOCOL, - CONF_SENSORS, - CONF_SWITCHES, - CONF_TOKEN, - CONF_TYPE, -) -from homeassistant.core import callback -from homeassistant.helpers.aiohttp_client import async_create_clientsession -from homeassistant.util.json import load_json - -from .const import ( - CONF_ACCOUNT, - CONF_KEY, - CONF_MODEL, - CONF_REFRESH_INTERVAL, - CONF_SERVER, - CONF_SUBTYPE, - DOMAIN, - EXTRA_CONTROL, - EXTRA_SENSOR, -) -from .midea.core.cloud import get_midea_cloud -from .midea.core.device import MiedaDevice -from .midea.core.discover import discover -from .midea_devices import MIDEA_DEVICES - -_LOGGER = logging.getLogger(__name__) - -ADD_WAY = { - "discovery": "Discover automatically", - "manually": "Configure manually", - "list": "List all appliances only", -} -PROTOCOLS = {1: "V1", 2: "V2", 3: "V3"} -STORAGE_PATH = f".storage/{DOMAIN}" - -SERVERS = { - 1: "MSmartHome", - 2: "美的美居", - 3: "Midea Air", - 4: "NetHome Plus", - 5: "Ariston Clima", -} - -PRESET_ACCOUNT = [ - 39182118275972017797890111985649342047468653967530949796945843010512, - 29406100301096535908214728322278519471982973450672552249652548883645, - 39182118275972017797890111985649342050088014265865102175083010656997, -] - - -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """ - ConfigFlow - """ - - available_device = [] - devices = {} - found_device = {} - supports = {} - unsorted = {} - account = {} - cloud = None - session = None - for device_type, device_info in MIDEA_DEVICES.items(): - unsorted[device_type] = device_info["name"] - - unsorted = sorted(unsorted.items(), key=lambda x: x[1]) - for item in unsorted: - supports[item[0]] = item[1] - - def _save_device_config(self, data: dict): - """ - _save_device_config - """ - os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True) - record_file = self.hass.config.path( - f"{STORAGE_PATH}/{data[CONF_DEVICE_ID]}.json" - ) - save_json(record_file, data) - - def _load_device_config(self, device_id): - """ - _load_device_config - """ - record_file = self.hass.config.path(f"{STORAGE_PATH}/{device_id}.json") - json_data = load_json(record_file, default={}) - return json_data - - def _save_account(self, account: dict): - """ - _save_account - """ - os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True) - record_file = self.hass.config.path(f"{STORAGE_PATH}/account.json") - account[CONF_PASSWORD] = format( - ( - int(account[CONF_ACCOUNT].encode("utf-8").hex(), 16) - ^ int(account[CONF_PASSWORD].encode("utf-8").hex(), 16) - ), - "x", - ) - save_json(record_file, account) - - def _load_account(self): - """ - _load_account - """ - record_file = self.hass.config.path(f"{STORAGE_PATH}/account.json") - json_data = load_json(record_file, default={}) - if CONF_ACCOUNT in json_data.keys(): - json_data[CONF_PASSWORD] = bytes.fromhex( - format( - ( - int(json_data[CONF_PASSWORD], 16) - ^ int(json_data[CONF_ACCOUNT].encode("utf-8").hex(), 16) - ), - "X", - ) - ).decode("UTF-8") - return json_data - - @staticmethod - def _check_storage_device(device: dict, storage_device: dict): - """ - _check_storage_device - """ - if storage_device.get(CONF_SUBTYPE) is None: - return False - if device.get(CONF_PROTOCOL) == 3 and ( - storage_device.get(CONF_TOKEN) is None - or storage_device.get(CONF_KEY) is None - ): - return False - return True - - def _already_configured(self, device_id, ip_address): - """ - _already_configured - """ - for entry in self._async_current_entries(): - if device_id == entry.data.get( - CONF_DEVICE_ID - ) or ip_address == entry.data.get(CONF_IP_ADDRESS): - return True - return False - - async def async_step_user(self, user_input=None, error=None): - """ - async_step_user - """ - if user_input is not None: - if user_input["action"] == "discovery": - return await self.async_step_discovery() - elif user_input["action"] == "manually": - self.found_device = {} - return await self.async_step_manually() - else: - return await self.async_step_list() - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - {vol.Required("action", default="discovery"): vol.In(ADD_WAY)} - ), - errors={"base": error} if error else None, - ) - - async def async_step_login(self, user_input=None, error=None): - """ - async_step_login - """ - if user_input is not None: - if self.session is None: - self.session = async_create_clientsession(self.hass) - if self.cloud is None: - self.cloud = get_midea_cloud( - session=self.session, - cloud_name=SERVERS[user_input[CONF_SERVER]], - account=user_input[CONF_ACCOUNT], - password=user_input[CONF_PASSWORD], - ) - if await self.cloud.login(): - self.account = { - CONF_ACCOUNT: user_input[CONF_ACCOUNT], - CONF_PASSWORD: user_input[CONF_PASSWORD], - CONF_SERVER: SERVERS[user_input[CONF_SERVER]], - } - self._save_account(self.account) - return await self.async_step_auto() - else: - return await self.async_step_login(error="login_failed") - return self.async_show_form( - step_id="login", - data_schema=vol.Schema( - { - vol.Required(CONF_ACCOUNT): str, - vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_SERVER, default=1): vol.In(SERVERS), - } - ), - errors={"base": error} if error else None, - ) - - async def async_step_list(self, user_input=None, error=None): - """ - async_step_list - """ - all_devices = discover() - if len(all_devices) > 0: - table = ( - "Appliance code|Type|IP address|SN|Supported\n:--:|:--:|:--:|:--:|:--:" - ) - for device_id, device in all_devices.items(): - supported = device.get(CONF_TYPE) in self.supports.keys() - table += ( - f"\n{device_id}|{'%02X' % device.get(CONF_TYPE)}|{device.get(CONF_IP_ADDRESS)}|" - f"{device.get('sn')}|" - f"{'YES' if supported else 'NO'}" - ) - else: - table = "Not found" - return self.async_show_form( - step_id="list", - description_placeholders={"table": table}, - errors={"base": error} if error else None, - ) - - async def async_step_discovery(self, user_input=None, error=None): - """ - async_step_discovery - """ - if user_input is not None: - if user_input[CONF_IP_ADDRESS].lower() == "auto": - ip_address = None - else: - ip_address = user_input[CONF_IP_ADDRESS] - self.devices = discover(self.supports.keys(), ip_address=ip_address) - self.available_device = {} - for device_id, device in self.devices.items(): - if not self._already_configured(device_id, device.get(CONF_IP_ADDRESS)): - self.available_device[device_id] = ( - f"{device_id} ({self.supports.get(device.get(CONF_TYPE))})" - ) - if len(self.available_device) > 0: - return await self.async_step_auto() - else: - return await self.async_step_discovery(error="no_devices") - return self.async_show_form( - step_id="discovery", - data_schema=vol.Schema( - {vol.Required(CONF_IP_ADDRESS, default="auto"): str} - ), - errors={"base": error} if error else None, - ) - - async def async_step_auto(self, user_input=None, error=None): - """ - async_step_auto - """ - if user_input is not None: - device_id = user_input[CONF_DEVICE] - device = self.devices.get(device_id) - storage_device = self._load_device_config(device_id) - if self._check_storage_device(device, storage_device): - self.found_device = { - CONF_DEVICE_ID: device_id, - CONF_TYPE: device.get(CONF_TYPE), - CONF_PROTOCOL: device.get(CONF_PROTOCOL), - CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), - CONF_PORT: device.get(CONF_PORT), - CONF_MODEL: device.get(CONF_MODEL), - CONF_NAME: storage_device.get(CONF_NAME), - CONF_SUBTYPE: storage_device.get(CONF_SUBTYPE), - CONF_TOKEN: storage_device.get(CONF_TOKEN), - CONF_KEY: storage_device.get(CONF_KEY), - } - _LOGGER.debug( - f"Loaded configuration for device {device_id} from storage" - ) - return await self.async_step_manually() - else: - if CONF_ACCOUNT not in self.account.keys(): - self.account = self._load_account() - if CONF_ACCOUNT not in self.account.keys(): - return await self.async_step_login() - if self.session is None: - self.session = async_create_clientsession(self.hass) - if self.cloud is None: - self.cloud = get_midea_cloud( - self.account[CONF_SERVER], - self.session, - self.account[CONF_ACCOUNT], - self.account[CONF_PASSWORD], - ) - if not await self.cloud.login(): - return await self.async_step_login() - self.found_device = { - CONF_DEVICE_ID: device_id, - CONF_TYPE: device.get(CONF_TYPE), - CONF_PROTOCOL: device.get(CONF_PROTOCOL), - CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), - CONF_PORT: device.get(CONF_PORT), - CONF_MODEL: device.get(CONF_MODEL), - } - if device_info := await self.cloud.get_device_info(device_id): - self.found_device[CONF_NAME] = device_info.get("name") - self.found_device[CONF_SUBTYPE] = device_info.get("model_number") - if device.get(CONF_PROTOCOL) == 3: - if self.account[CONF_SERVER] == "美的美居": - _LOGGER.debug( - "Try to get the Token and the Key use the preset MSmartHome account" - ) - self.cloud = get_midea_cloud( - "MSmartHome", - self.session, - bytes.fromhex( - format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[1]), "X") - ).decode("ASCII"), - bytes.fromhex( - format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[2]), "X") - ).decode("ASCII"), - ) - if not await self.cloud.login(): - return await self.async_step_auto(error="preset_account") - keys = await self.cloud.get_keys(user_input[CONF_DEVICE]) - for method, key in keys.items(): - dm = MiedaDevice( - name="", - device_id=device_id, - device_type=device.get(CONF_TYPE), - ip_address=device.get(CONF_IP_ADDRESS), - port=device.get(CONF_PORT), - token=key["token"], - key=key["key"], - protocol=3, - model=device.get(CONF_MODEL), - subtype=0, - attributes={}, - ) - if dm.connect(refresh_status=False): - dm.close_socket() - self.found_device[CONF_TOKEN] = key["token"] - self.found_device[CONF_KEY] = key["key"] - return await self.async_step_manually() - return await self.async_step_auto(error="connect_error") - else: - return await self.async_step_manually() - - return self.async_show_form( - step_id="auto", - data_schema=vol.Schema( - { - vol.Required( - CONF_DEVICE, default=list(self.available_device.keys())[0] - ): vol.In(self.available_device), - } - ), - errors={"base": error} if error else None, - ) - - async def async_step_manually(self, user_input=None, error=None): - """ - async_step_manually - """ - if user_input is not None: - self.found_device = { - CONF_DEVICE_ID: user_input[CONF_DEVICE_ID], - CONF_TYPE: user_input[CONF_TYPE], - CONF_PROTOCOL: user_input[CONF_PROTOCOL], - CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS], - CONF_PORT: user_input[CONF_PORT], - CONF_MODEL: user_input[CONF_MODEL], - CONF_TOKEN: user_input[CONF_TOKEN], - CONF_KEY: user_input[CONF_KEY], - } - try: - bytearray.fromhex(user_input[CONF_TOKEN]) - bytearray.fromhex(user_input[CONF_KEY]) - except ValueError: - return await self.async_step_manually(error="invalid_token") - if user_input[CONF_PROTOCOL] == 3 and ( - len(user_input[CONF_TOKEN]) == 0 or len(user_input[CONF_KEY]) == 0 - ): - return await self.async_step_manually(error="invalid_token") - dm = MiedaDevice( - name="", - device_id=user_input[CONF_DEVICE_ID], - device_type=user_input[CONF_TYPE], - ip_address=user_input[CONF_IP_ADDRESS], - port=user_input[CONF_PORT], - token=user_input[CONF_TOKEN], - key=user_input[CONF_KEY], - protocol=user_input[CONF_PROTOCOL], - model=user_input[CONF_MODEL], - subtype=0, - attributes={}, - ) - if dm.connect(refresh_status=False): - dm.close_socket() - data = { - CONF_NAME: user_input[CONF_NAME], - CONF_DEVICE_ID: user_input[CONF_DEVICE_ID], - CONF_TYPE: user_input[CONF_TYPE], - CONF_PROTOCOL: user_input[CONF_PROTOCOL], - CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS], - CONF_PORT: user_input[CONF_PORT], - CONF_MODEL: user_input[CONF_MODEL], - CONF_SUBTYPE: user_input[CONF_SUBTYPE], - CONF_TOKEN: user_input[CONF_TOKEN], - CONF_KEY: user_input[CONF_KEY], - } - self._save_device_config(data) - return self.async_create_entry( - title=f"{user_input[CONF_NAME]}", data=data - ) - else: - return await self.async_step_manually(error="config_incorrect") - return self.async_show_form( - step_id="manually", - data_schema=vol.Schema( - { - vol.Required( - CONF_NAME, - default=( - self.found_device.get(CONF_NAME) - if self.found_device.get(CONF_NAME) - else self.supports.get(self.found_device.get(CONF_TYPE)) - ), - ): str, - vol.Required( - CONF_DEVICE_ID, default=self.found_device.get(CONF_DEVICE_ID) - ): int, - vol.Required( - CONF_TYPE, - default=( - self.found_device.get(CONF_TYPE) - if self.found_device.get(CONF_TYPE) - else 0xAC - ), - ): vol.In(self.supports), - vol.Required( - CONF_IP_ADDRESS, default=self.found_device.get(CONF_IP_ADDRESS) - ): str, - vol.Required( - CONF_PORT, - default=( - self.found_device.get(CONF_PORT) - if self.found_device.get(CONF_PORT) - else 6444 - ), - ): int, - vol.Required( - CONF_PROTOCOL, - default=( - self.found_device.get(CONF_PROTOCOL) - if self.found_device.get(CONF_PROTOCOL) - else 3 - ), - ): vol.In(PROTOCOLS), - vol.Required( - CONF_MODEL, - default=( - self.found_device.get(CONF_MODEL) - if self.found_device.get(CONF_MODEL) - else "Unknown" - ), - ): str, - vol.Required( - CONF_SUBTYPE, - default=( - self.found_device.get(CONF_SUBTYPE) - if self.found_device.get(CONF_SUBTYPE) - else 0 - ), - ): int, - vol.Optional( - CONF_TOKEN, - default=( - self.found_device.get(CONF_TOKEN) - if self.found_device.get(CONF_TOKEN) - else "" - ), - ): str, - vol.Optional( - CONF_KEY, - default=( - self.found_device.get(CONF_KEY) - if self.found_device.get(CONF_KEY) - else "" - ), - ): str, - } - ), - errors={"base": error} if error else None, - ) - - @staticmethod - @callback - def async_get_options_flow(config_entry): - """ - async_get_options_flow - """ - return OptionsFlowHandler(config_entry) - - -class OptionsFlowHandler(config_entries.OptionsFlow): - """ - OptionsFlowHandler - """ - - def __init__(self, config_entry: config_entries.ConfigEntry): - """ - __init__ - """ - self._config_entry = config_entry - self._device_type = config_entry.data.get(CONF_TYPE) - if self._device_type is None: - self._device_type = 0xAC - if CONF_SENSORS in self._config_entry.options: - for key in self._config_entry.options[CONF_SENSORS]: - if key not in MIDEA_DEVICES[self._device_type]["entities"]: - self._config_entry.options[CONF_SENSORS].remove(key) - if CONF_SWITCHES in self._config_entry.options: - for key in self._config_entry.options[CONF_SWITCHES]: - if key not in MIDEA_DEVICES[self._device_type]["entities"]: - self._config_entry.options[CONF_SWITCHES].remove(key) - - async def async_step_init(self, user_input=None): - """ - async_step_init - """ - if self._device_type == CONF_ACCOUNT: - return self.async_abort(reason="account_option") - if user_input is not None: - return self.async_create_entry(title="", data=user_input) - sensors = {} - switches = {} - for attribute, attribute_config in ( - MIDEA_DEVICES.get(self._device_type).get("entities").items() - ): - attribute_name = ( - attribute if isinstance(attribute, str) else attribute.value - ) - if attribute_config.get("type") in EXTRA_SENSOR: - sensors[attribute_name] = attribute_config.get("name") - elif attribute_config.get( - "type" - ) in EXTRA_CONTROL and not attribute_config.get("default"): - switches[attribute_name] = attribute_config.get("name") - ip_address = self._config_entry.options.get(CONF_IP_ADDRESS, None) - if ip_address is None: - ip_address = self._config_entry.data.get(CONF_IP_ADDRESS, None) - refresh_interval = self._config_entry.options.get(CONF_REFRESH_INTERVAL, 30) - extra_sensors = list( - set(sensors.keys()) & set(self._config_entry.options.get(CONF_SENSORS, [])) - ) - extra_switches = list( - set(switches.keys()) - & set(self._config_entry.options.get(CONF_SWITCHES, [])) - ) - customize = self._config_entry.options.get(CONF_CUSTOMIZE, "") - data_schema = vol.Schema( - { - vol.Required(CONF_IP_ADDRESS, default=ip_address): str, - vol.Required(CONF_REFRESH_INTERVAL, default=refresh_interval): int, - } - ) - if len(sensors) > 0: - data_schema = data_schema.extend( - { - vol.Required( - CONF_SENSORS, - default=extra_sensors, - ): cv.multi_select(sensors) - } - ) - if len(switches) > 0: - data_schema = data_schema.extend( - { - vol.Required( - CONF_SWITCHES, - default=extra_switches, - ): cv.multi_select(switches) - } - ) - data_schema = data_schema.extend( - { - vol.Optional( - CONF_CUSTOMIZE, - default=customize, - ): str - } - ) - - return self.async_show_form(step_id="init", data_schema=data_schema) +""" +config_flow.py +""" + +import os + +import voluptuous as vol + +try: + from homeassistant.helpers.json import save_json +except ImportError: + from homeassistant.util.json import save_json + +import logging + +import homeassistant.helpers.config_validation as cv +from homeassistant import config_entries +from homeassistant.const import ( + CONF_CUSTOMIZE, + CONF_DEVICE, + CONF_DEVICE_ID, + CONF_IP_ADDRESS, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_PROTOCOL, + CONF_SENSORS, + CONF_SWITCHES, + CONF_TOKEN, + CONF_TYPE, +) +from homeassistant.core import callback +from homeassistant.helpers.aiohttp_client import async_create_clientsession +from homeassistant.util.json import load_json + +from .const import ( + CONF_ACCOUNT, + CONF_KEY, + CONF_MODEL, + CONF_REFRESH_INTERVAL, + CONF_SERVER, + CONF_SUBTYPE, + DOMAIN, + EXTRA_CONTROL, + EXTRA_SENSOR, +) +from .midea.core.cloud import get_midea_cloud +from .midea.core.device import MiedaDevice +from .midea.core.discover import discover +from .midea_devices import MIDEA_DEVICES + +_LOGGER = logging.getLogger(__name__) + +ADD_WAY = { + "discovery": "Discover automatically", + "manually": "Configure manually", + "list": "List all appliances only", +} +PROTOCOLS = {1: "V1", 2: "V2", 3: "V3"} +STORAGE_PATH = f".storage/{DOMAIN}" + +SERVERS = { + 1: "MSmartHome", + 2: "美的美居", + 3: "Midea Air", + 4: "NetHome Plus", + 5: "Ariston Clima", +} + +PRESET_ACCOUNT = [ + 39182118275972017797890111985649342047468653967530949796945843010512, + 29406100301096535908214728322278519471982973450672552249652548883645, + 39182118275972017797890111985649342050088014265865102175083010656997, +] + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """ + ConfigFlow + """ + + available_device = [] + devices = {} + found_device = {} + supports = {} + unsorted = {} + account = {} + cloud = None + session = None + for device_type, device_info in MIDEA_DEVICES.items(): + unsorted[device_type] = device_info["name"] + + unsorted = sorted(unsorted.items(), key=lambda x: x[1]) + for item in unsorted: + supports[item[0]] = item[1] + + def _save_device_config(self, data: dict): + """ + _save_device_config + """ + os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True) + record_file = self.hass.config.path( + f"{STORAGE_PATH}/{data[CONF_DEVICE_ID]}.json" + ) + save_json(record_file, data) + + def _load_device_config(self, device_id): + """ + _load_device_config + """ + record_file = self.hass.config.path(f"{STORAGE_PATH}/{device_id}.json") + json_data = load_json(record_file, default={}) + return json_data + + def _save_account(self, account: dict): + """ + _save_account + """ + os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True) + record_file = self.hass.config.path(f"{STORAGE_PATH}/account.json") + account[CONF_PASSWORD] = format( + ( + int(account[CONF_ACCOUNT].encode("utf-8").hex(), 16) + ^ int(account[CONF_PASSWORD].encode("utf-8").hex(), 16) + ), + "x", + ) + save_json(record_file, account) + + def _load_account(self): + """ + _load_account + """ + record_file = self.hass.config.path(f"{STORAGE_PATH}/account.json") + json_data = load_json(record_file, default={}) + if CONF_ACCOUNT in json_data.keys(): + json_data[CONF_PASSWORD] = bytes.fromhex( + format( + ( + int(json_data[CONF_PASSWORD], 16) + ^ int(json_data[CONF_ACCOUNT].encode("utf-8").hex(), 16) + ), + "X", + ) + ).decode("UTF-8") + return json_data + + @staticmethod + def _check_storage_device(device: dict, storage_device: dict): + """ + _check_storage_device + """ + if storage_device.get(CONF_SUBTYPE) is None: + return False + if device.get(CONF_PROTOCOL) == 3 and ( + storage_device.get(CONF_TOKEN) is None + or storage_device.get(CONF_KEY) is None + ): + return False + return True + + def _already_configured(self, device_id, ip_address): + """ + _already_configured + """ + for entry in self._async_current_entries(): + if device_id == entry.data.get( + CONF_DEVICE_ID + ) or ip_address == entry.data.get(CONF_IP_ADDRESS): + return True + return False + + async def async_step_user(self, user_input=None, error=None): + """ + async_step_user + """ + if user_input is not None: + if user_input["action"] == "discovery": + return await self.async_step_discovery() + elif user_input["action"] == "manually": + self.found_device = {} + return await self.async_step_manually() + else: + return await self.async_step_list() + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + {vol.Required("action", default="discovery"): vol.In(ADD_WAY)} + ), + errors={"base": error} if error else None, + ) + + async def async_step_login(self, user_input=None, error=None): + """ + async_step_login + """ + if user_input is not None: + if self.session is None: + self.session = async_create_clientsession(self.hass) + if self.cloud is None: + self.cloud = get_midea_cloud( + session=self.session, + cloud_name=SERVERS[user_input[CONF_SERVER]], + account=user_input[CONF_ACCOUNT], + password=user_input[CONF_PASSWORD], + ) + if await self.cloud.login(): + self.account = { + CONF_ACCOUNT: user_input[CONF_ACCOUNT], + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_SERVER: SERVERS[user_input[CONF_SERVER]], + } + self._save_account(self.account) + return await self.async_step_auto() + else: + return await self.async_step_login(error="login_failed") + return self.async_show_form( + step_id="login", + data_schema=vol.Schema( + { + vol.Required(CONF_ACCOUNT): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_SERVER, default=1): vol.In(SERVERS), + } + ), + errors={"base": error} if error else None, + ) + + async def async_step_list(self, user_input=None, error=None): + """ + async_step_list + """ + all_devices = discover() + if len(all_devices) > 0: + table = ( + "Appliance code|Type|IP address|SN|Supported\n:--:|:--:|:--:|:--:|:--:" + ) + for device_id, device in all_devices.items(): + supported = device.get(CONF_TYPE) in self.supports.keys() + table += ( + f"\n{device_id}|{'%02X' % device.get(CONF_TYPE)}|{device.get(CONF_IP_ADDRESS)}|" + f"{device.get('sn')}|" + f"{'YES' if supported else 'NO'}" + ) + else: + table = "Not found" + return self.async_show_form( + step_id="list", + description_placeholders={"table": table}, + errors={"base": error} if error else None, + ) + + async def async_step_discovery(self, user_input=None, error=None): + """ + async_step_discovery + """ + if user_input is not None: + if user_input[CONF_IP_ADDRESS].lower() == "auto": + ip_address = None + else: + ip_address = user_input[CONF_IP_ADDRESS] + self.devices = discover(self.supports.keys(), ip_address=ip_address) + self.available_device = {} + for device_id, device in self.devices.items(): + if not self._already_configured(device_id, device.get(CONF_IP_ADDRESS)): + self.available_device[device_id] = ( + f"{device_id} ({self.supports.get(device.get(CONF_TYPE))})" + ) + if len(self.available_device) > 0: + return await self.async_step_auto() + else: + return await self.async_step_discovery(error="no_devices") + return self.async_show_form( + step_id="discovery", + data_schema=vol.Schema( + {vol.Required(CONF_IP_ADDRESS, default="auto"): str} + ), + errors={"base": error} if error else None, + ) + + async def async_step_auto(self, user_input=None, error=None): + """ + async_step_auto + """ + if user_input is not None: + device_id = user_input[CONF_DEVICE] + device = self.devices.get(device_id) + storage_device = self._load_device_config(device_id) + if self._check_storage_device(device, storage_device): + self.found_device = { + CONF_DEVICE_ID: device_id, + CONF_TYPE: device.get(CONF_TYPE), + CONF_PROTOCOL: device.get(CONF_PROTOCOL), + CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), + CONF_PORT: device.get(CONF_PORT), + CONF_MODEL: device.get(CONF_MODEL), + CONF_NAME: storage_device.get(CONF_NAME), + CONF_SUBTYPE: storage_device.get(CONF_SUBTYPE), + CONF_TOKEN: storage_device.get(CONF_TOKEN), + CONF_KEY: storage_device.get(CONF_KEY), + } + _LOGGER.debug( + f"Loaded configuration for device {device_id} from storage" + ) + return await self.async_step_manually() + else: + if CONF_ACCOUNT not in self.account.keys(): + self.account = self._load_account() + if CONF_ACCOUNT not in self.account.keys(): + return await self.async_step_login() + if self.session is None: + self.session = async_create_clientsession(self.hass) + if self.cloud is None: + self.cloud = get_midea_cloud( + self.account[CONF_SERVER], + self.session, + self.account[CONF_ACCOUNT], + self.account[CONF_PASSWORD], + ) + if not await self.cloud.login(): + return await self.async_step_login() + self.found_device = { + CONF_DEVICE_ID: device_id, + CONF_TYPE: device.get(CONF_TYPE), + CONF_PROTOCOL: device.get(CONF_PROTOCOL), + CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), + CONF_PORT: device.get(CONF_PORT), + CONF_MODEL: device.get(CONF_MODEL), + } + if device_info := await self.cloud.get_device_info(device_id): + self.found_device[CONF_NAME] = device_info.get("name") + self.found_device[CONF_SUBTYPE] = device_info.get("model_number") + if device.get(CONF_PROTOCOL) == 3: + if self.account[CONF_SERVER] == "美的美居": + _LOGGER.debug( + "Try to get the Token and the Key use the preset MSmartHome account" + ) + self.cloud = get_midea_cloud( + "MSmartHome", + self.session, + bytes.fromhex( + format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[1]), "X") + ).decode("ASCII"), + bytes.fromhex( + format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[2]), "X") + ).decode("ASCII"), + ) + if not await self.cloud.login(): + return await self.async_step_auto(error="preset_account") + keys = await self.cloud.get_keys(user_input[CONF_DEVICE]) + for method, key in keys.items(): + dm = MiedaDevice( + name="", + device_id=device_id, + device_type=device.get(CONF_TYPE), + ip_address=device.get(CONF_IP_ADDRESS), + port=device.get(CONF_PORT), + token=key["token"], + key=key["key"], + protocol=3, + model=device.get(CONF_MODEL), + subtype=0, + attributes={}, + ) + if dm.connect(refresh_status=False): + dm.close_socket() + self.found_device[CONF_TOKEN] = key["token"] + self.found_device[CONF_KEY] = key["key"] + return await self.async_step_manually() + return await self.async_step_auto(error="connect_error") + else: + return await self.async_step_manually() + + return self.async_show_form( + step_id="auto", + data_schema=vol.Schema( + { + vol.Required( + CONF_DEVICE, default=list(self.available_device.keys())[0] + ): vol.In(self.available_device), + } + ), + errors={"base": error} if error else None, + ) + + async def async_step_manually(self, user_input=None, error=None): + """ + async_step_manually + """ + if user_input is not None: + self.found_device = { + CONF_DEVICE_ID: user_input[CONF_DEVICE_ID], + CONF_TYPE: user_input[CONF_TYPE], + CONF_PROTOCOL: user_input[CONF_PROTOCOL], + CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS], + CONF_PORT: user_input[CONF_PORT], + CONF_MODEL: user_input[CONF_MODEL], + CONF_TOKEN: user_input[CONF_TOKEN], + CONF_KEY: user_input[CONF_KEY], + } + try: + bytearray.fromhex(user_input[CONF_TOKEN]) + bytearray.fromhex(user_input[CONF_KEY]) + except ValueError: + return await self.async_step_manually(error="invalid_token") + if user_input[CONF_PROTOCOL] == 3 and ( + len(user_input[CONF_TOKEN]) == 0 or len(user_input[CONF_KEY]) == 0 + ): + return await self.async_step_manually(error="invalid_token") + dm = MiedaDevice( + name="", + device_id=user_input[CONF_DEVICE_ID], + device_type=user_input[CONF_TYPE], + ip_address=user_input[CONF_IP_ADDRESS], + port=user_input[CONF_PORT], + token=user_input[CONF_TOKEN], + key=user_input[CONF_KEY], + protocol=user_input[CONF_PROTOCOL], + model=user_input[CONF_MODEL], + subtype=0, + attributes={}, + ) + if dm.connect(refresh_status=False): + dm.close_socket() + data = { + CONF_NAME: user_input[CONF_NAME], + CONF_DEVICE_ID: user_input[CONF_DEVICE_ID], + CONF_TYPE: user_input[CONF_TYPE], + CONF_PROTOCOL: user_input[CONF_PROTOCOL], + CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS], + CONF_PORT: user_input[CONF_PORT], + CONF_MODEL: user_input[CONF_MODEL], + CONF_SUBTYPE: user_input[CONF_SUBTYPE], + CONF_TOKEN: user_input[CONF_TOKEN], + CONF_KEY: user_input[CONF_KEY], + } + self._save_device_config(data) + return self.async_create_entry( + title=f"{user_input[CONF_NAME]}", data=data + ) + else: + return await self.async_step_manually(error="config_incorrect") + return self.async_show_form( + step_id="manually", + data_schema=vol.Schema( + { + vol.Required( + CONF_NAME, + default=( + self.found_device.get(CONF_NAME) + if self.found_device.get(CONF_NAME) + else self.supports.get(self.found_device.get(CONF_TYPE)) + ), + ): str, + vol.Required( + CONF_DEVICE_ID, default=self.found_device.get(CONF_DEVICE_ID) + ): int, + vol.Required( + CONF_TYPE, + default=( + self.found_device.get(CONF_TYPE) + if self.found_device.get(CONF_TYPE) + else 0xAC + ), + ): vol.In(self.supports), + vol.Required( + CONF_IP_ADDRESS, default=self.found_device.get(CONF_IP_ADDRESS) + ): str, + vol.Required( + CONF_PORT, + default=( + self.found_device.get(CONF_PORT) + if self.found_device.get(CONF_PORT) + else 6444 + ), + ): int, + vol.Required( + CONF_PROTOCOL, + default=( + self.found_device.get(CONF_PROTOCOL) + if self.found_device.get(CONF_PROTOCOL) + else 3 + ), + ): vol.In(PROTOCOLS), + vol.Required( + CONF_MODEL, + default=( + self.found_device.get(CONF_MODEL) + if self.found_device.get(CONF_MODEL) + else "Unknown" + ), + ): str, + vol.Required( + CONF_SUBTYPE, + default=( + self.found_device.get(CONF_SUBTYPE) + if self.found_device.get(CONF_SUBTYPE) + else 0 + ), + ): int, + vol.Optional( + CONF_TOKEN, + default=( + self.found_device.get(CONF_TOKEN) + if self.found_device.get(CONF_TOKEN) + else "" + ), + ): str, + vol.Optional( + CONF_KEY, + default=( + self.found_device.get(CONF_KEY) + if self.found_device.get(CONF_KEY) + else "" + ), + ): str, + } + ), + errors={"base": error} if error else None, + ) + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """ + async_get_options_flow + """ + return OptionsFlowHandler(config_entry) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """ + OptionsFlowHandler + """ + + def __init__(self, config_entry: config_entries.ConfigEntry): + """ + __init__ + """ + self._config_entry = config_entry + self._device_type = config_entry.data.get(CONF_TYPE) + if self._device_type is None: + self._device_type = 0xAC + if CONF_SENSORS in self._config_entry.options: + for key in self._config_entry.options[CONF_SENSORS]: + if key not in MIDEA_DEVICES[self._device_type]["entities"]: + self._config_entry.options[CONF_SENSORS].remove(key) + if CONF_SWITCHES in self._config_entry.options: + for key in self._config_entry.options[CONF_SWITCHES]: + if key not in MIDEA_DEVICES[self._device_type]["entities"]: + self._config_entry.options[CONF_SWITCHES].remove(key) + + async def async_step_init(self, user_input=None): + """ + async_step_init + """ + if self._device_type == CONF_ACCOUNT: + return self.async_abort(reason="account_option") + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + sensors = {} + switches = {} + for attribute, attribute_config in ( + MIDEA_DEVICES.get(self._device_type).get("entities").items() + ): + attribute_name = ( + attribute if isinstance(attribute, str) else attribute.value + ) + if attribute_config.get("type") in EXTRA_SENSOR: + sensors[attribute_name] = attribute_config.get("name") + elif attribute_config.get( + "type" + ) in EXTRA_CONTROL and not attribute_config.get("default"): + switches[attribute_name] = attribute_config.get("name") + ip_address = self._config_entry.options.get(CONF_IP_ADDRESS, None) + if ip_address is None: + ip_address = self._config_entry.data.get(CONF_IP_ADDRESS, None) + refresh_interval = self._config_entry.options.get(CONF_REFRESH_INTERVAL, 30) + extra_sensors = list( + set(sensors.keys()) & set(self._config_entry.options.get(CONF_SENSORS, [])) + ) + extra_switches = list( + set(switches.keys()) + & set(self._config_entry.options.get(CONF_SWITCHES, [])) + ) + customize = self._config_entry.options.get(CONF_CUSTOMIZE, "") + data_schema = vol.Schema( + { + vol.Required(CONF_IP_ADDRESS, default=ip_address): str, + vol.Required(CONF_REFRESH_INTERVAL, default=refresh_interval): int, + } + ) + if len(sensors) > 0: + data_schema = data_schema.extend( + { + vol.Required( + CONF_SENSORS, + default=extra_sensors, + ): cv.multi_select(sensors) + } + ) + if len(switches) > 0: + data_schema = data_schema.extend( + { + vol.Required( + CONF_SWITCHES, + default=extra_switches, + ): cv.multi_select(switches) + } + ) + data_schema = data_schema.extend( + { + vol.Optional( + CONF_CUSTOMIZE, + default=customize, + ): str + } + ) + + return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/custom_components/midea_ac_lan/const.py b/custom_components/midea_ac_lan/const.py index bbebdc8d..85f76b50 100644 --- a/custom_components/midea_ac_lan/const.py +++ b/custom_components/midea_ac_lan/const.py @@ -1,25 +1,25 @@ -""" -const.py -""" - -from homeassistant.const import Platform - -DOMAIN = "midea_ac_lan" -COMPONENT = "component" -DEVICES = "devices" -CONF_KEY = "key" -CONF_MODEL = "model" -CONF_SUBTYPE = "subtype" -CONF_ACCOUNT = "account" -CONF_SERVER = "server" -CONF_REFRESH_INTERVAL = "refresh_interval" -EXTRA_SENSOR = [Platform.SENSOR, Platform.BINARY_SENSOR] -EXTRA_SWITCH = [Platform.SWITCH, Platform.LOCK, Platform.SELECT, Platform.NUMBER] -EXTRA_CONTROL = [ - Platform.CLIMATE, - Platform.WATER_HEATER, - Platform.FAN, - Platform.HUMIDIFIER, - Platform.LIGHT, -] + EXTRA_SWITCH -ALL_PLATFORM = EXTRA_SENSOR + EXTRA_CONTROL +""" +const.py +""" + +from homeassistant.const import Platform + +DOMAIN = "midea_ac_lan" +COMPONENT = "component" +DEVICES = "devices" +CONF_KEY = "key" +CONF_MODEL = "model" +CONF_SUBTYPE = "subtype" +CONF_ACCOUNT = "account" +CONF_SERVER = "server" +CONF_REFRESH_INTERVAL = "refresh_interval" +EXTRA_SENSOR = [Platform.SENSOR, Platform.BINARY_SENSOR] +EXTRA_SWITCH = [Platform.SWITCH, Platform.LOCK, Platform.SELECT, Platform.NUMBER] +EXTRA_CONTROL = [ + Platform.CLIMATE, + Platform.WATER_HEATER, + Platform.FAN, + Platform.HUMIDIFIER, + Platform.LIGHT, +] + EXTRA_SWITCH +ALL_PLATFORM = EXTRA_SENSOR + EXTRA_CONTROL diff --git a/custom_components/midea_ac_lan/fan.py b/custom_components/midea_ac_lan/fan.py index 853c8abe..c14d7a00 100644 --- a/custom_components/midea_ac_lan/fan.py +++ b/custom_components/midea_ac_lan/fan.py @@ -1,362 +1,362 @@ -""" -fan.py -""" - -import logging -from typing import Any - -from homeassistant.components.fan import FanEntity, FanEntityFeature -from homeassistant.const import ( - CONF_DEVICE_ID, - CONF_SWITCHES, - STATE_OFF, - STATE_ON, - Platform, -) - -from .const import DEVICES, DOMAIN -from .midea.devices.ac.device import DeviceAttributes as ACAttributes -from .midea.devices.ce.device import DeviceAttributes as CEAttributes -from .midea.devices.x40.device import DeviceAttributes as X40Attributes -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - -_LOGGER = logging.getLogger(__name__) - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """ - async_setup_entry - """ - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) - devs = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.FAN and ( - config.get("default") or entity_key in extra_switches - ): - if device.device_type == 0xFA: - devs.append(MideaFAFan(device, entity_key)) - elif device.device_type == 0xB6: - devs.append(MideaB6Fan(device, entity_key)) - elif device.device_type == 0xAC: - devs.append(MideaACFreshAirFan(device, entity_key)) - elif device.device_type == 0xCE: - devs.append(MideaCEFan(device, entity_key)) - elif device.device_type == 0x40: - devs.append(Midea40Fan(device, entity_key)) - async_add_entities(devs) - - -class MideaFan(MideaEntity, FanEntity): - """ - MideaFan - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - - def turn_on( - self, - percentage: int | None = None, - preset_mode: str | None = None, - **kwargs: Any, - ) -> None: - """ - turn_on - """ - if percentage: - fan_speed = int(percentage / self.percentage_step + 0.5) - else: - fan_speed = None - self._device.turn_on(fan_speed=fan_speed, mode=preset_mode) - - @property - def preset_modes(self): - """ - preset_modes - """ - return ( - self._device.preset_modes if hasattr(self._device, "preset_modes") else None - ) - - @property - def is_on(self) -> bool: - """ - is_on - """ - return self._device.get_attribute("power") - - @property - def oscillating(self): - """ - oscillating - """ - return self._device.get_attribute("oscillate") - - @property - def preset_mode(self): - """ - preset_mode - """ - return self._device.get_attribute("mode") - - @property - def fan_speed(self): - """ - fan_speed - """ - return self._device.get_attribute("fan_speed") - - def turn_off(self): - """ - turn_off - """ - self._device.set_attribute(attr="power", value=False) - - def toggle(self): - """ - toggle - """ - toggle = not self.is_on - self._device.set_attribute(attr="power", value=toggle) - - def oscillate(self, oscillating: bool): - """ - oscillate - """ - self._device.set_attribute(attr="oscillate", value=oscillating) - - def set_preset_mode(self, preset_mode: str): - """ - set_preset_mode - """ - self._device.set_attribute(attr="mode", value=preset_mode.capitalize()) - - @property - def percentage(self): - """ - percentage - """ - return round(self.fan_speed * self.percentage_step) - - def set_percentage(self, percentage: int): - """ - set_percentage - """ - fan_speed = round(percentage / self.percentage_step) - self._device.set_attribute(attr="fan_speed", value=fan_speed) - - async def async_set_percentage(self, percentage: int): - """ - async_set_percentage - """ - if percentage == 0: - await self.async_turn_off() - else: - await self.hass.async_add_executor_job(self.set_percentage, percentage) - - def update_state(self, status): - """ - update_state - """ - try: - self.schedule_update_ha_state() - except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) - - -class MideaFAFan(MideaFan): - """ - MideaFAFan - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED - | FanEntityFeature.OSCILLATE - | FanEntityFeature.PRESET_MODE - ) - self._attr_speed_count = self._device.speed_count - - -class MideaB6Fan(MideaFan): - """ - MideaB6Fan - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE - ) - self._attr_speed_count = self._device.speed_count - - -class MideaACFreshAirFan(MideaFan): - """ - MideaACFreshAirFan - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE - ) - self._attr_speed_count = 100 - - @property - def preset_modes(self): - """ - preset_modes - """ - return self._device.fresh_air_fan_speeds - - @property - def state(self): - """ - state - """ - return ( - STATE_ON - if self._device.get_attribute(ACAttributes.fresh_air_power) - else STATE_OFF - ) - - @property - def is_on(self) -> bool: - """ - is_on - """ - return self.state == STATE_ON - - @property - def fan_speed(self): - """ - fan_speed - """ - return self._device.get_attribute(ACAttributes.fresh_air_fan_speed) - - def turn_on(self, percentage, preset_mode, **kwargs): - """ - turn_on - """ - self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=True) - - def turn_off(self): - """ - turn_off - """ - self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=False) - - def toggle(self): - """ - toggle - """ - toggle = not self.is_on - self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=toggle) - - def set_percentage(self, percentage: int): - """ - set_percentage - """ - fan_speed = int(percentage / self.percentage_step + 0.5) - self._device.set_attribute( - attr=ACAttributes.fresh_air_fan_speed, value=fan_speed - ) - - def set_preset_mode(self, preset_mode: str): - """ - set_preset_mode - """ - self._device.set_attribute(attr=ACAttributes.fresh_air_mode, value=preset_mode) - - @property - def preset_mode(self): - """ - preset_mode - """ - return self._device.get_attribute(attr=ACAttributes.fresh_air_mode) - - -class MideaCEFan(MideaFan): - """ - MideaCEFan - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE - ) - self._attr_speed_count = self._device.speed_count - - def turn_on(self, percentage, preset_mode, **kwargs): - """ - turn_on - """ - self._device.set_attribute(attr=CEAttributes.power, value=True) - - async def async_set_percentage(self, percentage: int): - """ - async_set_percentage - """ - await self.hass.async_add_executor_job(self.set_percentage, percentage) - - -class Midea40Fan(MideaFan): - """ - Midea40Fan - """ - - def __init__(self, device, entity_key): - """ - __init__ - """ - super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE - ) - self._attr_speed_count = 2 - - @property - def state(self): - """ - state - """ - return ( - STATE_ON - if self._device.get_attribute(attr=X40Attributes.fan_speed) > 0 - else STATE_OFF - ) - - def turn_on(self, percentage, preset_mode, **kwargs): - """ - turn_on - """ - self._device.set_attribute(attr=X40Attributes.fan_speed, value=1) - - def turn_off(self): - """ - turn_off - """ - self._device.set_attribute(attr=X40Attributes.fan_speed, value=0) +""" +fan.py +""" + +import logging +from typing import Any + +from homeassistant.components.fan import FanEntity, FanEntityFeature +from homeassistant.const import ( + CONF_DEVICE_ID, + CONF_SWITCHES, + STATE_OFF, + STATE_ON, + Platform, +) + +from .const import DEVICES, DOMAIN +from .midea.devices.ac.device import DeviceAttributes as ACAttributes +from .midea.devices.ce.device import DeviceAttributes as CEAttributes +from .midea.devices.x40.device import DeviceAttributes as X40Attributes +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """ + async_setup_entry + """ + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + devs = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.FAN and ( + config.get("default") or entity_key in extra_switches + ): + if device.device_type == 0xFA: + devs.append(MideaFAFan(device, entity_key)) + elif device.device_type == 0xB6: + devs.append(MideaB6Fan(device, entity_key)) + elif device.device_type == 0xAC: + devs.append(MideaACFreshAirFan(device, entity_key)) + elif device.device_type == 0xCE: + devs.append(MideaCEFan(device, entity_key)) + elif device.device_type == 0x40: + devs.append(Midea40Fan(device, entity_key)) + async_add_entities(devs) + + +class MideaFan(MideaEntity, FanEntity): + """ + MideaFan + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + + def turn_on( + self, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, + ) -> None: + """ + turn_on + """ + if percentage: + fan_speed = int(percentage / self.percentage_step + 0.5) + else: + fan_speed = None + self._device.turn_on(fan_speed=fan_speed, mode=preset_mode) + + @property + def preset_modes(self): + """ + preset_modes + """ + return ( + self._device.preset_modes if hasattr(self._device, "preset_modes") else None + ) + + @property + def is_on(self) -> bool: + """ + is_on + """ + return self._device.get_attribute("power") + + @property + def oscillating(self): + """ + oscillating + """ + return self._device.get_attribute("oscillate") + + @property + def preset_mode(self): + """ + preset_mode + """ + return self._device.get_attribute("mode") + + @property + def fan_speed(self): + """ + fan_speed + """ + return self._device.get_attribute("fan_speed") + + def turn_off(self): + """ + turn_off + """ + self._device.set_attribute(attr="power", value=False) + + def toggle(self): + """ + toggle + """ + toggle = not self.is_on + self._device.set_attribute(attr="power", value=toggle) + + def oscillate(self, oscillating: bool): + """ + oscillate + """ + self._device.set_attribute(attr="oscillate", value=oscillating) + + def set_preset_mode(self, preset_mode: str): + """ + set_preset_mode + """ + self._device.set_attribute(attr="mode", value=preset_mode.capitalize()) + + @property + def percentage(self): + """ + percentage + """ + return round(self.fan_speed * self.percentage_step) + + def set_percentage(self, percentage: int): + """ + set_percentage + """ + fan_speed = round(percentage / self.percentage_step) + self._device.set_attribute(attr="fan_speed", value=fan_speed) + + async def async_set_percentage(self, percentage: int): + """ + async_set_percentage + """ + if percentage == 0: + await self.async_turn_off() + else: + await self.hass.async_add_executor_job(self.set_percentage, percentage) + + def update_state(self, status): + """ + update_state + """ + try: + self.schedule_update_ha_state() + except Exception as e: + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) + + +class MideaFAFan(MideaFan): + """ + MideaFAFan + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED + | FanEntityFeature.OSCILLATE + | FanEntityFeature.PRESET_MODE + ) + self._attr_speed_count = self._device.speed_count + + +class MideaB6Fan(MideaFan): + """ + MideaB6Fan + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + ) + self._attr_speed_count = self._device.speed_count + + +class MideaACFreshAirFan(MideaFan): + """ + MideaACFreshAirFan + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + ) + self._attr_speed_count = 100 + + @property + def preset_modes(self): + """ + preset_modes + """ + return self._device.fresh_air_fan_speeds + + @property + def state(self): + """ + state + """ + return ( + STATE_ON + if self._device.get_attribute(ACAttributes.fresh_air_power) + else STATE_OFF + ) + + @property + def is_on(self) -> bool: + """ + is_on + """ + return self.state == STATE_ON + + @property + def fan_speed(self): + """ + fan_speed + """ + return self._device.get_attribute(ACAttributes.fresh_air_fan_speed) + + def turn_on(self, percentage, preset_mode, **kwargs): + """ + turn_on + """ + self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=True) + + def turn_off(self): + """ + turn_off + """ + self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=False) + + def toggle(self): + """ + toggle + """ + toggle = not self.is_on + self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=toggle) + + def set_percentage(self, percentage: int): + """ + set_percentage + """ + fan_speed = int(percentage / self.percentage_step + 0.5) + self._device.set_attribute( + attr=ACAttributes.fresh_air_fan_speed, value=fan_speed + ) + + def set_preset_mode(self, preset_mode: str): + """ + set_preset_mode + """ + self._device.set_attribute(attr=ACAttributes.fresh_air_mode, value=preset_mode) + + @property + def preset_mode(self): + """ + preset_mode + """ + return self._device.get_attribute(attr=ACAttributes.fresh_air_mode) + + +class MideaCEFan(MideaFan): + """ + MideaCEFan + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + ) + self._attr_speed_count = self._device.speed_count + + def turn_on(self, percentage, preset_mode, **kwargs): + """ + turn_on + """ + self._device.set_attribute(attr=CEAttributes.power, value=True) + + async def async_set_percentage(self, percentage: int): + """ + async_set_percentage + """ + await self.hass.async_add_executor_job(self.set_percentage, percentage) + + +class Midea40Fan(MideaFan): + """ + Midea40Fan + """ + + def __init__(self, device, entity_key): + """ + __init__ + """ + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE + ) + self._attr_speed_count = 2 + + @property + def state(self): + """ + state + """ + return ( + STATE_ON + if self._device.get_attribute(attr=X40Attributes.fan_speed) > 0 + else STATE_OFF + ) + + def turn_on(self, percentage, preset_mode, **kwargs): + """ + turn_on + """ + self._device.set_attribute(attr=X40Attributes.fan_speed, value=1) + + def turn_off(self): + """ + turn_off + """ + self._device.set_attribute(attr=X40Attributes.fan_speed, value=0) diff --git a/custom_components/midea_ac_lan/humidifier.py b/custom_components/midea_ac_lan/humidifier.py index 35988137..1027b585 100644 --- a/custom_components/midea_ac_lan/humidifier.py +++ b/custom_components/midea_ac_lan/humidifier.py @@ -1,109 +1,109 @@ -import logging - -from homeassistant.components.humidifier import ( - HumidifierDeviceClass, - HumidifierEntity, - HumidifierEntityFeature, -) -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - -_LOGGER = logging.getLogger(__name__) - - -async def async_setup_entry(hass, config_entry, async_add_entities): - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) - devs = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.HUMIDIFIER and ( - config.get("default") or entity_key in extra_switches - ): - if device.device_type == 0xA1: - devs.append(MideaA1Humidifier(device, entity_key)) - if device.device_type == 0xFD: - devs.append(MideaFDHumidifier(device, entity_key)) - async_add_entities(devs) - - -class MideaHumidifier(MideaEntity, HumidifierEntity): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - - @property - def target_humidity(self): - return self._device.get_attribute("target_humidity") - - @property - def mode(self): - return self._device.get_attribute("mode") - - @property - def available_modes(self): - return self._device.modes - - def set_humidity(self, humidity: int): - self._device.set_attribute("target_humidity", humidity) - - def set_mode(self, mode: str): - self._device.set_attribute("mode", mode) - - @property - def min_humidity(self): - return self._min_humidity - - @property - def max_humidity(self): - return self._max_humidity - - @property - def is_on(self): - return self._device.get_attribute(attr="power") - - def turn_on(self): - self._device.set_attribute(attr="power", value=True) - - def turn_off(self): - self._device.set_attribute(attr="power", value=False) - - def update_state(self, status): - try: - self.schedule_update_ha_state() - except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) - - -class MideaA1Humidifier(MideaHumidifier): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - self._min_humidity = 35 - self._max_humidity = 85 - - @property - def device_class(self): - return HumidifierDeviceClass.DEHUMIDIFIER - - @property - def supported_features(self): - return HumidifierEntityFeature.MODES - - -class MideaFDHumidifier(MideaHumidifier): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - self._min_humidity = 35 - self._max_humidity = 85 - - @property - def device_class(self): - return HumidifierDeviceClass.HUMIDIFIER - - @property - def supported_features(self): - return HumidifierEntityFeature.MODES +import logging + +from homeassistant.components.humidifier import ( + HumidifierDeviceClass, + HumidifierEntity, + HumidifierEntityFeature, +) +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + devs = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.HUMIDIFIER and ( + config.get("default") or entity_key in extra_switches + ): + if device.device_type == 0xA1: + devs.append(MideaA1Humidifier(device, entity_key)) + if device.device_type == 0xFD: + devs.append(MideaFDHumidifier(device, entity_key)) + async_add_entities(devs) + + +class MideaHumidifier(MideaEntity, HumidifierEntity): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + + @property + def target_humidity(self): + return self._device.get_attribute("target_humidity") + + @property + def mode(self): + return self._device.get_attribute("mode") + + @property + def available_modes(self): + return self._device.modes + + def set_humidity(self, humidity: int): + self._device.set_attribute("target_humidity", humidity) + + def set_mode(self, mode: str): + self._device.set_attribute("mode", mode) + + @property + def min_humidity(self): + return self._min_humidity + + @property + def max_humidity(self): + return self._max_humidity + + @property + def is_on(self): + return self._device.get_attribute(attr="power") + + def turn_on(self): + self._device.set_attribute(attr="power", value=True) + + def turn_off(self): + self._device.set_attribute(attr="power", value=False) + + def update_state(self, status): + try: + self.schedule_update_ha_state() + except Exception as e: + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) + + +class MideaA1Humidifier(MideaHumidifier): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + self._min_humidity = 35 + self._max_humidity = 85 + + @property + def device_class(self): + return HumidifierDeviceClass.DEHUMIDIFIER + + @property + def supported_features(self): + return HumidifierEntityFeature.MODES + + +class MideaFDHumidifier(MideaHumidifier): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + self._min_humidity = 35 + self._max_humidity = 85 + + @property + def device_class(self): + return HumidifierDeviceClass.HUMIDIFIER + + @property + def supported_features(self): + return HumidifierEntityFeature.MODES diff --git a/custom_components/midea_ac_lan/lock.py b/custom_components/midea_ac_lan/lock.py index e846ece3..f62eeb54 100644 --- a/custom_components/midea_ac_lan/lock.py +++ b/custom_components/midea_ac_lan/lock.py @@ -1,34 +1,34 @@ -from homeassistant.components.lock import LockEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - - -async def async_setup_entry(hass, config_entry, async_add_entities): - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) - locks = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.LOCK and entity_key in extra_switches: - dev = MideaLock(device, entity_key) - locks.append(dev) - async_add_entities(locks) - - -class MideaLock(MideaEntity, LockEntity): - - @property - def is_locked(self): - return self._device.get_attribute(self._entity_key) - - def lock(self, **kwargs) -> None: - self._device.set_attribute(attr=self._entity_key, value=True) - - def unlock(self, **kwargs) -> None: - self._device.set_attribute(attr=self._entity_key, value=False) - - def open(self, **kwargs) -> None: - self.async_unlock() +from homeassistant.components.lock import LockEntity +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry(hass, config_entry, async_add_entities): + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + locks = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.LOCK and entity_key in extra_switches: + dev = MideaLock(device, entity_key) + locks.append(dev) + async_add_entities(locks) + + +class MideaLock(MideaEntity, LockEntity): + + @property + def is_locked(self): + return self._device.get_attribute(self._entity_key) + + def lock(self, **kwargs) -> None: + self._device.set_attribute(attr=self._entity_key, value=True) + + def unlock(self, **kwargs) -> None: + self._device.set_attribute(attr=self._entity_key, value=False) + + def open(self, **kwargs) -> None: + self.async_unlock() diff --git a/custom_components/midea_ac_lan/manifest.json b/custom_components/midea_ac_lan/manifest.json index 40fad58c..88ab0214 100644 --- a/custom_components/midea_ac_lan/manifest.json +++ b/custom_components/midea_ac_lan/manifest.json @@ -1,17 +1,17 @@ -{ - "domain": "midea_ac_lan", - "name": "Midea AC LAN", - "codeowners": [ - "@wuwentao" - ], - "config_flow": true, - "dependencies": [], - "documentation": "https://github.com/wuwentao/midea_ac_lan#readme", - "integration_type": "device", - "iot_class": "local_push", - "issue_tracker": "https://github.com/wuwentao/midea_ac_lan/issues", - "requirements": [ - "pycryptodome" - ], - "version": "v0.3.23" -} +{ + "domain": "midea_ac_lan", + "name": "Midea AC LAN", + "codeowners": [ + "@wuwentao" + ], + "config_flow": true, + "dependencies": [], + "documentation": "https://github.com/wuwentao/midea_ac_lan#readme", + "integration_type": "device", + "iot_class": "local_push", + "issue_tracker": "https://github.com/wuwentao/midea_ac_lan/issues", + "requirements": [ + "pycryptodome" + ], + "version": "v0.3.23" +} diff --git a/custom_components/midea_ac_lan/midea/backports/enum.py b/custom_components/midea_ac_lan/midea/backports/enum.py index 6a6c1b0c..c4de8762 100644 --- a/custom_components/midea_ac_lan/midea/backports/enum.py +++ b/custom_components/midea_ac_lan/midea/backports/enum.py @@ -1,35 +1,35 @@ -"""Enum backports from standard lib.""" - -from __future__ import annotations - -from enum import Enum -from typing import Any, TypeVar - -_StrEnumSelfT = TypeVar("_StrEnumSelfT", bound="StrEnum") - - -class StrEnum(str, Enum): - """Partial backport of Python 3.11's StrEnum for our basic use cases.""" - - def __new__( - cls: type[_StrEnumSelfT], value: str, *args: Any, **kwargs: Any - ) -> _StrEnumSelfT: - """Create a new StrEnum instance.""" - if not isinstance(value, str): - raise TypeError(f"{value!r} is not a string") - return super().__new__(cls, value, *args, **kwargs) - - def __str__(self) -> str: - """Return self.value.""" - return str(self.value) - - @staticmethod - def _generate_next_value_( - name: str, start: int, count: int, last_values: list[Any] - ) -> Any: - """ - Make `auto()` explicitly unsupported. - We may revisit this when it's very clear that Python 3.11's - `StrEnum.auto()` behavior will no longer change. - """ - raise TypeError("auto() is not supported by this implementation") +"""Enum backports from standard lib.""" + +from __future__ import annotations + +from enum import Enum +from typing import Any, TypeVar + +_StrEnumSelfT = TypeVar("_StrEnumSelfT", bound="StrEnum") + + +class StrEnum(str, Enum): + """Partial backport of Python 3.11's StrEnum for our basic use cases.""" + + def __new__( + cls: type[_StrEnumSelfT], value: str, *args: Any, **kwargs: Any + ) -> _StrEnumSelfT: + """Create a new StrEnum instance.""" + if not isinstance(value, str): + raise TypeError(f"{value!r} is not a string") + return super().__new__(cls, value, *args, **kwargs) + + def __str__(self) -> str: + """Return self.value.""" + return str(self.value) + + @staticmethod + def _generate_next_value_( + name: str, start: int, count: int, last_values: list[Any] + ) -> Any: + """ + Make `auto()` explicitly unsupported. + We may revisit this when it's very clear that Python 3.11's + `StrEnum.auto()` behavior will no longer change. + """ + raise TypeError("auto() is not supported by this implementation") diff --git a/custom_components/midea_ac_lan/midea/core/cloud.py b/custom_components/midea_ac_lan/midea/core/cloud.py index f686fabb..f4eb2eb0 100644 --- a/custom_components/midea_ac_lan/midea/core/cloud.py +++ b/custom_components/midea_ac_lan/midea/core/cloud.py @@ -1,667 +1,667 @@ -import base64 -import datetime -import json -import logging -import time -from secrets import token_hex -from threading import Lock - -from aiohttp import ClientSession - -from .security import ( - CloudSecurity, - MeijuCloudSecurity, - MideaAirSecurity, - MSmartCloudSecurity, -) - -_LOGGER = logging.getLogger(__name__) - -clouds = { - "美的美居": { - "class_name": "MeijuCloud", - "app_id": "900", - "app_key": "46579c15", - "login_key": "ad0ee21d48a64bf49f4fb583ab76e799", - "iot_key": bytes.fromhex( - format(9795516279659324117647275084689641883661667, "x") - ).decode(), - "hmac_key": bytes.fromhex( - format(117390035944627627450677220413733956185864939010425, "x") - ).decode(), - "api_url": "https://mp-prod.smartmidea.net/mas/v5/app/proxy?alias=", - }, - "MSmartHome": { - "class_name": "MSmartHomeCloud", - "app_id": "1010", - "app_key": "ac21b9f9cbfe4ca5a88562ef25e2b768", - "iot_key": bytes.fromhex(format(7882822598523843940, "x")).decode(), - "hmac_key": bytes.fromhex( - format(117390035944627627450677220413733956185864939010425, "x") - ).decode(), - "api_url": "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=", - }, - "Midea Air": { - "class_name": "MideaAirCloud", - "app_id": "1117", - "app_key": "ff0cf6f5f0c3471de36341cab3f7a9af", - "api_url": "https://mapp.appsmb.com", - }, - "NetHome Plus": { - "class_name": "MideaAirCloud", - "app_id": "1017", - "app_key": "3742e9e5842d4ad59c2db887e12449f9", - "api_url": "https://mapp.appsmb.com", - }, - "Ariston Clima": { - "class_name": "MideaAirCloud", - "app_id": "1005", - "app_key": "434a209a5ce141c3b726de067835d7f0", - "api_url": "https://mapp.appsmb.com", - }, -} - -default_keys = { - 99: { - "token": "ee755a84a115703768bcc7c6c13d3d629aa416f1e2fd798beb9f78cbb1381d09" - "1cc245d7b063aad2a900e5b498fbd936c811f5d504b2e656d4f33b3bbc6d1da3", - "key": "ed37bd31558a4b039aaf4e7a7a59aa7a75fd9101682045f69baf45d28380ae5c", - } -} - - -class MideaCloud: - def __init__( - self, - session: ClientSession, - security: CloudSecurity, - app_id: str, - app_key: str, - account: str, - password: str, - api_url: str, - ): - self._device_id = CloudSecurity.get_deviceid(account) - self._session = session - self._security = security - self._api_lock = Lock() - self._app_id = app_id - self._app_key = app_key - self._account = account - self._password = password - self._api_url = api_url - self._access_token = None - self._uid = None - self._login_id = None - - def _make_general_data(self): - return {} - - async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: - header = header or {} - if not data.get("reqId"): - data.update({"reqId": token_hex(16)}) - if not data.get("stamp"): - data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) - random = str(int(time.time())) - url = self._api_url + endpoint - dump_data = json.dumps(data) - sign = self._security.sign("", dump_data, random) - header.update( - { - "content-type": "application/json; charset=utf-8", - "secretVersion": "1", - "sign": sign, - "random": random, - } - ) - if self._uid is not None: - header.update({"uid": self._uid}) - if self._access_token is not None: - header.update({"accessToken": self._access_token}) - response: dict = {"code": -1} - for i in range(0, 3): - try: - with self._api_lock: - r = await self._session.request( - "POST", url, headers=header, data=dump_data, timeout=10 - ) - raw = await r.read() - _LOGGER.debug( - f"Midea cloud API url: {url}, data: {data}, response: {raw}" - ) - response = json.loads(raw) - break - except Exception as e: - _LOGGER.warning(f"Midea cloud API error, url: {url}, error: {repr(e)}") - if int(response["code"]) == 0 and "data" in response: - return response["data"] - return None - - async def _get_login_id(self) -> str | None: - data = self._make_general_data() - data.update({"loginAccount": f"{self._account}"}) - if response := await self._api_request( - endpoint="/v1/user/login/id/get", data=data - ): - return response.get("loginId") - return None - - async def login(self) -> bool: - raise NotImplementedError() - - async def get_keys(self, appliance_id: int): - result = {} - for method in [1, 2]: - udp_id = self._security.get_udp_id(appliance_id, method) - data = self._make_general_data() - data.update({"udpid": udp_id}) - response = await self._api_request( - endpoint="/v1/iot/secure/getToken", data=data - ) - if response and "tokenlist" in response: - for token in response["tokenlist"]: - if token["udpId"] == udp_id: - result[method] = { - "token": token["token"].lower(), - "key": token["key"].lower(), - } - result.update(default_keys) - return result - - async def list_home(self) -> dict | None: - return {1: "My home"} - - async def list_appliances(self, home_id) -> dict | None: - raise NotImplementedError() - - async def get_device_info(self, device_id: int): - if response := await self.list_appliances(home_id=None): - if device_id in response.keys(): - return response[device_id] - return None - - async def download_lua( - self, - path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", - ): - raise NotImplementedError() - - -class MeijuCloud(MideaCloud): - def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, - ): - super().__init__( - session=session, - security=MeijuCloudSecurity( - login_key=clouds[cloud_name]["login_key"], - iot_key=clouds[cloud_name]["iot_key"], - hmac_key=clouds[cloud_name]["hmac_key"], - ), - app_id=clouds[cloud_name]["app_id"], - app_key=clouds[cloud_name]["app_key"], - account=account, - password=password, - api_url=clouds[cloud_name]["api_url"], - ) - - async def login(self) -> bool: - if login_id := await self._get_login_id(): - self._login_id = login_id - stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - data = { - "iotData": { - "clientType": 1, - "deviceId": self._device_id, - "iampwd": self._security.encrypt_iam_password( - self._login_id, self._password - ), - "iotAppId": self._app_id, - "loginAccount": self._account, - "password": self._security.encrypt_password( - self._login_id, self._password - ), - "reqId": token_hex(16), - "stamp": stamp, - }, - "data": { - "appKey": self._app_key, - "deviceId": self._device_id, - "platform": 2, - }, - "timestamp": stamp, - "stamp": stamp, - } - if response := await self._api_request( - endpoint="/mj/user/login", data=data - ): - self._access_token = response["mdata"]["accessToken"] - self._security.set_aes_keys( - self._security.aes_decrypt_with_fixed_key(response["key"]), None - ) - - return True - return False - - async def list_home(self): - if response := await self._api_request( - endpoint="/v1/homegroup/list/get", data={} - ): - homes = {} - for home in response["homeList"]: - homes.update({int(home["homegroupId"]): home["name"]}) - return homes - return None - - async def list_appliances(self, home_id) -> dict | None: - data = {"homegroupId": home_id} - if response := await self._api_request( - endpoint="/v1/appliance/home/list/get", data=data - ): - appliances = {} - for home in response.get("homeList") or []: - for room in home.get("roomList") or []: - for appliance in room.get("applianceList"): - try: - model_number = int(appliance.get("modelNumber", 0)) - except ValueError: - model_number = 0 - device_info = { - "name": appliance.get("name"), - "type": int(appliance.get("type"), 16), - "sn": ( - self._security.aes_decrypt(appliance.get("sn")) - if appliance.get("sn") - else "" - ), - "sn8": appliance.get("sn8", "00000000"), - "model_number": model_number, - "manufacturer_code": appliance.get( - "enterpriseCode", "0000" - ), - "model": appliance.get("productModel"), - "online": appliance.get("onlineStatus") == "1", - } - if ( - device_info.get("sn8") is None - or len(device_info.get("sn8")) == 0 - ): - device_info["sn8"] = "00000000" - if ( - device_info.get("model") is None - or len(device_info.get("model")) == 0 - ): - device_info["model"] = device_info["sn8"] - appliances[int(appliance["applianceCode"])] = device_info - return appliances - return None - - async def get_device_info(self, device_id: int): - data = {"applianceCode": device_id} - if response := await self._api_request( - endpoint="/v1/appliance/info/get", data=data - ): - try: - model_number = int(response.get("modelNumber", 0)) - except ValueError: - model_number = 0 - device_info = { - "name": response.get("name"), - "type": int(response.get("type"), 16), - "sn": ( - self._security.aes_decrypt(response.get("sn")) - if response.get("sn") - else "" - ), - "sn8": response.get("sn8", "00000000"), - "model_number": model_number, - "manufacturer_code": response.get("enterpriseCode", "0000"), - "model": response.get("productModel"), - "online": response.get("onlineStatus") == "1", - } - if device_info.get("sn8") is None or len(device_info.get("sn8")) == 0: - device_info["sn8"] = "00000000" - if device_info.get("model") is None or len(device_info.get("model")) == 0: - device_info["model"] = device_info["sn8"] - return device_info - return None - - async def download_lua( - self, - path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", - ): - data = { - "applianceSn": sn, - "applianceType": "0x%02X" % device_type, - "applianceMFCode": manufacturer_code, - "version": "0", - "iotAppId": self._app_id, - } - fnm = None - if response := await self._api_request( - endpoint="/v1/appliance/protocol/lua/luaGet", data=data - ): - res = await self._session.get(response["url"]) - if res.status == 200: - lua = await res.text() - if lua: - stream = ( - 'local bit = require "bit"\n' - + self._security.aes_decrypt_with_fixed_key(lua) - ) - stream = stream.replace("\r\n", "\n") - fnm = f"{path}/{response['fileName']}" - with open(fnm, "w") as fp: - fp.write(stream) - return fnm - - -class MSmartHomeCloud(MideaCloud): - def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, - ): - super().__init__( - session=session, - security=MSmartCloudSecurity( - login_key=clouds[cloud_name]["app_key"], - iot_key=clouds[cloud_name]["iot_key"], - hmac_key=clouds[cloud_name]["hmac_key"], - ), - app_id=clouds[cloud_name]["app_id"], - app_key=clouds[cloud_name]["app_key"], - account=account, - password=password, - api_url=clouds[cloud_name]["api_url"], - ) - self._auth_base = base64.b64encode( - f"{self._app_key}:{clouds['MSmartHome']['iot_key']}".encode("ascii") - ).decode("ascii") - - def _make_general_data(self): - return { - # "appVersion": self.APP_VERSION, - "src": self._app_id, - "format": "2", - "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"), - "platformId": "1", - "deviceId": self._device_id, - "reqId": token_hex(16), - "uid": self._uid, - "clientType": "1", - "appId": self._app_id, - } - - async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: - header = header or {} - header.update( - {"x-recipe-app": self._app_id, "authorization": f"Basic {self._auth_base}"} - ) - - return await super()._api_request(endpoint, data, header) - - async def _re_route(self): - data = self._make_general_data() - data.update({"userType": "0", "userName": f"{self._account}"}) - if response := await self._api_request( - endpoint="/v1/multicloud/platform/user/route", data=data - ): - if api_url := response.get("masUrl"): - self._api_url = api_url - - async def login(self) -> bool: - await self._re_route() - if login_id := await self._get_login_id(): - self._login_id = login_id - iot_data = self._make_general_data() - iot_data.pop("uid") - stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - iot_data.update( - { - "iampwd": self._security.encrypt_iam_password( - self._login_id, self._password - ), - "loginAccount": self._account, - "password": self._security.encrypt_password( - self._login_id, self._password - ), - "stamp": stamp, - } - ) - data = { - "iotData": iot_data, - "data": { - "appKey": self._app_key, - "deviceId": self._device_id, - "platform": "2", - }, - "stamp": stamp, - } - if response := await self._api_request( - endpoint="/mj/user/login", data=data - ): - self._uid = response["uid"] - self._access_token = response["mdata"]["accessToken"] - self._security.set_aes_keys( - response["accessToken"], response["randomData"] - ) - return True - return False - - async def list_appliances(self, home_id) -> dict | None: - data = self._make_general_data() - if response := await self._api_request( - endpoint="/v1/appliance/user/list/get", data=data - ): - appliances = {} - for appliance in response["list"]: - try: - model_number = int(appliance.get("modelNumber", 0)) - except ValueError: - model_number = 0 - device_info = { - "name": appliance.get("name"), - "type": int(appliance.get("type"), 16), - "sn": ( - self._security.aes_decrypt(appliance.get("sn")) - if appliance.get("sn") - else "" - ), - "sn8": "", - "model_number": model_number, - "manufacturer_code": appliance.get("enterpriseCode", "0000"), - "model": "", - "online": appliance.get("onlineStatus") == "1", - } - device_info["sn8"] = ( - device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" - ) - device_info["model"] = device_info.get("sn8") - appliances[int(appliance["id"])] = device_info - return appliances - return None - - async def download_lua( - self, - path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", - ): - data = { - "clientType": "1", - "appId": self._app_id, - "format": "2", - "deviceId": self._device_id, - "iotAppId": self._app_id, - "applianceMFCode": manufacturer_code, - "applianceType": "0x%02X" % device_type, - "modelNumber": model_number, - "applianceSn": self._security.aes_encrypt_with_fixed_key( - sn.encode("ascii") - ).hex(), - "version": "0", - "encryptedType ": "2", - } - fnm = None - if response := await self._api_request( - endpoint="/v2/luaEncryption/luaGet", data=data - ): - res = await self._session.get(response["url"]) - if res.status == 200: - lua = await res.text() - if lua: - stream = ( - 'local bit = require "bit"\n' - + self._security.aes_decrypt_with_fixed_key(lua) - ) - stream = stream.replace("\r\n", "\n") - fnm = f"{path}/{response['fileName']}" - with open(fnm, "w") as fp: - fp.write(stream) - return fnm - - -class MideaAirCloud(MideaCloud): - def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, - ): - super().__init__( - session=session, - security=MideaAirSecurity(login_key=clouds[cloud_name]["app_key"]), - app_id=clouds[cloud_name]["app_id"], - app_key=clouds[cloud_name]["app_key"], - account=account, - password=password, - api_url=clouds[cloud_name]["api_url"], - ) - self._session_id = None - - def _make_general_data(self): - data = { - "src": self._app_id, - "format": "2", - "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"), - "deviceId": self._device_id, - "reqId": token_hex(16), - "clientType": "1", - "appId": self._app_id, - } - if self._session_id is not None: - data.update({"sessionId": self._session_id}) - return data - - async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: - header = header or {} - if not data.get("reqId"): - data.update({"reqId": token_hex(16)}) - if not data.get("stamp"): - data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) - url = self._api_url + endpoint - - sign = self._security.sign(url, data, "") - data.update({"sign": sign}) - if self._uid is not None: - header.update({"uid": self._uid}) - if self._access_token is not None: - header.update({"accessToken": self._access_token}) - response: dict = {"code": -1} - for i in range(0, 3): - try: - with self._api_lock: - r = await self._session.request( - "POST", url, headers=header, data=data, timeout=10 - ) - raw = await r.read() - _LOGGER.debug( - f"Midea cloud API url: {url}, data: {data}, response: {raw}" - ) - response = json.loads(raw) - break - except Exception as e: - _LOGGER.warning(f"Midea cloud API error, url: {url}, error: {repr(e)}") - if int(response["errorCode"]) == 0 and "result" in response: - return response["result"] - return None - - async def login(self) -> bool: - if login_id := await self._get_login_id(): - self._login_id = login_id - data = self._make_general_data() - data.update( - { - "loginAccount": self._account, - "password": self._security.encrypt_password( - self._login_id, self._password - ), - } - ) - if response := await self._api_request( - endpoint="/v1/user/login", data=data - ): - self._access_token = response["accessToken"] - self._uid = response["userId"] - self._session_id = response["sessionId"] - return True - return False - - async def list_appliances(self, home_id) -> dict | None: - data = self._make_general_data() - if response := await self._api_request( - endpoint="/v1/appliance/user/list/get", data=data - ): - appliances = {} - for appliance in response["list"]: - try: - model_number = int(appliance.get("modelNumber", 0)) - except ValueError: - model_number = 0 - device_info = { - "name": appliance.get("name"), - "type": int(appliance.get("type"), 16), - "sn": appliance.get("sn"), - "sn8": "", - "model_number": model_number, - "manufacturer_code": appliance.get("enterpriseCode", "0000"), - "model": "", - "online": appliance.get("onlineStatus") == "1", - } - device_info["sn8"] = ( - device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" - ) - device_info["model"] = device_info.get("sn8") - appliances[int(appliance["id"])] = device_info - return appliances - return None - - -def get_midea_cloud( - cloud_name: str, session: ClientSession, account: str, password: str -) -> MideaCloud | None: - cloud = None - if cloud_name in clouds.keys(): - cloud = globals()[clouds[cloud_name]["class_name"]]( - cloud_name=cloud_name, session=session, account=account, password=password - ) - return cloud +import base64 +import datetime +import json +import logging +import time +from secrets import token_hex +from threading import Lock + +from aiohttp import ClientSession + +from .security import ( + CloudSecurity, + MeijuCloudSecurity, + MideaAirSecurity, + MSmartCloudSecurity, +) + +_LOGGER = logging.getLogger(__name__) + +clouds = { + "美的美居": { + "class_name": "MeijuCloud", + "app_id": "900", + "app_key": "46579c15", + "login_key": "ad0ee21d48a64bf49f4fb583ab76e799", + "iot_key": bytes.fromhex( + format(9795516279659324117647275084689641883661667, "x") + ).decode(), + "hmac_key": bytes.fromhex( + format(117390035944627627450677220413733956185864939010425, "x") + ).decode(), + "api_url": "https://mp-prod.smartmidea.net/mas/v5/app/proxy?alias=", + }, + "MSmartHome": { + "class_name": "MSmartHomeCloud", + "app_id": "1010", + "app_key": "ac21b9f9cbfe4ca5a88562ef25e2b768", + "iot_key": bytes.fromhex(format(7882822598523843940, "x")).decode(), + "hmac_key": bytes.fromhex( + format(117390035944627627450677220413733956185864939010425, "x") + ).decode(), + "api_url": "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=", + }, + "Midea Air": { + "class_name": "MideaAirCloud", + "app_id": "1117", + "app_key": "ff0cf6f5f0c3471de36341cab3f7a9af", + "api_url": "https://mapp.appsmb.com", + }, + "NetHome Plus": { + "class_name": "MideaAirCloud", + "app_id": "1017", + "app_key": "3742e9e5842d4ad59c2db887e12449f9", + "api_url": "https://mapp.appsmb.com", + }, + "Ariston Clima": { + "class_name": "MideaAirCloud", + "app_id": "1005", + "app_key": "434a209a5ce141c3b726de067835d7f0", + "api_url": "https://mapp.appsmb.com", + }, +} + +default_keys = { + 99: { + "token": "ee755a84a115703768bcc7c6c13d3d629aa416f1e2fd798beb9f78cbb1381d09" + "1cc245d7b063aad2a900e5b498fbd936c811f5d504b2e656d4f33b3bbc6d1da3", + "key": "ed37bd31558a4b039aaf4e7a7a59aa7a75fd9101682045f69baf45d28380ae5c", + } +} + + +class MideaCloud: + def __init__( + self, + session: ClientSession, + security: CloudSecurity, + app_id: str, + app_key: str, + account: str, + password: str, + api_url: str, + ): + self._device_id = CloudSecurity.get_deviceid(account) + self._session = session + self._security = security + self._api_lock = Lock() + self._app_id = app_id + self._app_key = app_key + self._account = account + self._password = password + self._api_url = api_url + self._access_token = None + self._uid = None + self._login_id = None + + def _make_general_data(self): + return {} + + async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: + header = header or {} + if not data.get("reqId"): + data.update({"reqId": token_hex(16)}) + if not data.get("stamp"): + data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) + random = str(int(time.time())) + url = self._api_url + endpoint + dump_data = json.dumps(data) + sign = self._security.sign("", dump_data, random) + header.update( + { + "content-type": "application/json; charset=utf-8", + "secretVersion": "1", + "sign": sign, + "random": random, + } + ) + if self._uid is not None: + header.update({"uid": self._uid}) + if self._access_token is not None: + header.update({"accessToken": self._access_token}) + response: dict = {"code": -1} + for i in range(0, 3): + try: + with self._api_lock: + r = await self._session.request( + "POST", url, headers=header, data=dump_data, timeout=10 + ) + raw = await r.read() + _LOGGER.debug( + f"Midea cloud API url: {url}, data: {data}, response: {raw}" + ) + response = json.loads(raw) + break + except Exception as e: + _LOGGER.warning(f"Midea cloud API error, url: {url}, error: {repr(e)}") + if int(response["code"]) == 0 and "data" in response: + return response["data"] + return None + + async def _get_login_id(self) -> str | None: + data = self._make_general_data() + data.update({"loginAccount": f"{self._account}"}) + if response := await self._api_request( + endpoint="/v1/user/login/id/get", data=data + ): + return response.get("loginId") + return None + + async def login(self) -> bool: + raise NotImplementedError() + + async def get_keys(self, appliance_id: int): + result = {} + for method in [1, 2]: + udp_id = self._security.get_udp_id(appliance_id, method) + data = self._make_general_data() + data.update({"udpid": udp_id}) + response = await self._api_request( + endpoint="/v1/iot/secure/getToken", data=data + ) + if response and "tokenlist" in response: + for token in response["tokenlist"]: + if token["udpId"] == udp_id: + result[method] = { + "token": token["token"].lower(), + "key": token["key"].lower(), + } + result.update(default_keys) + return result + + async def list_home(self) -> dict | None: + return {1: "My home"} + + async def list_appliances(self, home_id) -> dict | None: + raise NotImplementedError() + + async def get_device_info(self, device_id: int): + if response := await self.list_appliances(home_id=None): + if device_id in response.keys(): + return response[device_id] + return None + + async def download_lua( + self, + path: str, + device_type: int, + sn: str, + model_number: str | None, + manufacturer_code: str = "0000", + ): + raise NotImplementedError() + + +class MeijuCloud(MideaCloud): + def __init__( + self, + cloud_name: str, + session: ClientSession, + account: str, + password: str, + ): + super().__init__( + session=session, + security=MeijuCloudSecurity( + login_key=clouds[cloud_name]["login_key"], + iot_key=clouds[cloud_name]["iot_key"], + hmac_key=clouds[cloud_name]["hmac_key"], + ), + app_id=clouds[cloud_name]["app_id"], + app_key=clouds[cloud_name]["app_key"], + account=account, + password=password, + api_url=clouds[cloud_name]["api_url"], + ) + + async def login(self) -> bool: + if login_id := await self._get_login_id(): + self._login_id = login_id + stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + data = { + "iotData": { + "clientType": 1, + "deviceId": self._device_id, + "iampwd": self._security.encrypt_iam_password( + self._login_id, self._password + ), + "iotAppId": self._app_id, + "loginAccount": self._account, + "password": self._security.encrypt_password( + self._login_id, self._password + ), + "reqId": token_hex(16), + "stamp": stamp, + }, + "data": { + "appKey": self._app_key, + "deviceId": self._device_id, + "platform": 2, + }, + "timestamp": stamp, + "stamp": stamp, + } + if response := await self._api_request( + endpoint="/mj/user/login", data=data + ): + self._access_token = response["mdata"]["accessToken"] + self._security.set_aes_keys( + self._security.aes_decrypt_with_fixed_key(response["key"]), None + ) + + return True + return False + + async def list_home(self): + if response := await self._api_request( + endpoint="/v1/homegroup/list/get", data={} + ): + homes = {} + for home in response["homeList"]: + homes.update({int(home["homegroupId"]): home["name"]}) + return homes + return None + + async def list_appliances(self, home_id) -> dict | None: + data = {"homegroupId": home_id} + if response := await self._api_request( + endpoint="/v1/appliance/home/list/get", data=data + ): + appliances = {} + for home in response.get("homeList") or []: + for room in home.get("roomList") or []: + for appliance in room.get("applianceList"): + try: + model_number = int(appliance.get("modelNumber", 0)) + except ValueError: + model_number = 0 + device_info = { + "name": appliance.get("name"), + "type": int(appliance.get("type"), 16), + "sn": ( + self._security.aes_decrypt(appliance.get("sn")) + if appliance.get("sn") + else "" + ), + "sn8": appliance.get("sn8", "00000000"), + "model_number": model_number, + "manufacturer_code": appliance.get( + "enterpriseCode", "0000" + ), + "model": appliance.get("productModel"), + "online": appliance.get("onlineStatus") == "1", + } + if ( + device_info.get("sn8") is None + or len(device_info.get("sn8")) == 0 + ): + device_info["sn8"] = "00000000" + if ( + device_info.get("model") is None + or len(device_info.get("model")) == 0 + ): + device_info["model"] = device_info["sn8"] + appliances[int(appliance["applianceCode"])] = device_info + return appliances + return None + + async def get_device_info(self, device_id: int): + data = {"applianceCode": device_id} + if response := await self._api_request( + endpoint="/v1/appliance/info/get", data=data + ): + try: + model_number = int(response.get("modelNumber", 0)) + except ValueError: + model_number = 0 + device_info = { + "name": response.get("name"), + "type": int(response.get("type"), 16), + "sn": ( + self._security.aes_decrypt(response.get("sn")) + if response.get("sn") + else "" + ), + "sn8": response.get("sn8", "00000000"), + "model_number": model_number, + "manufacturer_code": response.get("enterpriseCode", "0000"), + "model": response.get("productModel"), + "online": response.get("onlineStatus") == "1", + } + if device_info.get("sn8") is None or len(device_info.get("sn8")) == 0: + device_info["sn8"] = "00000000" + if device_info.get("model") is None or len(device_info.get("model")) == 0: + device_info["model"] = device_info["sn8"] + return device_info + return None + + async def download_lua( + self, + path: str, + device_type: int, + sn: str, + model_number: str | None, + manufacturer_code: str = "0000", + ): + data = { + "applianceSn": sn, + "applianceType": "0x%02X" % device_type, + "applianceMFCode": manufacturer_code, + "version": "0", + "iotAppId": self._app_id, + } + fnm = None + if response := await self._api_request( + endpoint="/v1/appliance/protocol/lua/luaGet", data=data + ): + res = await self._session.get(response["url"]) + if res.status == 200: + lua = await res.text() + if lua: + stream = ( + 'local bit = require "bit"\n' + + self._security.aes_decrypt_with_fixed_key(lua) + ) + stream = stream.replace("\r\n", "\n") + fnm = f"{path}/{response['fileName']}" + with open(fnm, "w") as fp: + fp.write(stream) + return fnm + + +class MSmartHomeCloud(MideaCloud): + def __init__( + self, + cloud_name: str, + session: ClientSession, + account: str, + password: str, + ): + super().__init__( + session=session, + security=MSmartCloudSecurity( + login_key=clouds[cloud_name]["app_key"], + iot_key=clouds[cloud_name]["iot_key"], + hmac_key=clouds[cloud_name]["hmac_key"], + ), + app_id=clouds[cloud_name]["app_id"], + app_key=clouds[cloud_name]["app_key"], + account=account, + password=password, + api_url=clouds[cloud_name]["api_url"], + ) + self._auth_base = base64.b64encode( + f"{self._app_key}:{clouds['MSmartHome']['iot_key']}".encode("ascii") + ).decode("ascii") + + def _make_general_data(self): + return { + # "appVersion": self.APP_VERSION, + "src": self._app_id, + "format": "2", + "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"), + "platformId": "1", + "deviceId": self._device_id, + "reqId": token_hex(16), + "uid": self._uid, + "clientType": "1", + "appId": self._app_id, + } + + async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: + header = header or {} + header.update( + {"x-recipe-app": self._app_id, "authorization": f"Basic {self._auth_base}"} + ) + + return await super()._api_request(endpoint, data, header) + + async def _re_route(self): + data = self._make_general_data() + data.update({"userType": "0", "userName": f"{self._account}"}) + if response := await self._api_request( + endpoint="/v1/multicloud/platform/user/route", data=data + ): + if api_url := response.get("masUrl"): + self._api_url = api_url + + async def login(self) -> bool: + await self._re_route() + if login_id := await self._get_login_id(): + self._login_id = login_id + iot_data = self._make_general_data() + iot_data.pop("uid") + stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + iot_data.update( + { + "iampwd": self._security.encrypt_iam_password( + self._login_id, self._password + ), + "loginAccount": self._account, + "password": self._security.encrypt_password( + self._login_id, self._password + ), + "stamp": stamp, + } + ) + data = { + "iotData": iot_data, + "data": { + "appKey": self._app_key, + "deviceId": self._device_id, + "platform": "2", + }, + "stamp": stamp, + } + if response := await self._api_request( + endpoint="/mj/user/login", data=data + ): + self._uid = response["uid"] + self._access_token = response["mdata"]["accessToken"] + self._security.set_aes_keys( + response["accessToken"], response["randomData"] + ) + return True + return False + + async def list_appliances(self, home_id) -> dict | None: + data = self._make_general_data() + if response := await self._api_request( + endpoint="/v1/appliance/user/list/get", data=data + ): + appliances = {} + for appliance in response["list"]: + try: + model_number = int(appliance.get("modelNumber", 0)) + except ValueError: + model_number = 0 + device_info = { + "name": appliance.get("name"), + "type": int(appliance.get("type"), 16), + "sn": ( + self._security.aes_decrypt(appliance.get("sn")) + if appliance.get("sn") + else "" + ), + "sn8": "", + "model_number": model_number, + "manufacturer_code": appliance.get("enterpriseCode", "0000"), + "model": "", + "online": appliance.get("onlineStatus") == "1", + } + device_info["sn8"] = ( + device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" + ) + device_info["model"] = device_info.get("sn8") + appliances[int(appliance["id"])] = device_info + return appliances + return None + + async def download_lua( + self, + path: str, + device_type: int, + sn: str, + model_number: str | None, + manufacturer_code: str = "0000", + ): + data = { + "clientType": "1", + "appId": self._app_id, + "format": "2", + "deviceId": self._device_id, + "iotAppId": self._app_id, + "applianceMFCode": manufacturer_code, + "applianceType": "0x%02X" % device_type, + "modelNumber": model_number, + "applianceSn": self._security.aes_encrypt_with_fixed_key( + sn.encode("ascii") + ).hex(), + "version": "0", + "encryptedType ": "2", + } + fnm = None + if response := await self._api_request( + endpoint="/v2/luaEncryption/luaGet", data=data + ): + res = await self._session.get(response["url"]) + if res.status == 200: + lua = await res.text() + if lua: + stream = ( + 'local bit = require "bit"\n' + + self._security.aes_decrypt_with_fixed_key(lua) + ) + stream = stream.replace("\r\n", "\n") + fnm = f"{path}/{response['fileName']}" + with open(fnm, "w") as fp: + fp.write(stream) + return fnm + + +class MideaAirCloud(MideaCloud): + def __init__( + self, + cloud_name: str, + session: ClientSession, + account: str, + password: str, + ): + super().__init__( + session=session, + security=MideaAirSecurity(login_key=clouds[cloud_name]["app_key"]), + app_id=clouds[cloud_name]["app_id"], + app_key=clouds[cloud_name]["app_key"], + account=account, + password=password, + api_url=clouds[cloud_name]["api_url"], + ) + self._session_id = None + + def _make_general_data(self): + data = { + "src": self._app_id, + "format": "2", + "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"), + "deviceId": self._device_id, + "reqId": token_hex(16), + "clientType": "1", + "appId": self._app_id, + } + if self._session_id is not None: + data.update({"sessionId": self._session_id}) + return data + + async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: + header = header or {} + if not data.get("reqId"): + data.update({"reqId": token_hex(16)}) + if not data.get("stamp"): + data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) + url = self._api_url + endpoint + + sign = self._security.sign(url, data, "") + data.update({"sign": sign}) + if self._uid is not None: + header.update({"uid": self._uid}) + if self._access_token is not None: + header.update({"accessToken": self._access_token}) + response: dict = {"code": -1} + for i in range(0, 3): + try: + with self._api_lock: + r = await self._session.request( + "POST", url, headers=header, data=data, timeout=10 + ) + raw = await r.read() + _LOGGER.debug( + f"Midea cloud API url: {url}, data: {data}, response: {raw}" + ) + response = json.loads(raw) + break + except Exception as e: + _LOGGER.warning(f"Midea cloud API error, url: {url}, error: {repr(e)}") + if int(response["errorCode"]) == 0 and "result" in response: + return response["result"] + return None + + async def login(self) -> bool: + if login_id := await self._get_login_id(): + self._login_id = login_id + data = self._make_general_data() + data.update( + { + "loginAccount": self._account, + "password": self._security.encrypt_password( + self._login_id, self._password + ), + } + ) + if response := await self._api_request( + endpoint="/v1/user/login", data=data + ): + self._access_token = response["accessToken"] + self._uid = response["userId"] + self._session_id = response["sessionId"] + return True + return False + + async def list_appliances(self, home_id) -> dict | None: + data = self._make_general_data() + if response := await self._api_request( + endpoint="/v1/appliance/user/list/get", data=data + ): + appliances = {} + for appliance in response["list"]: + try: + model_number = int(appliance.get("modelNumber", 0)) + except ValueError: + model_number = 0 + device_info = { + "name": appliance.get("name"), + "type": int(appliance.get("type"), 16), + "sn": appliance.get("sn"), + "sn8": "", + "model_number": model_number, + "manufacturer_code": appliance.get("enterpriseCode", "0000"), + "model": "", + "online": appliance.get("onlineStatus") == "1", + } + device_info["sn8"] = ( + device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" + ) + device_info["model"] = device_info.get("sn8") + appliances[int(appliance["id"])] = device_info + return appliances + return None + + +def get_midea_cloud( + cloud_name: str, session: ClientSession, account: str, password: str +) -> MideaCloud | None: + cloud = None + if cloud_name in clouds.keys(): + cloud = globals()[clouds[cloud_name]["class_name"]]( + cloud_name=cloud_name, session=session, account=account, password=password + ) + return cloud diff --git a/custom_components/midea_ac_lan/midea/core/crc8.py b/custom_components/midea_ac_lan/midea/core/crc8.py index 3d56f2d2..8ce11bcf 100644 --- a/custom_components/midea_ac_lan/midea/core/crc8.py +++ b/custom_components/midea_ac_lan/midea/core/crc8.py @@ -1,270 +1,270 @@ -crc8_854_table = [ - 0x00, - 0x5E, - 0xBC, - 0xE2, - 0x61, - 0x3F, - 0xDD, - 0x83, - 0xC2, - 0x9C, - 0x7E, - 0x20, - 0xA3, - 0xFD, - 0x1F, - 0x41, - 0x9D, - 0xC3, - 0x21, - 0x7F, - 0xFC, - 0xA2, - 0x40, - 0x1E, - 0x5F, - 0x01, - 0xE3, - 0xBD, - 0x3E, - 0x60, - 0x82, - 0xDC, - 0x23, - 0x7D, - 0x9F, - 0xC1, - 0x42, - 0x1C, - 0xFE, - 0xA0, - 0xE1, - 0xBF, - 0x5D, - 0x03, - 0x80, - 0xDE, - 0x3C, - 0x62, - 0xBE, - 0xE0, - 0x02, - 0x5C, - 0xDF, - 0x81, - 0x63, - 0x3D, - 0x7C, - 0x22, - 0xC0, - 0x9E, - 0x1D, - 0x43, - 0xA1, - 0xFF, - 0x46, - 0x18, - 0xFA, - 0xA4, - 0x27, - 0x79, - 0x9B, - 0xC5, - 0x84, - 0xDA, - 0x38, - 0x66, - 0xE5, - 0xBB, - 0x59, - 0x07, - 0xDB, - 0x85, - 0x67, - 0x39, - 0xBA, - 0xE4, - 0x06, - 0x58, - 0x19, - 0x47, - 0xA5, - 0xFB, - 0x78, - 0x26, - 0xC4, - 0x9A, - 0x65, - 0x3B, - 0xD9, - 0x87, - 0x04, - 0x5A, - 0xB8, - 0xE6, - 0xA7, - 0xF9, - 0x1B, - 0x45, - 0xC6, - 0x98, - 0x7A, - 0x24, - 0xF8, - 0xA6, - 0x44, - 0x1A, - 0x99, - 0xC7, - 0x25, - 0x7B, - 0x3A, - 0x64, - 0x86, - 0xD8, - 0x5B, - 0x05, - 0xE7, - 0xB9, - 0x8C, - 0xD2, - 0x30, - 0x6E, - 0xED, - 0xB3, - 0x51, - 0x0F, - 0x4E, - 0x10, - 0xF2, - 0xAC, - 0x2F, - 0x71, - 0x93, - 0xCD, - 0x11, - 0x4F, - 0xAD, - 0xF3, - 0x70, - 0x2E, - 0xCC, - 0x92, - 0xD3, - 0x8D, - 0x6F, - 0x31, - 0xB2, - 0xEC, - 0x0E, - 0x50, - 0xAF, - 0xF1, - 0x13, - 0x4D, - 0xCE, - 0x90, - 0x72, - 0x2C, - 0x6D, - 0x33, - 0xD1, - 0x8F, - 0x0C, - 0x52, - 0xB0, - 0xEE, - 0x32, - 0x6C, - 0x8E, - 0xD0, - 0x53, - 0x0D, - 0xEF, - 0xB1, - 0xF0, - 0xAE, - 0x4C, - 0x12, - 0x91, - 0xCF, - 0x2D, - 0x73, - 0xCA, - 0x94, - 0x76, - 0x28, - 0xAB, - 0xF5, - 0x17, - 0x49, - 0x08, - 0x56, - 0xB4, - 0xEA, - 0x69, - 0x37, - 0xD5, - 0x8B, - 0x57, - 0x09, - 0xEB, - 0xB5, - 0x36, - 0x68, - 0x8A, - 0xD4, - 0x95, - 0xCB, - 0x29, - 0x77, - 0xF4, - 0xAA, - 0x48, - 0x16, - 0xE9, - 0xB7, - 0x55, - 0x0B, - 0x88, - 0xD6, - 0x34, - 0x6A, - 0x2B, - 0x75, - 0x97, - 0xC9, - 0x4A, - 0x14, - 0xF6, - 0xA8, - 0x74, - 0x2A, - 0xC8, - 0x96, - 0x15, - 0x4B, - 0xA9, - 0xF7, - 0xB6, - 0xE8, - 0x0A, - 0x54, - 0xD7, - 0x89, - 0x6B, - 0x35, -] - - -def calculate(data): - crc_value = 0 - for m in data: - k = crc_value ^ m - if k > 256: - k -= 256 - if k < 0: - k += 256 - crc_value = crc8_854_table[k] - return crc_value +crc8_854_table = [ + 0x00, + 0x5E, + 0xBC, + 0xE2, + 0x61, + 0x3F, + 0xDD, + 0x83, + 0xC2, + 0x9C, + 0x7E, + 0x20, + 0xA3, + 0xFD, + 0x1F, + 0x41, + 0x9D, + 0xC3, + 0x21, + 0x7F, + 0xFC, + 0xA2, + 0x40, + 0x1E, + 0x5F, + 0x01, + 0xE3, + 0xBD, + 0x3E, + 0x60, + 0x82, + 0xDC, + 0x23, + 0x7D, + 0x9F, + 0xC1, + 0x42, + 0x1C, + 0xFE, + 0xA0, + 0xE1, + 0xBF, + 0x5D, + 0x03, + 0x80, + 0xDE, + 0x3C, + 0x62, + 0xBE, + 0xE0, + 0x02, + 0x5C, + 0xDF, + 0x81, + 0x63, + 0x3D, + 0x7C, + 0x22, + 0xC0, + 0x9E, + 0x1D, + 0x43, + 0xA1, + 0xFF, + 0x46, + 0x18, + 0xFA, + 0xA4, + 0x27, + 0x79, + 0x9B, + 0xC5, + 0x84, + 0xDA, + 0x38, + 0x66, + 0xE5, + 0xBB, + 0x59, + 0x07, + 0xDB, + 0x85, + 0x67, + 0x39, + 0xBA, + 0xE4, + 0x06, + 0x58, + 0x19, + 0x47, + 0xA5, + 0xFB, + 0x78, + 0x26, + 0xC4, + 0x9A, + 0x65, + 0x3B, + 0xD9, + 0x87, + 0x04, + 0x5A, + 0xB8, + 0xE6, + 0xA7, + 0xF9, + 0x1B, + 0x45, + 0xC6, + 0x98, + 0x7A, + 0x24, + 0xF8, + 0xA6, + 0x44, + 0x1A, + 0x99, + 0xC7, + 0x25, + 0x7B, + 0x3A, + 0x64, + 0x86, + 0xD8, + 0x5B, + 0x05, + 0xE7, + 0xB9, + 0x8C, + 0xD2, + 0x30, + 0x6E, + 0xED, + 0xB3, + 0x51, + 0x0F, + 0x4E, + 0x10, + 0xF2, + 0xAC, + 0x2F, + 0x71, + 0x93, + 0xCD, + 0x11, + 0x4F, + 0xAD, + 0xF3, + 0x70, + 0x2E, + 0xCC, + 0x92, + 0xD3, + 0x8D, + 0x6F, + 0x31, + 0xB2, + 0xEC, + 0x0E, + 0x50, + 0xAF, + 0xF1, + 0x13, + 0x4D, + 0xCE, + 0x90, + 0x72, + 0x2C, + 0x6D, + 0x33, + 0xD1, + 0x8F, + 0x0C, + 0x52, + 0xB0, + 0xEE, + 0x32, + 0x6C, + 0x8E, + 0xD0, + 0x53, + 0x0D, + 0xEF, + 0xB1, + 0xF0, + 0xAE, + 0x4C, + 0x12, + 0x91, + 0xCF, + 0x2D, + 0x73, + 0xCA, + 0x94, + 0x76, + 0x28, + 0xAB, + 0xF5, + 0x17, + 0x49, + 0x08, + 0x56, + 0xB4, + 0xEA, + 0x69, + 0x37, + 0xD5, + 0x8B, + 0x57, + 0x09, + 0xEB, + 0xB5, + 0x36, + 0x68, + 0x8A, + 0xD4, + 0x95, + 0xCB, + 0x29, + 0x77, + 0xF4, + 0xAA, + 0x48, + 0x16, + 0xE9, + 0xB7, + 0x55, + 0x0B, + 0x88, + 0xD6, + 0x34, + 0x6A, + 0x2B, + 0x75, + 0x97, + 0xC9, + 0x4A, + 0x14, + 0xF6, + 0xA8, + 0x74, + 0x2A, + 0xC8, + 0x96, + 0x15, + 0x4B, + 0xA9, + 0xF7, + 0xB6, + 0xE8, + 0x0A, + 0x54, + 0xD7, + 0x89, + 0x6B, + 0x35, +] + + +def calculate(data): + crc_value = 0 + for m in data: + k = crc_value ^ m + if k > 256: + k -= 256 + if k < 0: + k += 256 + crc_value = crc8_854_table[k] + return crc_value diff --git a/custom_components/midea_ac_lan/midea/core/device.py b/custom_components/midea_ac_lan/midea/core/device.py index 85b2aa71..dc2f2c07 100644 --- a/custom_components/midea_ac_lan/midea/core/device.py +++ b/custom_components/midea_ac_lan/midea/core/device.py @@ -1,423 +1,423 @@ -import threading - -try: - from enum import StrEnum -except ImportError: - from ..backports.enum import StrEnum - -import logging -import socket -import time -from enum import IntEnum - -from .message import ( - MessageApplianceResponse, - MessageQueryAppliance, - MessageQuestCustom, - MessageType, -) -from .packet_builder import PacketBuilder -from .security import ( - MSGTYPE_ENCRYPTED_REQUEST, - MSGTYPE_HANDSHAKE_REQUEST, - LocalSecurity, -) - -_LOGGER = logging.getLogger(__name__) - - -class AuthException(Exception): - pass - - -class ResponseException(Exception): - pass - - -class RefreshFailed(Exception): - pass - - -class DeviceAttributes(StrEnum): - pass - - -class ParseMessageResult(IntEnum): - SUCCESS = 0 - PADDING = 1 - ERROR = 99 - - -class MiedaDevice(threading.Thread): - def __init__( - self, - name: str, - device_id: int, - device_type: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - attributes: dict, - ): - threading.Thread.__init__(self) - self._attributes = attributes if attributes else {} - self._socket = None - self._ip_address = ip_address - self._port = port - self._security = LocalSecurity() - self._token = bytes.fromhex(token) if token else None - self._key = bytes.fromhex(key) if key else None - self._buffer = b"" - self._device_name = name - self._device_id = device_id - self._device_type = device_type - self._protocol = protocol - self._model = model - self._subtype = subtype - self._protocol_version = 0 - self._updates = [] - self._unsupported_protocol = [] - self._is_run = False - self._available = True - self._appliance_query = True - self._refresh_interval = 30 - self._heartbeat_interval = 10 - self._default_refresh_interval = 30 - - @property - def name(self): - return self._device_name - - @property - def available(self): - return self._available - - @property - def device_id(self): - return self._device_id - - @property - def device_type(self): - return self._device_type - - @property - def model(self): - return self._model - - @property - def subtype(self): - return self._subtype - - @staticmethod - def fetch_v2_message(msg): - result = [] - while len(msg) > 0: - factual_msg_len = len(msg) - if factual_msg_len < 6: - break - alleged_msg_len = msg[4] + (msg[5] << 8) - if factual_msg_len >= alleged_msg_len: - result.append(msg[:alleged_msg_len]) - msg = msg[alleged_msg_len:] - else: - break - return result, msg - - def connect(self, refresh_status=True): - try: - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._socket.settimeout(10) - _LOGGER.debug( - f"[{self._device_id}] Connecting to {self._ip_address}:{self._port}" - ) - self._socket.connect((self._ip_address, self._port)) - _LOGGER.debug(f"[{self._device_id}] Connected") - if self._protocol == 3: - self.authenticate() - _LOGGER.debug(f"[{self._device_id}] Authentication success") - if refresh_status: - self.refresh_status(wait_response=True) - self.enable_device(True) - return True - except socket.timeout: - _LOGGER.debug(f"[{self._device_id}] Connection timed out") - except socket.error: - _LOGGER.debug(f"[{self._device_id}] Connection error") - except AuthException: - _LOGGER.debug(f"[{self._device_id}] Authentication failed") - except ResponseException: - _LOGGER.debug(f"[{self._device_id}] Unexpected response received") - except RefreshFailed: - _LOGGER.debug(f"[{self._device_id}] Refresh status is timed out") - except Exception as e: - _LOGGER.error( - f"[{self._device_id}] Unknown error: {e.__traceback__.tb_frame.f_globals['__file__']}, " - f"{e.__traceback__.tb_lineno}, {repr(e)}" - ) - self.enable_device(False) - return False - - def authenticate(self): - request = self._security.encode_8370(self._token, MSGTYPE_HANDSHAKE_REQUEST) - _LOGGER.debug(f"[{self._device_id}] Handshaking") - self._socket.send(request) - response = self._socket.recv(512) - if len(response) < 20: - raise AuthException() - response = response[8:72] - self._security.tcp_key(response, self._key) - - def send_message(self, data): - if self._protocol == 3: - self.send_message_v3(data, msg_type=MSGTYPE_ENCRYPTED_REQUEST) - else: - self.send_message_v2(data) - - def send_message_v2(self, data): - if self._socket is not None: - self._socket.send(data) - else: - _LOGGER.debug( - f"[{self._device_id}] Send failure, device disconnected, data: {data.hex()}" - ) - - def send_message_v3(self, data, msg_type=MSGTYPE_ENCRYPTED_REQUEST): - data = self._security.encode_8370(data, msg_type) - self.send_message_v2(data) - - def build_send(self, cmd): - data = cmd.serialize() - _LOGGER.debug(f"[{self._device_id}] Sending: {cmd}") - msg = PacketBuilder(self._device_id, data).finalize() - self.send_message(msg) - - def refresh_status(self, wait_response=False): - cmds: list = self.build_query() - if self._appliance_query: - cmds = [MessageQueryAppliance(self.device_type)] + cmds - error_count = 0 - for cmd in cmds: - if cmd.__class__.__name__ not in self._unsupported_protocol: - self.build_send(cmd) - if wait_response: - try: - while True: - msg = self._socket.recv(512) - if len(msg) == 0: - raise socket.error - result = self.parse_message(msg) - if result == ParseMessageResult.SUCCESS: - break - elif result == ParseMessageResult.PADDING: - continue - else: - raise ResponseException - except socket.timeout: - error_count += 1 - self._unsupported_protocol.append(cmd.__class__.__name__) - _LOGGER.debug( - f"[{self._device_id}] Does not supports " - f"the protocol {cmd.__class__.__name__}, ignored" - ) - except ResponseException: - error_count += 1 - else: - error_count += 1 - if error_count == len(cmds): - raise RefreshFailed - - def pre_process_message(self, msg): - if msg[9] == MessageType.query_appliance: - message = MessageApplianceResponse(msg) - self._appliance_query = False - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - self._protocol_version = message.protocol_version - _LOGGER.debug( - f"[{self._device_id}] Device protocol version: {self._protocol_version}" - ) - return False - return True - - def parse_message(self, msg): - if self._protocol == 3: - messages, self._buffer = self._security.decode_8370(self._buffer + msg) - else: - messages, self._buffer = self.fetch_v2_message(self._buffer + msg) - if len(messages) == 0: - return ParseMessageResult.PADDING - for message in messages: - if message == b"ERROR": - return ParseMessageResult.ERROR - payload_len = message[4] + (message[5] << 8) - 56 - payload_type = message[2] + (message[3] << 8) - if payload_type in [0x1001, 0x0001]: - # Heartbeat detected - pass - elif len(message) > 56: - cryptographic = message[40:-16] - if payload_len % 16 == 0: - decrypted = self._security.aes_decrypt(cryptographic) - try: - cont = True - if self._appliance_query: - cont = self.pre_process_message(decrypted) - if cont: - status = self.process_message(decrypted) - if len(status) > 0: - self.update_all(status) - else: - _LOGGER.debug( - f"[{self._device_id}] Unidentified protocol" - ) - except Exception as e: - _LOGGER.error( - f"[{self._device_id}] Error in process message, msg = {e}" - ) - else: - _LOGGER.warning( - f"[{self._device_id}] Illegal payload, " - f"original message = {msg.hex()}, buffer = {self._buffer.hex()}, " - f"8370 decoded = {message.hex()}, payload type = {payload_type}, " - f"alleged payload length = {payload_len}, factual payload length = {len(cryptographic)}" - ) - else: - _LOGGER.warning( - f"[{self._device_id}] Illegal message, " - f"original message = {msg.hex()}, buffer = {self._buffer.hex()}, " - f"8370 decoded = {message.hex()}, payload type = {payload_type}, " - f"alleged payload length = {payload_len}, message length = {len(message)}, " - ) - return ParseMessageResult.SUCCESS - - def build_query(self): - raise NotImplementedError - - def process_message(self, msg): - raise NotImplementedError - - def send_command(self, cmd_type, cmd_body: bytearray): - cmd = MessageQuestCustom( - self._device_type, self._protocol_version, cmd_type, cmd_body - ) - try: - self.build_send(cmd) - except socket.error as e: - _LOGGER.debug( - f"[{self._device_id}] Interface send_command failure, {repr(e)}, " - f"cmd_type: {cmd_type}, cmd_body: {cmd_body.hex()}" - ) - - def send_heartbeat(self): - msg = PacketBuilder(self._device_id, bytearray([0x00])).finalize(msg_type=0) - self.send_message(msg) - - def register_update(self, update): - self._updates.append(update) - - def update_all(self, status): - _LOGGER.debug(f"[{self._device_id}] Status update: {status}") - for update in self._updates: - update(status) - - def enable_device(self, available=True): - self._available = available - status = {"available": available} - self.update_all(status) - - def open(self): - if not self._is_run: - self._is_run = True - threading.Thread.start(self) - - def close(self): - if self._is_run: - self._is_run = False - self.close_socket() - - def close_socket(self): - self._unsupported_protocol = [] - self._buffer = b"" - if self._socket: - self._socket.close() - self._socket = None - - def set_ip_address(self, ip_address): - if self._ip_address != ip_address: - _LOGGER.debug(f"[{self._device_id}] Update IP address to {ip_address}") - self._ip_address = ip_address - self.close_socket() - - def set_refresh_interval(self, refresh_interval): - self._refresh_interval = refresh_interval - - def run(self): - while self._is_run: - while self._socket is None: - if self.connect(refresh_status=True) is False: - if not self._is_run: - return - self.close_socket() - time.sleep(5) - timeout_counter = 0 - start = time.time() - previous_refresh = start - previous_heartbeat = start - self._socket.settimeout(1) - while True: - try: - now = time.time() - if 0 < self._refresh_interval <= now - previous_refresh: - self.refresh_status() - previous_refresh = now - if now - previous_heartbeat >= self._heartbeat_interval: - self.send_heartbeat() - previous_heartbeat = now - msg = self._socket.recv(512) - msg_len = len(msg) - if msg_len == 0: - raise socket.error("Connection closed by peer") - result = self.parse_message(msg) - if result == ParseMessageResult.ERROR: - _LOGGER.debug(f"[{self._device_id}] Message 'ERROR' received") - self.close_socket() - break - elif result == ParseMessageResult.SUCCESS: - timeout_counter = 0 - except socket.timeout: - timeout_counter = timeout_counter + 1 - if timeout_counter >= 120: - _LOGGER.debug(f"[{self._device_id}] Heartbeat timed out") - self.close_socket() - break - except socket.error as e: - if self._is_run: - _LOGGER.debug(f"[{self._device_id}] Socket error {repr(e)}") - self.close_socket() - break - except Exception as e: - _LOGGER.error( - f"[{self._device_id}] Unknown error :{e.__traceback__.tb_frame.f_globals['__file__']}, " - f"{e.__traceback__.tb_lineno}, {repr(e)}" - ) - self.close_socket() - break - - def set_attribute(self, attr, value): - raise NotImplementedError - - def get_attribute(self, attr): - return self._attributes.get(attr) - - def set_customize(self, customize): - pass - - @property - def attributes(self): - ret = {} - for status in self._attributes.keys(): - ret[str(status)] = self._attributes[status] - return ret +import threading + +try: + from enum import StrEnum +except ImportError: + from ..backports.enum import StrEnum + +import logging +import socket +import time +from enum import IntEnum + +from .message import ( + MessageApplianceResponse, + MessageQueryAppliance, + MessageQuestCustom, + MessageType, +) +from .packet_builder import PacketBuilder +from .security import ( + MSGTYPE_ENCRYPTED_REQUEST, + MSGTYPE_HANDSHAKE_REQUEST, + LocalSecurity, +) + +_LOGGER = logging.getLogger(__name__) + + +class AuthException(Exception): + pass + + +class ResponseException(Exception): + pass + + +class RefreshFailed(Exception): + pass + + +class DeviceAttributes(StrEnum): + pass + + +class ParseMessageResult(IntEnum): + SUCCESS = 0 + PADDING = 1 + ERROR = 99 + + +class MiedaDevice(threading.Thread): + def __init__( + self, + name: str, + device_id: int, + device_type: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + attributes: dict, + ): + threading.Thread.__init__(self) + self._attributes = attributes if attributes else {} + self._socket = None + self._ip_address = ip_address + self._port = port + self._security = LocalSecurity() + self._token = bytes.fromhex(token) if token else None + self._key = bytes.fromhex(key) if key else None + self._buffer = b"" + self._device_name = name + self._device_id = device_id + self._device_type = device_type + self._protocol = protocol + self._model = model + self._subtype = subtype + self._protocol_version = 0 + self._updates = [] + self._unsupported_protocol = [] + self._is_run = False + self._available = True + self._appliance_query = True + self._refresh_interval = 30 + self._heartbeat_interval = 10 + self._default_refresh_interval = 30 + + @property + def name(self): + return self._device_name + + @property + def available(self): + return self._available + + @property + def device_id(self): + return self._device_id + + @property + def device_type(self): + return self._device_type + + @property + def model(self): + return self._model + + @property + def subtype(self): + return self._subtype + + @staticmethod + def fetch_v2_message(msg): + result = [] + while len(msg) > 0: + factual_msg_len = len(msg) + if factual_msg_len < 6: + break + alleged_msg_len = msg[4] + (msg[5] << 8) + if factual_msg_len >= alleged_msg_len: + result.append(msg[:alleged_msg_len]) + msg = msg[alleged_msg_len:] + else: + break + return result, msg + + def connect(self, refresh_status=True): + try: + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.settimeout(10) + _LOGGER.debug( + f"[{self._device_id}] Connecting to {self._ip_address}:{self._port}" + ) + self._socket.connect((self._ip_address, self._port)) + _LOGGER.debug(f"[{self._device_id}] Connected") + if self._protocol == 3: + self.authenticate() + _LOGGER.debug(f"[{self._device_id}] Authentication success") + if refresh_status: + self.refresh_status(wait_response=True) + self.enable_device(True) + return True + except socket.timeout: + _LOGGER.debug(f"[{self._device_id}] Connection timed out") + except socket.error: + _LOGGER.debug(f"[{self._device_id}] Connection error") + except AuthException: + _LOGGER.debug(f"[{self._device_id}] Authentication failed") + except ResponseException: + _LOGGER.debug(f"[{self._device_id}] Unexpected response received") + except RefreshFailed: + _LOGGER.debug(f"[{self._device_id}] Refresh status is timed out") + except Exception as e: + _LOGGER.error( + f"[{self._device_id}] Unknown error: {e.__traceback__.tb_frame.f_globals['__file__']}, " + f"{e.__traceback__.tb_lineno}, {repr(e)}" + ) + self.enable_device(False) + return False + + def authenticate(self): + request = self._security.encode_8370(self._token, MSGTYPE_HANDSHAKE_REQUEST) + _LOGGER.debug(f"[{self._device_id}] Handshaking") + self._socket.send(request) + response = self._socket.recv(512) + if len(response) < 20: + raise AuthException() + response = response[8:72] + self._security.tcp_key(response, self._key) + + def send_message(self, data): + if self._protocol == 3: + self.send_message_v3(data, msg_type=MSGTYPE_ENCRYPTED_REQUEST) + else: + self.send_message_v2(data) + + def send_message_v2(self, data): + if self._socket is not None: + self._socket.send(data) + else: + _LOGGER.debug( + f"[{self._device_id}] Send failure, device disconnected, data: {data.hex()}" + ) + + def send_message_v3(self, data, msg_type=MSGTYPE_ENCRYPTED_REQUEST): + data = self._security.encode_8370(data, msg_type) + self.send_message_v2(data) + + def build_send(self, cmd): + data = cmd.serialize() + _LOGGER.debug(f"[{self._device_id}] Sending: {cmd}") + msg = PacketBuilder(self._device_id, data).finalize() + self.send_message(msg) + + def refresh_status(self, wait_response=False): + cmds: list = self.build_query() + if self._appliance_query: + cmds = [MessageQueryAppliance(self.device_type)] + cmds + error_count = 0 + for cmd in cmds: + if cmd.__class__.__name__ not in self._unsupported_protocol: + self.build_send(cmd) + if wait_response: + try: + while True: + msg = self._socket.recv(512) + if len(msg) == 0: + raise socket.error + result = self.parse_message(msg) + if result == ParseMessageResult.SUCCESS: + break + elif result == ParseMessageResult.PADDING: + continue + else: + raise ResponseException + except socket.timeout: + error_count += 1 + self._unsupported_protocol.append(cmd.__class__.__name__) + _LOGGER.debug( + f"[{self._device_id}] Does not supports " + f"the protocol {cmd.__class__.__name__}, ignored" + ) + except ResponseException: + error_count += 1 + else: + error_count += 1 + if error_count == len(cmds): + raise RefreshFailed + + def pre_process_message(self, msg): + if msg[9] == MessageType.query_appliance: + message = MessageApplianceResponse(msg) + self._appliance_query = False + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + self._protocol_version = message.protocol_version + _LOGGER.debug( + f"[{self._device_id}] Device protocol version: {self._protocol_version}" + ) + return False + return True + + def parse_message(self, msg): + if self._protocol == 3: + messages, self._buffer = self._security.decode_8370(self._buffer + msg) + else: + messages, self._buffer = self.fetch_v2_message(self._buffer + msg) + if len(messages) == 0: + return ParseMessageResult.PADDING + for message in messages: + if message == b"ERROR": + return ParseMessageResult.ERROR + payload_len = message[4] + (message[5] << 8) - 56 + payload_type = message[2] + (message[3] << 8) + if payload_type in [0x1001, 0x0001]: + # Heartbeat detected + pass + elif len(message) > 56: + cryptographic = message[40:-16] + if payload_len % 16 == 0: + decrypted = self._security.aes_decrypt(cryptographic) + try: + cont = True + if self._appliance_query: + cont = self.pre_process_message(decrypted) + if cont: + status = self.process_message(decrypted) + if len(status) > 0: + self.update_all(status) + else: + _LOGGER.debug( + f"[{self._device_id}] Unidentified protocol" + ) + except Exception as e: + _LOGGER.error( + f"[{self._device_id}] Error in process message, msg = {e}" + ) + else: + _LOGGER.warning( + f"[{self._device_id}] Illegal payload, " + f"original message = {msg.hex()}, buffer = {self._buffer.hex()}, " + f"8370 decoded = {message.hex()}, payload type = {payload_type}, " + f"alleged payload length = {payload_len}, factual payload length = {len(cryptographic)}" + ) + else: + _LOGGER.warning( + f"[{self._device_id}] Illegal message, " + f"original message = {msg.hex()}, buffer = {self._buffer.hex()}, " + f"8370 decoded = {message.hex()}, payload type = {payload_type}, " + f"alleged payload length = {payload_len}, message length = {len(message)}, " + ) + return ParseMessageResult.SUCCESS + + def build_query(self): + raise NotImplementedError + + def process_message(self, msg): + raise NotImplementedError + + def send_command(self, cmd_type, cmd_body: bytearray): + cmd = MessageQuestCustom( + self._device_type, self._protocol_version, cmd_type, cmd_body + ) + try: + self.build_send(cmd) + except socket.error as e: + _LOGGER.debug( + f"[{self._device_id}] Interface send_command failure, {repr(e)}, " + f"cmd_type: {cmd_type}, cmd_body: {cmd_body.hex()}" + ) + + def send_heartbeat(self): + msg = PacketBuilder(self._device_id, bytearray([0x00])).finalize(msg_type=0) + self.send_message(msg) + + def register_update(self, update): + self._updates.append(update) + + def update_all(self, status): + _LOGGER.debug(f"[{self._device_id}] Status update: {status}") + for update in self._updates: + update(status) + + def enable_device(self, available=True): + self._available = available + status = {"available": available} + self.update_all(status) + + def open(self): + if not self._is_run: + self._is_run = True + threading.Thread.start(self) + + def close(self): + if self._is_run: + self._is_run = False + self.close_socket() + + def close_socket(self): + self._unsupported_protocol = [] + self._buffer = b"" + if self._socket: + self._socket.close() + self._socket = None + + def set_ip_address(self, ip_address): + if self._ip_address != ip_address: + _LOGGER.debug(f"[{self._device_id}] Update IP address to {ip_address}") + self._ip_address = ip_address + self.close_socket() + + def set_refresh_interval(self, refresh_interval): + self._refresh_interval = refresh_interval + + def run(self): + while self._is_run: + while self._socket is None: + if self.connect(refresh_status=True) is False: + if not self._is_run: + return + self.close_socket() + time.sleep(5) + timeout_counter = 0 + start = time.time() + previous_refresh = start + previous_heartbeat = start + self._socket.settimeout(1) + while True: + try: + now = time.time() + if 0 < self._refresh_interval <= now - previous_refresh: + self.refresh_status() + previous_refresh = now + if now - previous_heartbeat >= self._heartbeat_interval: + self.send_heartbeat() + previous_heartbeat = now + msg = self._socket.recv(512) + msg_len = len(msg) + if msg_len == 0: + raise socket.error("Connection closed by peer") + result = self.parse_message(msg) + if result == ParseMessageResult.ERROR: + _LOGGER.debug(f"[{self._device_id}] Message 'ERROR' received") + self.close_socket() + break + elif result == ParseMessageResult.SUCCESS: + timeout_counter = 0 + except socket.timeout: + timeout_counter = timeout_counter + 1 + if timeout_counter >= 120: + _LOGGER.debug(f"[{self._device_id}] Heartbeat timed out") + self.close_socket() + break + except socket.error as e: + if self._is_run: + _LOGGER.debug(f"[{self._device_id}] Socket error {repr(e)}") + self.close_socket() + break + except Exception as e: + _LOGGER.error( + f"[{self._device_id}] Unknown error :{e.__traceback__.tb_frame.f_globals['__file__']}, " + f"{e.__traceback__.tb_lineno}, {repr(e)}" + ) + self.close_socket() + break + + def set_attribute(self, attr, value): + raise NotImplementedError + + def get_attribute(self, attr): + return self._attributes.get(attr) + + def set_customize(self, customize): + pass + + @property + def attributes(self): + ret = {} + for status in self._attributes.keys(): + ret[str(status)] = self._attributes[status] + return ret diff --git a/custom_components/midea_ac_lan/midea/core/discover.py b/custom_components/midea_ac_lan/midea/core/discover.py index 52619809..0deb1790 100644 --- a/custom_components/midea_ac_lan/midea/core/discover.py +++ b/custom_components/midea_ac_lan/midea/core/discover.py @@ -1,308 +1,308 @@ -import logging -import socket -from ipaddress import IPv4Network - -import ifaddr - -from .security import LocalSecurity - -try: - import xml.etree.cElementTree as ET -except ImportError: - import xml.etree.ElementTree as ET - -_LOGGER = logging.getLogger(__name__) - -BROADCAST_MSG = bytearray( - [ - 0x5A, - 0x5A, - 0x01, - 0x11, - 0x48, - 0x00, - 0x92, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x7F, - 0x75, - 0xBD, - 0x6B, - 0x3E, - 0x4F, - 0x8B, - 0x76, - 0x2E, - 0x84, - 0x9C, - 0x6E, - 0x57, - 0x8D, - 0x65, - 0x90, - 0x03, - 0x6E, - 0x9D, - 0x43, - 0x42, - 0xA5, - 0x0F, - 0x1F, - 0x56, - 0x9E, - 0xB8, - 0xEC, - 0x91, - 0x8E, - 0x92, - 0xE5, - ] -) - -DEVICE_INFO_MSG = bytearray( - [ - 0x5A, - 0x5A, - 0x15, - 0x00, - 0x00, - 0x38, - 0x00, - 0x04, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x27, - 0x33, - 0x05, - 0x13, - 0x06, - 0x14, - 0x14, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x03, - 0xE8, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0xCA, - 0x8D, - 0x9B, - 0xF9, - 0xA0, - 0x30, - 0x1A, - 0xE3, - 0xB7, - 0xE4, - 0x2D, - 0x53, - 0x49, - 0x47, - 0x62, - 0xBE, - ] -) - - -def discover(discover_type=None, ip_address=None): - if discover_type is None: - discover_type = [] - security = LocalSecurity() - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - sock.settimeout(5) - found_devices = {} - if ip_address is None: - addrs = enum_all_broadcast() - else: - addrs = [ip_address] - _LOGGER.debug(f"All addresses for broadcast: {addrs}") - for addr in addrs: - try: - sock.sendto(BROADCAST_MSG, (addr, 6445)) - sock.sendto(BROADCAST_MSG, (addr, 20086)) - except Exception: - _LOGGER.warning(f"Can't access network {addr}") - while True: - try: - data, addr = sock.recvfrom(512) - ip = addr[0] - _LOGGER.debug(f"Received response from {addr}: {data.hex()}") - if len(data) >= 104 and ( - data[:2].hex() == "5a5a" or data[8:10].hex() == "5a5a" - ): - if data[:2].hex() == "5a5a": - protocol = 2 - elif data[:2].hex() == "8370": - protocol = 3 - if data[8:10].hex() == "5a5a": - data = data[8:-16] - else: - continue - device_id = int.from_bytes( - bytearray.fromhex(data[20:26].hex()), "little" - ) - if device_id in found_devices: - continue - encrypt_data = data[40:-16] - reply = security.aes_decrypt(encrypt_data) - _LOGGER.debug(f"Declassified reply: {reply.hex()}") - ssid = reply[41 : 41 + reply[40]].decode("utf-8") - device_type = ssid.split("_")[1] - port = bytes2port(reply[4:8]) - model = reply[17:25].decode("utf-8") - sn = reply[8:40].decode("utf-8") - elif data[:6].hex() == "3c3f786d6c20": - protocol = 1 - root = ET.fromstring(data.decode(encoding="utf-8", errors="replace")) - child = root.find("body/device") - m = child.attrib - port, sn, device_type = ( - int(m["port"]), - m["apc_sn"], - str(hex(int(m["apc_type"])))[2:], - ) - response = get_device_info(ip, int(port)) - device_id = get_id_from_response(response) - if len(sn) == 32: - model = sn[9:17] - elif len(sn) == 22: - model = sn[3:11] - else: - model = "" - else: - continue - device = { - "device_id": device_id, - "type": int(device_type, 16), - "ip_address": ip, - "port": port, - "model": model, - "sn": sn, - "protocol": protocol, - } - if len(discover_type) == 0 or device.get("type") in discover_type: - found_devices[device_id] = device - _LOGGER.debug(f"Found a supported device: {device}") - else: - _LOGGER.debug(f"Found a unsupported device: {device}") - except socket.timeout: - break - except socket.error as e: - _LOGGER.error(f"Socket error: {repr(e)}") - return found_devices - - -def get_id_from_response(response): - if response[64:-16][:6].hex() == "3c3f786d6c20": - xml = response[64:-16] - root = ET.fromstring(xml.decode(encoding="utf-8", errors="replace")) - child = root.find("smartDevice") - m = child.attrib - return int.from_bytes(bytearray.fromhex(m["devId"]), "little") - else: - return 0 - - -def bytes2port(paramArrayOfbyte): - if paramArrayOfbyte is None: - return 0 - b, i = 0, 0 - while b < 4: - if b < len(paramArrayOfbyte): - b1 = paramArrayOfbyte[b] & 0xFF - else: - b1 = 0 - i |= b1 << b * 8 - b += 1 - return i - - -def get_device_info(device_ip, device_port: int): - response = bytearray(0) - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.settimeout(8) - device_address = (device_ip, device_port) - sock.connect(device_address) - _LOGGER.debug( - f"Sending to {device_ip}:{device_port} {DEVICE_INFO_MSG.hex()}" - ) - sock.sendall(DEVICE_INFO_MSG) - response = sock.recv(512) - except socket.timeout: - _LOGGER.warning( - f"Connect the device {device_ip}:{device_port} timed out for 8s. " - f"Don't care about a small amount of this. if many maybe not support." - ) - except socket.error: - _LOGGER.warning(f"Can't connect to Device {device_ip}:{device_port}") - return response - - -def enum_all_broadcast(): - nets = [] - adapters = ifaddr.get_adapters() - for adapter in adapters: - for ip in adapter.ips: - if ip.is_IPv4 and ip.network_prefix < 32: - local_network = IPv4Network( - f"{ip.ip}/{ip.network_prefix}", strict=False - ) - if ( - local_network.is_private - and not local_network.is_loopback - and not local_network.is_link_local - ): - addr = str(local_network.broadcast_address) - if addr not in nets: - nets.append(addr) - return nets +import logging +import socket +from ipaddress import IPv4Network + +import ifaddr + +from .security import LocalSecurity + +try: + import xml.etree.cElementTree as ET +except ImportError: + import xml.etree.ElementTree as ET + +_LOGGER = logging.getLogger(__name__) + +BROADCAST_MSG = bytearray( + [ + 0x5A, + 0x5A, + 0x01, + 0x11, + 0x48, + 0x00, + 0x92, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7F, + 0x75, + 0xBD, + 0x6B, + 0x3E, + 0x4F, + 0x8B, + 0x76, + 0x2E, + 0x84, + 0x9C, + 0x6E, + 0x57, + 0x8D, + 0x65, + 0x90, + 0x03, + 0x6E, + 0x9D, + 0x43, + 0x42, + 0xA5, + 0x0F, + 0x1F, + 0x56, + 0x9E, + 0xB8, + 0xEC, + 0x91, + 0x8E, + 0x92, + 0xE5, + ] +) + +DEVICE_INFO_MSG = bytearray( + [ + 0x5A, + 0x5A, + 0x15, + 0x00, + 0x00, + 0x38, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x27, + 0x33, + 0x05, + 0x13, + 0x06, + 0x14, + 0x14, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0xE8, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xCA, + 0x8D, + 0x9B, + 0xF9, + 0xA0, + 0x30, + 0x1A, + 0xE3, + 0xB7, + 0xE4, + 0x2D, + 0x53, + 0x49, + 0x47, + 0x62, + 0xBE, + ] +) + + +def discover(discover_type=None, ip_address=None): + if discover_type is None: + discover_type = [] + security = LocalSecurity() + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + sock.settimeout(5) + found_devices = {} + if ip_address is None: + addrs = enum_all_broadcast() + else: + addrs = [ip_address] + _LOGGER.debug(f"All addresses for broadcast: {addrs}") + for addr in addrs: + try: + sock.sendto(BROADCAST_MSG, (addr, 6445)) + sock.sendto(BROADCAST_MSG, (addr, 20086)) + except Exception: + _LOGGER.warning(f"Can't access network {addr}") + while True: + try: + data, addr = sock.recvfrom(512) + ip = addr[0] + _LOGGER.debug(f"Received response from {addr}: {data.hex()}") + if len(data) >= 104 and ( + data[:2].hex() == "5a5a" or data[8:10].hex() == "5a5a" + ): + if data[:2].hex() == "5a5a": + protocol = 2 + elif data[:2].hex() == "8370": + protocol = 3 + if data[8:10].hex() == "5a5a": + data = data[8:-16] + else: + continue + device_id = int.from_bytes( + bytearray.fromhex(data[20:26].hex()), "little" + ) + if device_id in found_devices: + continue + encrypt_data = data[40:-16] + reply = security.aes_decrypt(encrypt_data) + _LOGGER.debug(f"Declassified reply: {reply.hex()}") + ssid = reply[41 : 41 + reply[40]].decode("utf-8") + device_type = ssid.split("_")[1] + port = bytes2port(reply[4:8]) + model = reply[17:25].decode("utf-8") + sn = reply[8:40].decode("utf-8") + elif data[:6].hex() == "3c3f786d6c20": + protocol = 1 + root = ET.fromstring(data.decode(encoding="utf-8", errors="replace")) + child = root.find("body/device") + m = child.attrib + port, sn, device_type = ( + int(m["port"]), + m["apc_sn"], + str(hex(int(m["apc_type"])))[2:], + ) + response = get_device_info(ip, int(port)) + device_id = get_id_from_response(response) + if len(sn) == 32: + model = sn[9:17] + elif len(sn) == 22: + model = sn[3:11] + else: + model = "" + else: + continue + device = { + "device_id": device_id, + "type": int(device_type, 16), + "ip_address": ip, + "port": port, + "model": model, + "sn": sn, + "protocol": protocol, + } + if len(discover_type) == 0 or device.get("type") in discover_type: + found_devices[device_id] = device + _LOGGER.debug(f"Found a supported device: {device}") + else: + _LOGGER.debug(f"Found a unsupported device: {device}") + except socket.timeout: + break + except socket.error as e: + _LOGGER.error(f"Socket error: {repr(e)}") + return found_devices + + +def get_id_from_response(response): + if response[64:-16][:6].hex() == "3c3f786d6c20": + xml = response[64:-16] + root = ET.fromstring(xml.decode(encoding="utf-8", errors="replace")) + child = root.find("smartDevice") + m = child.attrib + return int.from_bytes(bytearray.fromhex(m["devId"]), "little") + else: + return 0 + + +def bytes2port(paramArrayOfbyte): + if paramArrayOfbyte is None: + return 0 + b, i = 0, 0 + while b < 4: + if b < len(paramArrayOfbyte): + b1 = paramArrayOfbyte[b] & 0xFF + else: + b1 = 0 + i |= b1 << b * 8 + b += 1 + return i + + +def get_device_info(device_ip, device_port: int): + response = bytearray(0) + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.settimeout(8) + device_address = (device_ip, device_port) + sock.connect(device_address) + _LOGGER.debug( + f"Sending to {device_ip}:{device_port} {DEVICE_INFO_MSG.hex()}" + ) + sock.sendall(DEVICE_INFO_MSG) + response = sock.recv(512) + except socket.timeout: + _LOGGER.warning( + f"Connect the device {device_ip}:{device_port} timed out for 8s. " + f"Don't care about a small amount of this. if many maybe not support." + ) + except socket.error: + _LOGGER.warning(f"Can't connect to Device {device_ip}:{device_port}") + return response + + +def enum_all_broadcast(): + nets = [] + adapters = ifaddr.get_adapters() + for adapter in adapters: + for ip in adapter.ips: + if ip.is_IPv4 and ip.network_prefix < 32: + local_network = IPv4Network( + f"{ip.ip}/{ip.network_prefix}", strict=False + ) + if ( + local_network.is_private + and not local_network.is_loopback + and not local_network.is_link_local + ): + addr = str(local_network.broadcast_address) + if addr not in nets: + nets.append(addr) + return nets diff --git a/custom_components/midea_ac_lan/midea/core/message.py b/custom_components/midea_ac_lan/midea/core/message.py index 8a6222f9..91e485c6 100644 --- a/custom_components/midea_ac_lan/midea/core/message.py +++ b/custom_components/midea_ac_lan/midea/core/message.py @@ -1,272 +1,272 @@ -import logging -from abc import ABC -from enum import IntEnum - -_LOGGER = logging.getLogger(__name__) - - -class MessageLenError(Exception): - pass - - -class MessageBodyError(Exception): - pass - - -class MessageCheckSumError(Exception): - pass - - -class MessageType(IntEnum): - set = (0x02,) - query = (0x03,) - notify1 = (0x04,) - notify2 = (0x05,) - exception = (0x06,) - exception2 = (0x0A,) - query_appliance = 0xA0 - - -class MessageBase(ABC): - HEADER_LENGTH = 10 - - def __init__(self): - self._device_type = 0x00 - self._message_type = 0x00 - self._body_type = 0x00 - self._protocol_version = 0x00 - - @staticmethod - def checksum(data): - return (~sum(data) + 1) & 0xFF - - @property - def header(self): - raise NotImplementedError - - @property - def body(self): - raise NotImplementedError - - @property - def message_type(self): - return self._message_type - - @message_type.setter - def message_type(self, value): - self._message_type = value - - @property - def device_type(self): - return self._device_type - - @device_type.setter - def device_type(self, value): - self._device_type = value - - @property - def body_type(self): - return self._body_type - - @body_type.setter - def body_type(self, value): - self._body_type = value - - @property - def protocol_version(self): - return self._protocol_version - - @protocol_version.setter - def protocol_version(self, protocol_version): - self._protocol_version = protocol_version - - def __str__(self) -> str: - output = { - "header": self.header.hex(), - "body": self.body.hex(), - "message type": "%02x" % self._message_type, - "body type": ( - ("%02x" % self._body_type) if self._body_type is not None else "None" - ), - } - return str(output) - - -class MessageRequest(MessageBase): - def __init__(self, device_type, protocol_version, message_type, body_type): - super().__init__() - self.device_type = device_type - self.protocol_version = protocol_version - self.message_type = message_type - self.body_type = body_type - - @property - def header(self): - length = self.HEADER_LENGTH + len(self.body) - return bytearray( - [ - # flag - 0xAA, - # length - length, - # device type - self.device_type, - # frame checksum - 0x00, # self._device_type ^ length, - # unused - 0x00, - 0x00, - # frame ID - 0x00, - # frame protocol version - 0x00, - # device protocol version - self.protocol_version, - # frame type - self.message_type, - ] - ) - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([]) - if self.body_type is not None: - body.append(self.body_type) - if self._body is not None: - body.extend(self._body) - return body - - def serialize(self): - stream = self.header + self.body - stream.append(MessageBase.checksum(stream[1:])) - return stream - - -class MessageQuestCustom(MessageRequest): - def __init__(self, device_type, protocol_version, cmd_type, cmd_body): - super().__init__( - device_type=device_type, - protocol_version=protocol_version, - message_type=cmd_type, - body_type=None, - ) - self._cmd_body = cmd_body - - @property - def _body(self): - return bytearray([]) - - @property - def body(self): - return self._cmd_body - - -class MessageQueryAppliance(MessageRequest): - def __init__(self, device_type): - super().__init__( - device_type=device_type, - protocol_version=0, - message_type=MessageType.query_appliance, - body_type=None, - ) - - @property - def _body(self): - return bytearray([]) - - @property - def body(self): - return bytearray([0x00] * 19) - - -class MessageBody: - def __init__(self, body): - self._data = body - - @property - def data(self): - return self._data - - @property - def body_type(self): - return self._data[0] - - @staticmethod - def read_byte(body, byte, default_value=0): - return body[byte] if len(body) > byte else default_value - - -class NewProtocolMessageBody(MessageBody): - def __init__(self, body, bt): - super().__init__(body) - if bt == 0xB5: - self._pack_len = 4 - else: - self._pack_len = 5 - - @staticmethod - def pack(param, value: bytearray, pack_len=4): - length = len(value) - if pack_len == 4: - stream = bytearray([param & 0xFF, param >> 8, length]) + value - else: - stream = bytearray([param & 0xFF, param >> 8, 0x00, length]) + value - return stream - - def parse(self): - result = {} - try: - pos = 2 - for pack in range(0, self.data[1]): - param = self.data[pos] + (self.data[pos + 1] << 8) - if self._pack_len == 5: - pos += 1 - length = self.data[pos + 2] - if length > 0: - value = self.data[pos + 3 : pos + 3 + length] - result[param] = value - pos += 3 + length - except IndexError: - # Some device used non-standard new-protocol(美的乐享三代中央空调?) - _LOGGER.debug(f"Non-standard new-protocol {self.data.hex()}") - return result - - -class MessageResponse(MessageBase): - def __init__(self, message): - super().__init__() - if message is None or len(message) < self.HEADER_LENGTH + 1: - raise MessageLenError - self._header = message[: self.HEADER_LENGTH] - self.protocol_version = self._header[-2] - self.message_type = self._header[-1] - self.device_type = self._header[2] - body = message[self.HEADER_LENGTH : -1] - self._body = MessageBody(body) - self.body_type = self._body.body_type - - @property - def header(self): - return self._header - - @property - def body(self): - return self._body.data - - def set_body(self, body: MessageBody): - self._body = body - - def set_attr(self): - for key in vars(self._body).keys(): - if key != "data": - value = getattr(self._body, key, None) - setattr(self, key, value) - - -class MessageApplianceResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) +import logging +from abc import ABC +from enum import IntEnum + +_LOGGER = logging.getLogger(__name__) + + +class MessageLenError(Exception): + pass + + +class MessageBodyError(Exception): + pass + + +class MessageCheckSumError(Exception): + pass + + +class MessageType(IntEnum): + set = (0x02,) + query = (0x03,) + notify1 = (0x04,) + notify2 = (0x05,) + exception = (0x06,) + exception2 = (0x0A,) + query_appliance = 0xA0 + + +class MessageBase(ABC): + HEADER_LENGTH = 10 + + def __init__(self): + self._device_type = 0x00 + self._message_type = 0x00 + self._body_type = 0x00 + self._protocol_version = 0x00 + + @staticmethod + def checksum(data): + return (~sum(data) + 1) & 0xFF + + @property + def header(self): + raise NotImplementedError + + @property + def body(self): + raise NotImplementedError + + @property + def message_type(self): + return self._message_type + + @message_type.setter + def message_type(self, value): + self._message_type = value + + @property + def device_type(self): + return self._device_type + + @device_type.setter + def device_type(self, value): + self._device_type = value + + @property + def body_type(self): + return self._body_type + + @body_type.setter + def body_type(self, value): + self._body_type = value + + @property + def protocol_version(self): + return self._protocol_version + + @protocol_version.setter + def protocol_version(self, protocol_version): + self._protocol_version = protocol_version + + def __str__(self) -> str: + output = { + "header": self.header.hex(), + "body": self.body.hex(), + "message type": "%02x" % self._message_type, + "body type": ( + ("%02x" % self._body_type) if self._body_type is not None else "None" + ), + } + return str(output) + + +class MessageRequest(MessageBase): + def __init__(self, device_type, protocol_version, message_type, body_type): + super().__init__() + self.device_type = device_type + self.protocol_version = protocol_version + self.message_type = message_type + self.body_type = body_type + + @property + def header(self): + length = self.HEADER_LENGTH + len(self.body) + return bytearray( + [ + # flag + 0xAA, + # length + length, + # device type + self.device_type, + # frame checksum + 0x00, # self._device_type ^ length, + # unused + 0x00, + 0x00, + # frame ID + 0x00, + # frame protocol version + 0x00, + # device protocol version + self.protocol_version, + # frame type + self.message_type, + ] + ) + + @property + def _body(self): + raise NotImplementedError + + @property + def body(self): + body = bytearray([]) + if self.body_type is not None: + body.append(self.body_type) + if self._body is not None: + body.extend(self._body) + return body + + def serialize(self): + stream = self.header + self.body + stream.append(MessageBase.checksum(stream[1:])) + return stream + + +class MessageQuestCustom(MessageRequest): + def __init__(self, device_type, protocol_version, cmd_type, cmd_body): + super().__init__( + device_type=device_type, + protocol_version=protocol_version, + message_type=cmd_type, + body_type=None, + ) + self._cmd_body = cmd_body + + @property + def _body(self): + return bytearray([]) + + @property + def body(self): + return self._cmd_body + + +class MessageQueryAppliance(MessageRequest): + def __init__(self, device_type): + super().__init__( + device_type=device_type, + protocol_version=0, + message_type=MessageType.query_appliance, + body_type=None, + ) + + @property + def _body(self): + return bytearray([]) + + @property + def body(self): + return bytearray([0x00] * 19) + + +class MessageBody: + def __init__(self, body): + self._data = body + + @property + def data(self): + return self._data + + @property + def body_type(self): + return self._data[0] + + @staticmethod + def read_byte(body, byte, default_value=0): + return body[byte] if len(body) > byte else default_value + + +class NewProtocolMessageBody(MessageBody): + def __init__(self, body, bt): + super().__init__(body) + if bt == 0xB5: + self._pack_len = 4 + else: + self._pack_len = 5 + + @staticmethod + def pack(param, value: bytearray, pack_len=4): + length = len(value) + if pack_len == 4: + stream = bytearray([param & 0xFF, param >> 8, length]) + value + else: + stream = bytearray([param & 0xFF, param >> 8, 0x00, length]) + value + return stream + + def parse(self): + result = {} + try: + pos = 2 + for pack in range(0, self.data[1]): + param = self.data[pos] + (self.data[pos + 1] << 8) + if self._pack_len == 5: + pos += 1 + length = self.data[pos + 2] + if length > 0: + value = self.data[pos + 3 : pos + 3 + length] + result[param] = value + pos += 3 + length + except IndexError: + # Some device used non-standard new-protocol(美的乐享三代中央空调?) + _LOGGER.debug(f"Non-standard new-protocol {self.data.hex()}") + return result + + +class MessageResponse(MessageBase): + def __init__(self, message): + super().__init__() + if message is None or len(message) < self.HEADER_LENGTH + 1: + raise MessageLenError + self._header = message[: self.HEADER_LENGTH] + self.protocol_version = self._header[-2] + self.message_type = self._header[-1] + self.device_type = self._header[2] + body = message[self.HEADER_LENGTH : -1] + self._body = MessageBody(body) + self.body_type = self._body.body_type + + @property + def header(self): + return self._header + + @property + def body(self): + return self._body.data + + def set_body(self, body: MessageBody): + self._body = body + + def set_attr(self): + for key in vars(self._body).keys(): + if key != "data": + value = getattr(self._body, key, None) + setattr(self, key, value) + + +class MessageApplianceResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) diff --git a/custom_components/midea_ac_lan/midea/core/packet_builder.py b/custom_components/midea_ac_lan/midea/core/packet_builder.py index 49b9770c..04780fee 100644 --- a/custom_components/midea_ac_lan/midea/core/packet_builder.py +++ b/custom_components/midea_ac_lan/midea/core/packet_builder.py @@ -1,94 +1,94 @@ -import datetime - -from .security import LocalSecurity - - -class PacketBuilder: - def __init__(self, device_id: int, command): - self.command = None - self.security = LocalSecurity() - # aa20ac00000000000003418100ff03ff000200000000000000000000000006f274 - # Init the packet with the header data. - self.packet = bytearray( - [ - # 2 bytes - StaicHeader - 0x5A, - 0x5A, - # 2 bytes - mMessageType - 0x01, - 0x11, - # 2 bytes - PacketLenght - 0x00, - 0x00, - # 2 bytes - 0x20, - 0x00, - # 4 bytes - MessageId - 0x00, - 0x00, - 0x00, - 0x00, - # 8 bytes - Date&Time - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - # 6 bytes - mDeviceID - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - # 12 bytes - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - self.packet[12:20] = self.packet_time() - self.packet[20:28] = device_id.to_bytes(8, "little") - self.command = command - - def finalize(self, msg_type=1): - if msg_type != 1: - self.packet[3] = 0x10 - self.packet[6] = 0x7B - else: - self.packet.extend(self.security.aes_encrypt(self.command)) - # PacketLenght - self.packet[4:6] = (len(self.packet) + 16).to_bytes(2, "little") - # Append a basic checksum data(16 bytes) to the packet - self.packet.extend(self.encode32(self.packet)) - return self.packet - - def encode32(self, data: bytearray): - return self.security.encode32_data(data) - - @staticmethod - def checksum(data): - return (~sum(data) + 1) & 0xFF - - @staticmethod - def packet_time(): - t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:16] - b = bytearray() - for i in range(0, len(t), 2): - d = int(t[i : i + 2]) - b.insert(0, d) - return b +import datetime + +from .security import LocalSecurity + + +class PacketBuilder: + def __init__(self, device_id: int, command): + self.command = None + self.security = LocalSecurity() + # aa20ac00000000000003418100ff03ff000200000000000000000000000006f274 + # Init the packet with the header data. + self.packet = bytearray( + [ + # 2 bytes - StaicHeader + 0x5A, + 0x5A, + # 2 bytes - mMessageType + 0x01, + 0x11, + # 2 bytes - PacketLenght + 0x00, + 0x00, + # 2 bytes + 0x20, + 0x00, + # 4 bytes - MessageId + 0x00, + 0x00, + 0x00, + 0x00, + # 8 bytes - Date&Time + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + # 6 bytes - mDeviceID + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + # 12 bytes + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + self.packet[12:20] = self.packet_time() + self.packet[20:28] = device_id.to_bytes(8, "little") + self.command = command + + def finalize(self, msg_type=1): + if msg_type != 1: + self.packet[3] = 0x10 + self.packet[6] = 0x7B + else: + self.packet.extend(self.security.aes_encrypt(self.command)) + # PacketLenght + self.packet[4:6] = (len(self.packet) + 16).to_bytes(2, "little") + # Append a basic checksum data(16 bytes) to the packet + self.packet.extend(self.encode32(self.packet)) + return self.packet + + def encode32(self, data: bytearray): + return self.security.encode32_data(data) + + @staticmethod + def checksum(data): + return (~sum(data) + 1) & 0xFF + + @staticmethod + def packet_time(): + t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:16] + b = bytearray() + for i in range(0, len(t), 2): + d = int(t[i : i + 2]) + b.insert(0, d) + return b diff --git a/custom_components/midea_ac_lan/midea/core/security.py b/custom_components/midea_ac_lan/midea/core/security.py index 6b055a11..90166ea1 100644 --- a/custom_components/midea_ac_lan/midea/core/security.py +++ b/custom_components/midea_ac_lan/midea/core/security.py @@ -1,264 +1,264 @@ -import hmac -from hashlib import md5, sha256 -from typing import Any -from urllib.parse import unquote_plus, urlencode, urlparse - -from Crypto.Cipher import AES -from Crypto.Random import get_random_bytes -from Crypto.Util.Padding import pad, unpad -from Crypto.Util.strxor import strxor - -MSGTYPE_HANDSHAKE_REQUEST = 0x0 -MSGTYPE_HANDSHAKE_RESPONSE = 0x1 -MSGTYPE_ENCRYPTED_RESPONSE = 0x3 -MSGTYPE_ENCRYPTED_REQUEST = 0x6 - - -class CloudSecurity: - def __init__(self, login_key, iot_key, hmac_key, fixed_key=None, fixed_iv=None): - self._login_key = login_key - self._iot_key = iot_key - self._hmac_key = hmac_key - self._aes_key = None - self._aes_iv = None - self._fixed_key = format(fixed_key, "x").encode("ascii") if fixed_key else None - self._fixed_iv = format(fixed_iv, "x").encode("ascii") if fixed_iv else None - - def sign(self, url: str, data: Any, random: str) -> str: - msg = self._iot_key - msg += str(data) - msg += random - sign = hmac.new(self._hmac_key.encode("ascii"), msg.encode("ascii"), sha256) - return sign.hexdigest() - - def encrypt_password(self, login_id, data): - m = sha256() - m.update(data.encode("ascii")) - login_hash = login_id + m.hexdigest() + self._login_key - m = sha256() - m.update(login_hash.encode("ascii")) - return m.hexdigest() - - def encrypt_iam_password(self, login_id, data) -> str: - raise NotImplementedError - - @staticmethod - def get_deviceid(username): - return sha256(f"Hello, {username}!".encode("ascii")).digest().hex()[:16] - - @staticmethod - def get_udp_id(appliance_id, method=0): - if method == 0: - bytes_id = bytes(reversed(appliance_id.to_bytes(8, "big"))) - elif method == 1: - bytes_id = appliance_id.to_bytes(6, "big") - elif method == 2: - bytes_id = appliance_id.to_bytes(6, "little") - else: - return None - data = bytearray(sha256(bytes_id).digest()) - for i in range(0, 16): - data[i] ^= data[i + 16] - return data[0:16].hex() - - def set_aes_keys(self, key, iv): - if isinstance(key, str): - key = key.encode("ascii") - if isinstance(iv, str): - iv = iv.encode("ascii") - self._aes_key = key - self._aes_iv = iv - - def aes_encrypt_with_fixed_key(self, data): - return self.aes_encrypt(data, self._fixed_key, self._fixed_iv) - - def aes_decrypt_with_fixed_key(self, data): - return self.aes_decrypt(data, self._fixed_key, self._fixed_iv) - - def aes_encrypt(self, data, key=None, iv=None): - if key is not None: - aes_key = key - aes_iv = iv - else: - aes_key = self._aes_key - aes_iv = self._aes_iv - if aes_key is None: - raise ValueError("Encrypt need a key") - if isinstance(data, str): - data = bytes.fromhex(data) - if aes_iv is None: # ECB - return AES.new(aes_key, AES.MODE_ECB).encrypt(pad(data, 16)) - else: # CBC - return AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).encrypt(pad(data, 16)) - - def aes_decrypt(self, data, key=None, iv=None): - if key is not None: - aes_key = key - aes_iv = iv - else: - aes_key = self._aes_key - aes_iv = self._aes_iv - if aes_key is None: - raise ValueError("Encrypt need a key") - if isinstance(data, str): - data = bytes.fromhex(data) - if aes_iv is None: # ECB - return unpad( - AES.new(aes_key, AES.MODE_ECB).decrypt(data), len(aes_key) - ).decode() - else: # CBC - return unpad( - AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).decrypt(data), len(aes_key) - ).decode() - - -class MeijuCloudSecurity(CloudSecurity): - def __init__(self, login_key, iot_key, hmac_key): - super().__init__(login_key, iot_key, hmac_key, 10864842703515613082) - - def encrypt_iam_password(self, login_id, data) -> str: - md = md5() - md.update(data.encode("ascii")) - md_second = md5() - md_second.update(md.hexdigest().encode("ascii")) - return md_second.hexdigest() - - -class MSmartCloudSecurity(CloudSecurity): - def __init__(self, login_key, iot_key, hmac_key): - super().__init__( - login_key, iot_key, hmac_key, 13101328926877700970, 16429062708050928556 - ) - - def encrypt_iam_password(self, login_id, data) -> str: - md = md5() - md.update(data.encode("ascii")) - md_second = md5() - md_second.update(md.hexdigest().encode("ascii")) - login_hash = login_id + md_second.hexdigest() + self._login_key - sha = sha256() - sha.update(login_hash.encode("ascii")) - return sha.hexdigest() - - def set_aes_keys(self, encrypted_key, encrypted_iv): - key_digest = sha256(self._login_key.encode("ascii")).hexdigest() - tmp_key = key_digest[:16].encode("ascii") - tmp_iv = key_digest[16:32].encode("ascii") - self._aes_key = self.aes_decrypt(encrypted_key, tmp_key, tmp_iv).encode("ascii") - self._aes_iv = self.aes_decrypt(encrypted_iv, tmp_key, tmp_iv).encode("ascii") - - -class MideaAirSecurity(CloudSecurity): - def __init__(self, login_key): - super().__init__(login_key, None, None) - - def sign(self, url: str, data: Any, random: str) -> str: - payload = unquote_plus(urlencode(sorted(data.items(), key=lambda x: x[0]))) - sha = sha256() - sha.update((urlparse(url).path + payload + self._login_key).encode("ascii")) - return sha.hexdigest() - - -class LocalSecurity: - def __init__(self): - self.blockSize = 16 - self.iv = b"\0" * 16 - self.aes_key = bytes.fromhex( - format(141661095494369103254425781617665632877, "x") - ) - self.salt = bytes.fromhex( - format( - 233912452794221312800602098970898185176935770387238278451789080441632479840061417076563, - "x", - ) - ) - self._tcp_key = None - self._request_count = 0 - self._response_count = 0 - - def aes_decrypt(self, raw): - try: - return unpad( - AES.new(self.aes_key, AES.MODE_ECB).decrypt(bytearray(raw)), 16 - ) - except ValueError: - return bytearray(0) - - def aes_encrypt(self, raw): - return AES.new(self.aes_key, AES.MODE_ECB).encrypt(bytearray(pad(raw, 16))) - - def aes_cbc_decrypt(self, raw, key): - return AES.new(key=key, mode=AES.MODE_CBC, iv=self.iv).decrypt(raw) - - def aes_cbc_encrypt(self, raw, key): - return AES.new(key=key, mode=AES.MODE_CBC, iv=self.iv).encrypt(raw) - - def encode32_data(self, raw): - return md5(raw + self.salt).digest() - - def tcp_key(self, response, key): - if response == b"ERROR": - raise Exception("authentication failed") - if len(response) != 64: - raise Exception("unexpected data length") - payload = response[:32] - sign = response[32:] - plain = self.aes_cbc_decrypt(payload, key) - if sha256(plain).digest() != sign: - raise Exception("sign does not match") - self._tcp_key = strxor(plain, key) - self._request_count = 0 - self._response_count = 0 - return self._tcp_key - - def encode_8370(self, data, msgtype): - header = bytearray([0x83, 0x70]) - size, padding = len(data), 0 - if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): - if (size + 2) % 16 != 0: - padding = 16 - (size + 2 & 0xF) - size += padding + 32 - data += get_random_bytes(padding) - header += size.to_bytes(2, "big") - header += bytearray([0x20, padding << 4 | msgtype]) - data = self._request_count.to_bytes(2, "big") + data - self._request_count += 1 - if self._request_count >= 0xFFFF: - self._request_count = 0 - if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): - sign = sha256(header + data).digest() - data = self.aes_cbc_encrypt(raw=data, key=self._tcp_key) + sign - return header + data - - def decode_8370(self, data): - if len(data) < 6: - return [], data - header = data[:6] - if header[0] != 0x83 or header[1] != 0x70: - raise Exception("not an 8370 message") - size = int.from_bytes(header[2:4], "big") + 8 - leftover = None - if len(data) < size: - return [], data - elif len(data) > size: - leftover = data[size:] - data = data[:size] - if header[4] != 0x20: - raise Exception("missing byte 4") - padding = header[5] >> 4 - msgtype = header[5] & 0xF - data = data[6:] - if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): - sign = data[-32:] - data = data[:-32] - data = self.aes_cbc_decrypt(raw=data, key=self._tcp_key) - if sha256(header + data).digest() != sign: - raise Exception("sign does not match") - if padding: - data = data[:-padding] - self._response_count = int.from_bytes(data[:2], "big") - data = data[2:] - if leftover: - packets, incomplete = self.decode_8370(leftover) - return [data] + packets, incomplete - return [data], b"" +import hmac +from hashlib import md5, sha256 +from typing import Any +from urllib.parse import unquote_plus, urlencode, urlparse + +from Crypto.Cipher import AES +from Crypto.Random import get_random_bytes +from Crypto.Util.Padding import pad, unpad +from Crypto.Util.strxor import strxor + +MSGTYPE_HANDSHAKE_REQUEST = 0x0 +MSGTYPE_HANDSHAKE_RESPONSE = 0x1 +MSGTYPE_ENCRYPTED_RESPONSE = 0x3 +MSGTYPE_ENCRYPTED_REQUEST = 0x6 + + +class CloudSecurity: + def __init__(self, login_key, iot_key, hmac_key, fixed_key=None, fixed_iv=None): + self._login_key = login_key + self._iot_key = iot_key + self._hmac_key = hmac_key + self._aes_key = None + self._aes_iv = None + self._fixed_key = format(fixed_key, "x").encode("ascii") if fixed_key else None + self._fixed_iv = format(fixed_iv, "x").encode("ascii") if fixed_iv else None + + def sign(self, url: str, data: Any, random: str) -> str: + msg = self._iot_key + msg += str(data) + msg += random + sign = hmac.new(self._hmac_key.encode("ascii"), msg.encode("ascii"), sha256) + return sign.hexdigest() + + def encrypt_password(self, login_id, data): + m = sha256() + m.update(data.encode("ascii")) + login_hash = login_id + m.hexdigest() + self._login_key + m = sha256() + m.update(login_hash.encode("ascii")) + return m.hexdigest() + + def encrypt_iam_password(self, login_id, data) -> str: + raise NotImplementedError + + @staticmethod + def get_deviceid(username): + return sha256(f"Hello, {username}!".encode("ascii")).digest().hex()[:16] + + @staticmethod + def get_udp_id(appliance_id, method=0): + if method == 0: + bytes_id = bytes(reversed(appliance_id.to_bytes(8, "big"))) + elif method == 1: + bytes_id = appliance_id.to_bytes(6, "big") + elif method == 2: + bytes_id = appliance_id.to_bytes(6, "little") + else: + return None + data = bytearray(sha256(bytes_id).digest()) + for i in range(0, 16): + data[i] ^= data[i + 16] + return data[0:16].hex() + + def set_aes_keys(self, key, iv): + if isinstance(key, str): + key = key.encode("ascii") + if isinstance(iv, str): + iv = iv.encode("ascii") + self._aes_key = key + self._aes_iv = iv + + def aes_encrypt_with_fixed_key(self, data): + return self.aes_encrypt(data, self._fixed_key, self._fixed_iv) + + def aes_decrypt_with_fixed_key(self, data): + return self.aes_decrypt(data, self._fixed_key, self._fixed_iv) + + def aes_encrypt(self, data, key=None, iv=None): + if key is not None: + aes_key = key + aes_iv = iv + else: + aes_key = self._aes_key + aes_iv = self._aes_iv + if aes_key is None: + raise ValueError("Encrypt need a key") + if isinstance(data, str): + data = bytes.fromhex(data) + if aes_iv is None: # ECB + return AES.new(aes_key, AES.MODE_ECB).encrypt(pad(data, 16)) + else: # CBC + return AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).encrypt(pad(data, 16)) + + def aes_decrypt(self, data, key=None, iv=None): + if key is not None: + aes_key = key + aes_iv = iv + else: + aes_key = self._aes_key + aes_iv = self._aes_iv + if aes_key is None: + raise ValueError("Encrypt need a key") + if isinstance(data, str): + data = bytes.fromhex(data) + if aes_iv is None: # ECB + return unpad( + AES.new(aes_key, AES.MODE_ECB).decrypt(data), len(aes_key) + ).decode() + else: # CBC + return unpad( + AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).decrypt(data), len(aes_key) + ).decode() + + +class MeijuCloudSecurity(CloudSecurity): + def __init__(self, login_key, iot_key, hmac_key): + super().__init__(login_key, iot_key, hmac_key, 10864842703515613082) + + def encrypt_iam_password(self, login_id, data) -> str: + md = md5() + md.update(data.encode("ascii")) + md_second = md5() + md_second.update(md.hexdigest().encode("ascii")) + return md_second.hexdigest() + + +class MSmartCloudSecurity(CloudSecurity): + def __init__(self, login_key, iot_key, hmac_key): + super().__init__( + login_key, iot_key, hmac_key, 13101328926877700970, 16429062708050928556 + ) + + def encrypt_iam_password(self, login_id, data) -> str: + md = md5() + md.update(data.encode("ascii")) + md_second = md5() + md_second.update(md.hexdigest().encode("ascii")) + login_hash = login_id + md_second.hexdigest() + self._login_key + sha = sha256() + sha.update(login_hash.encode("ascii")) + return sha.hexdigest() + + def set_aes_keys(self, encrypted_key, encrypted_iv): + key_digest = sha256(self._login_key.encode("ascii")).hexdigest() + tmp_key = key_digest[:16].encode("ascii") + tmp_iv = key_digest[16:32].encode("ascii") + self._aes_key = self.aes_decrypt(encrypted_key, tmp_key, tmp_iv).encode("ascii") + self._aes_iv = self.aes_decrypt(encrypted_iv, tmp_key, tmp_iv).encode("ascii") + + +class MideaAirSecurity(CloudSecurity): + def __init__(self, login_key): + super().__init__(login_key, None, None) + + def sign(self, url: str, data: Any, random: str) -> str: + payload = unquote_plus(urlencode(sorted(data.items(), key=lambda x: x[0]))) + sha = sha256() + sha.update((urlparse(url).path + payload + self._login_key).encode("ascii")) + return sha.hexdigest() + + +class LocalSecurity: + def __init__(self): + self.blockSize = 16 + self.iv = b"\0" * 16 + self.aes_key = bytes.fromhex( + format(141661095494369103254425781617665632877, "x") + ) + self.salt = bytes.fromhex( + format( + 233912452794221312800602098970898185176935770387238278451789080441632479840061417076563, + "x", + ) + ) + self._tcp_key = None + self._request_count = 0 + self._response_count = 0 + + def aes_decrypt(self, raw): + try: + return unpad( + AES.new(self.aes_key, AES.MODE_ECB).decrypt(bytearray(raw)), 16 + ) + except ValueError: + return bytearray(0) + + def aes_encrypt(self, raw): + return AES.new(self.aes_key, AES.MODE_ECB).encrypt(bytearray(pad(raw, 16))) + + def aes_cbc_decrypt(self, raw, key): + return AES.new(key=key, mode=AES.MODE_CBC, iv=self.iv).decrypt(raw) + + def aes_cbc_encrypt(self, raw, key): + return AES.new(key=key, mode=AES.MODE_CBC, iv=self.iv).encrypt(raw) + + def encode32_data(self, raw): + return md5(raw + self.salt).digest() + + def tcp_key(self, response, key): + if response == b"ERROR": + raise Exception("authentication failed") + if len(response) != 64: + raise Exception("unexpected data length") + payload = response[:32] + sign = response[32:] + plain = self.aes_cbc_decrypt(payload, key) + if sha256(plain).digest() != sign: + raise Exception("sign does not match") + self._tcp_key = strxor(plain, key) + self._request_count = 0 + self._response_count = 0 + return self._tcp_key + + def encode_8370(self, data, msgtype): + header = bytearray([0x83, 0x70]) + size, padding = len(data), 0 + if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): + if (size + 2) % 16 != 0: + padding = 16 - (size + 2 & 0xF) + size += padding + 32 + data += get_random_bytes(padding) + header += size.to_bytes(2, "big") + header += bytearray([0x20, padding << 4 | msgtype]) + data = self._request_count.to_bytes(2, "big") + data + self._request_count += 1 + if self._request_count >= 0xFFFF: + self._request_count = 0 + if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): + sign = sha256(header + data).digest() + data = self.aes_cbc_encrypt(raw=data, key=self._tcp_key) + sign + return header + data + + def decode_8370(self, data): + if len(data) < 6: + return [], data + header = data[:6] + if header[0] != 0x83 or header[1] != 0x70: + raise Exception("not an 8370 message") + size = int.from_bytes(header[2:4], "big") + 8 + leftover = None + if len(data) < size: + return [], data + elif len(data) > size: + leftover = data[size:] + data = data[:size] + if header[4] != 0x20: + raise Exception("missing byte 4") + padding = header[5] >> 4 + msgtype = header[5] & 0xF + data = data[6:] + if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): + sign = data[-32:] + data = data[:-32] + data = self.aes_cbc_decrypt(raw=data, key=self._tcp_key) + if sha256(header + data).digest() != sign: + raise Exception("sign does not match") + if padding: + data = data[:-padding] + self._response_count = int.from_bytes(data[:2], "big") + data = data[2:] + if leftover: + packets, incomplete = self.decode_8370(leftover) + return [data] + packets, incomplete + return [data], b"" diff --git a/custom_components/midea_ac_lan/midea/devices/__init__.py b/custom_components/midea_ac_lan/midea/devices/__init__.py index 2eddd391..6a908f26 100644 --- a/custom_components/midea_ac_lan/midea/devices/__init__.py +++ b/custom_components/midea_ac_lan/midea/devices/__init__.py @@ -1,50 +1,50 @@ -from importlib import import_module -from types import ModuleType - -from homeassistant.core import HomeAssistant - - -async def async_device_selector( - hass: HomeAssistant, - name: str, - device_id: int, - device_type: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, -): - try: - - if device_type < 0xA0: - device_path = f".{'x%02x' % device_type}.device" - else: - device_path = f".{'%02x' % device_type}.device" - - modules: list[ModuleType] = [] - - def _load_device_module() -> None: - """Load all service modules.""" - modules.append(import_module(device_path, __package__)) - - await hass.async_add_import_executor_job(_load_device_module) - - device = modules[0].MideaAppliance( - name=name, - device_id=device_id, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - customize=customize, - ) - except ModuleNotFoundError: - device = None - return device +from importlib import import_module +from types import ModuleType + +from homeassistant.core import HomeAssistant + + +async def async_device_selector( + hass: HomeAssistant, + name: str, + device_id: int, + device_type: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, +): + try: + + if device_type < 0xA0: + device_path = f".{'x%02x' % device_type}.device" + else: + device_path = f".{'%02x' % device_type}.device" + + modules: list[ModuleType] = [] + + def _load_device_module() -> None: + """Load all service modules.""" + modules.append(import_module(device_path, __package__)) + + await hass.async_add_import_executor_job(_load_device_module) + + device = modules[0].MideaAppliance( + name=name, + device_id=device_id, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + customize=customize, + ) + except ModuleNotFoundError: + device = None + return device diff --git a/custom_components/midea_ac_lan/midea/devices/a1/device.py b/custom_components/midea_ac_lan/midea/devices/a1/device.py index 7b3f9474..856b479b 100644 --- a/custom_components/midea_ac_lan/midea/devices/a1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/a1/device.py @@ -1,184 +1,184 @@ -import logging - -from .message import MessageA1Response, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - prompt_tone = "prompt_tone" - child_lock = "child_lock" - mode = "mode" - fan_speed = "fan_speed" - swing = "swing" - target_humidity = "target_humidity" - anion = "anion" - tank = "tank" - water_level_set = "water_level_set" - tank_full = "tank_full" - current_humidity = "current_humidity" - current_temperature = "current_temperature" - - -class MideaA1Device(MiedaDevice): - _modes = ["Manual", "Continuous", "Auto", "Clothes-Dry", "Shoes-Dry"] - _speeds = { - 1: "Lowest", - 40: "Low", - 60: "Medium", - 80: "High", - 102: "Auto", - 127: "Off", - } - _water_level_sets = ["25", "50", "75", "100"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xA1, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.prompt_tone: True, - DeviceAttributes.child_lock: False, - DeviceAttributes.mode: None, - DeviceAttributes.fan_speed: 60, - DeviceAttributes.swing: False, - DeviceAttributes.target_humidity: 35, - DeviceAttributes.anion: False, - DeviceAttributes.tank: 0, - DeviceAttributes.water_level_set: 50, - DeviceAttributes.tank_full: None, - DeviceAttributes.current_humidity: None, - DeviceAttributes.current_temperature: None, - }, - ) - - @property - def modes(self): - return MideaA1Device._modes - - @property - def fan_speeds(self): - return list(MideaA1Device._speeds.values()) - - @property - def water_level_sets(self): - return MideaA1Device._water_level_sets - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageA1Response(msg) - self._protocol_version = message.protocol_version - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - if value <= len(MideaA1Device._modes): - self._attributes[status] = MideaA1Device._modes[value - 1] - else: - self._attributes[status] = None - elif status == DeviceAttributes.fan_speed: - if value in MideaA1Device._speeds.keys(): - self._attributes[status] = MideaA1Device._speeds.get(value) - else: - self._attributes[status] = None - elif status == DeviceAttributes.water_level_set: - self._attributes[status] = str(value) - else: - self._attributes[status] = value - tank_full = self._attributes[DeviceAttributes.tank] >= int( - self._attributes[DeviceAttributes.water_level_set] - ) - if ( - self._attributes[DeviceAttributes.tank_full] is None - or self._attributes[DeviceAttributes.tank_full] != tank_full - ): - self._attributes[DeviceAttributes.tank_full] = tank_full - new_status[str(DeviceAttributes.tank_full)] = tank_full - new_status[str(status)] = self._attributes[status] - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.child_lock = self._attributes[DeviceAttributes.child_lock] - if self._attributes[DeviceAttributes.mode] in MideaA1Device._modes: - message.mode = ( - MideaA1Device._modes.index(self._attributes[DeviceAttributes.mode]) + 1 - ) - else: - message.mode = 1 - message.fan_speed = ( - 40 - if self._attributes[DeviceAttributes.fan_speed] is None - else list(MideaA1Device._speeds.keys())[ - list(MideaA1Device._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - ) - message.target_humidity = self._attributes[DeviceAttributes.target_humidity] - message.swing = self._attributes[DeviceAttributes.swing] - message.anion = self._attributes[DeviceAttributes.anion] - message.water_level_set = int( - self._attributes[DeviceAttributes.water_level_set] - ) - return message - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.prompt_tone: - self._attributes[DeviceAttributes.prompt_tone] = value - self.update_all({DeviceAttributes.prompt_tone.value: value}) - else: - message = self.make_message_set() - if attr == DeviceAttributes.mode: - if value in MideaA1Device._modes: - message.mode = MideaA1Device._modes.index(value) + 1 - elif attr == DeviceAttributes.fan_speed: - if value in MideaA1Device._speeds.values(): - message.fan_speed = list(MideaA1Device._speeds.keys())[ - list(MideaA1Device._speeds.values()).index(value) - ] - elif attr == DeviceAttributes.water_level_set: - if value in MideaA1Device._water_level_sets: - message.water_level_set = int(value) - else: - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(MideaA1Device): - pass +import logging + +from .message import MessageA1Response, MessageQuery, MessageSet + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + prompt_tone = "prompt_tone" + child_lock = "child_lock" + mode = "mode" + fan_speed = "fan_speed" + swing = "swing" + target_humidity = "target_humidity" + anion = "anion" + tank = "tank" + water_level_set = "water_level_set" + tank_full = "tank_full" + current_humidity = "current_humidity" + current_temperature = "current_temperature" + + +class MideaA1Device(MiedaDevice): + _modes = ["Manual", "Continuous", "Auto", "Clothes-Dry", "Shoes-Dry"] + _speeds = { + 1: "Lowest", + 40: "Low", + 60: "Medium", + 80: "High", + 102: "Auto", + 127: "Off", + } + _water_level_sets = ["25", "50", "75", "100"] + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xA1, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.prompt_tone: True, + DeviceAttributes.child_lock: False, + DeviceAttributes.mode: None, + DeviceAttributes.fan_speed: 60, + DeviceAttributes.swing: False, + DeviceAttributes.target_humidity: 35, + DeviceAttributes.anion: False, + DeviceAttributes.tank: 0, + DeviceAttributes.water_level_set: 50, + DeviceAttributes.tank_full: None, + DeviceAttributes.current_humidity: None, + DeviceAttributes.current_temperature: None, + }, + ) + + @property + def modes(self): + return MideaA1Device._modes + + @property + def fan_speeds(self): + return list(MideaA1Device._speeds.values()) + + @property + def water_level_sets(self): + return MideaA1Device._water_level_sets + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageA1Response(msg) + self._protocol_version = message.protocol_version + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.mode: + if value <= len(MideaA1Device._modes): + self._attributes[status] = MideaA1Device._modes[value - 1] + else: + self._attributes[status] = None + elif status == DeviceAttributes.fan_speed: + if value in MideaA1Device._speeds.keys(): + self._attributes[status] = MideaA1Device._speeds.get(value) + else: + self._attributes[status] = None + elif status == DeviceAttributes.water_level_set: + self._attributes[status] = str(value) + else: + self._attributes[status] = value + tank_full = self._attributes[DeviceAttributes.tank] >= int( + self._attributes[DeviceAttributes.water_level_set] + ) + if ( + self._attributes[DeviceAttributes.tank_full] is None + or self._attributes[DeviceAttributes.tank_full] != tank_full + ): + self._attributes[DeviceAttributes.tank_full] = tank_full + new_status[str(DeviceAttributes.tank_full)] = tank_full + new_status[str(status)] = self._attributes[status] + return new_status + + def make_message_set(self): + message = MessageSet(self._protocol_version) + message.power = self._attributes[DeviceAttributes.power] + message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] + message.child_lock = self._attributes[DeviceAttributes.child_lock] + if self._attributes[DeviceAttributes.mode] in MideaA1Device._modes: + message.mode = ( + MideaA1Device._modes.index(self._attributes[DeviceAttributes.mode]) + 1 + ) + else: + message.mode = 1 + message.fan_speed = ( + 40 + if self._attributes[DeviceAttributes.fan_speed] is None + else list(MideaA1Device._speeds.keys())[ + list(MideaA1Device._speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + ) + ] + ) + message.target_humidity = self._attributes[DeviceAttributes.target_humidity] + message.swing = self._attributes[DeviceAttributes.swing] + message.anion = self._attributes[DeviceAttributes.anion] + message.water_level_set = int( + self._attributes[DeviceAttributes.water_level_set] + ) + return message + + def set_attribute(self, attr, value): + if attr == DeviceAttributes.prompt_tone: + self._attributes[DeviceAttributes.prompt_tone] = value + self.update_all({DeviceAttributes.prompt_tone.value: value}) + else: + message = self.make_message_set() + if attr == DeviceAttributes.mode: + if value in MideaA1Device._modes: + message.mode = MideaA1Device._modes.index(value) + 1 + elif attr == DeviceAttributes.fan_speed: + if value in MideaA1Device._speeds.values(): + message.fan_speed = list(MideaA1Device._speeds.keys())[ + list(MideaA1Device._speeds.values()).index(value) + ] + elif attr == DeviceAttributes.water_level_set: + if value in MideaA1Device._water_level_sets: + message.water_level_set = int(value) + else: + setattr(message, str(attr), value) + self.build_send(message) + + +class MideaAppliance(MideaA1Device): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/a1/message.py b/custom_components/midea_ac_lan/midea/devices/a1/message.py index 00958165..f6a43b7a 100644 --- a/custom_components/midea_ac_lan/midea/devices/a1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/a1/message.py @@ -1,222 +1,222 @@ -from enum import IntEnum - -from ...core.crc8 import calculate -from ...core.message import ( - MessageBody, - MessageRequest, - MessageResponse, - MessageType, - NewProtocolMessageBody, -) - - -class NewProtocolTags(IntEnum): - light = 0x005B - - -class MessageA1Base(MessageRequest): - _message_serial = 0 - - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xA1, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - MessageA1Base._message_serial += 1 - if MessageA1Base._message_serial >= 100: - MessageA1Base._message_serial = 1 - self._message_id = MessageA1Base._message_serial - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) - body.append(calculate(body)) - return body - - -class MessageQuery(MessageA1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray( - [ - 0x81, - 0x00, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageNewProtocolQuery(MessageA1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0xB1, - ) - - @property - def _body(self): - query_params = [NewProtocolTags.light] - _body = bytearray([len(query_params)]) - for param in query_params: - _body.extend([param & 0xFF, param >> 8]) - return _body - - -class MessageSet(MessageA1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x48, - ) - self.power = False - self.prompt_tone = False - self.mode = 1 - self.fan_speed = 40 - self.child_lock = False - self.target_humidity = 40 - self.swing = False - self.anion = False - self.water_level_set = 50 - - @property - def _body(self): - # byte1, power, prompt_tone - power = 0x01 if self.power else 0x00 - prompt_tone = 0x40 if self.prompt_tone else 0x00 - # byte2 mode - mode = self.mode - # byte3 fan_speed - fan_speed = self.fan_speed - # byte7 target_humidity - target_humidity = self.target_humidity - # byte8 child_lock - child_lock = 0x80 if self.child_lock else 0x00 - # byte9 anion - anion = 0x40 if self.anion else 0x00 - # byte10 swing - swing = 0x08 if self.swing else 0x00 - # byte 13 water_level_set - water_level_set = self.water_level_set - return bytearray( - [ - power | prompt_tone | 0x02, - mode, - fan_speed, - 0x00, - 0x00, - 0x00, - target_humidity, - child_lock, - anion, - swing, - 0x00, - 0x00, - water_level_set, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageNewProtocolSet(MessageA1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0xB0, - ) - self.light = None - - @property - def _body(self): - pack_count = 0 - payload = bytearray([0x00]) - if self.light is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.light, - value=bytearray([0x01 if self.light else 0x00]), - ) - ) - payload[0] = pack_count - return payload - - -class A1GeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.mode = body[2] & 0x0F - self.fan_speed = body[3] & 0x7F - self.target_humidity = 35 if (body[7] < 35) else body[7] - self.child_lock = (body[8] & 0x80) > 0 - self.anion = (body[9] & 0x40) > 0 - self.tank = body[10] & 0x7F - self.water_level_set = body[15] - self.current_humidity = body[16] - self.current_temperature = (body[17] - 50) / 2 - self.swing = (body[19] & 0x20) > 0 - if self.fan_speed < 5: - self.fan_speed = 1 - - -class A1NewProtocolMessageBody(NewProtocolMessageBody): - def __init__(self, body, bt): - super().__init__(body, bt) - params = self.parse() - if NewProtocolTags.light in params: - self.light = params[NewProtocolTags.light][0] > 0 - - -class MessageA1Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: - if self.body_type in [0xB0, 0xB1, 0xB5]: - self.set_body(A1NewProtocolMessageBody(super().body, self.body_type)) - else: - self.set_body(A1GeneralMessageBody(super().body)) - elif self.message_type == MessageType.notify2 and self.body_type == 0xA0: - self.set_body(A1GeneralMessageBody(super().body)) - self.set_attr() +from enum import IntEnum + +from ...core.crc8 import calculate +from ...core.message import ( + MessageBody, + MessageRequest, + MessageResponse, + MessageType, + NewProtocolMessageBody, +) + + +class NewProtocolTags(IntEnum): + light = 0x005B + + +class MessageA1Base(MessageRequest): + _message_serial = 0 + + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xA1, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + MessageA1Base._message_serial += 1 + if MessageA1Base._message_serial >= 100: + MessageA1Base._message_serial = 1 + self._message_id = MessageA1Base._message_serial + + @property + def _body(self): + raise NotImplementedError + + @property + def body(self): + body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) + body.append(calculate(body)) + return body + + +class MessageQuery(MessageA1Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x41, + ) + + @property + def _body(self): + return bytearray( + [ + 0x81, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class MessageNewProtocolQuery(MessageA1Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0xB1, + ) + + @property + def _body(self): + query_params = [NewProtocolTags.light] + _body = bytearray([len(query_params)]) + for param in query_params: + _body.extend([param & 0xFF, param >> 8]) + return _body + + +class MessageSet(MessageA1Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x48, + ) + self.power = False + self.prompt_tone = False + self.mode = 1 + self.fan_speed = 40 + self.child_lock = False + self.target_humidity = 40 + self.swing = False + self.anion = False + self.water_level_set = 50 + + @property + def _body(self): + # byte1, power, prompt_tone + power = 0x01 if self.power else 0x00 + prompt_tone = 0x40 if self.prompt_tone else 0x00 + # byte2 mode + mode = self.mode + # byte3 fan_speed + fan_speed = self.fan_speed + # byte7 target_humidity + target_humidity = self.target_humidity + # byte8 child_lock + child_lock = 0x80 if self.child_lock else 0x00 + # byte9 anion + anion = 0x40 if self.anion else 0x00 + # byte10 swing + swing = 0x08 if self.swing else 0x00 + # byte 13 water_level_set + water_level_set = self.water_level_set + return bytearray( + [ + power | prompt_tone | 0x02, + mode, + fan_speed, + 0x00, + 0x00, + 0x00, + target_humidity, + child_lock, + anion, + swing, + 0x00, + 0x00, + water_level_set, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class MessageNewProtocolSet(MessageA1Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0xB0, + ) + self.light = None + + @property + def _body(self): + pack_count = 0 + payload = bytearray([0x00]) + if self.light is not None: + pack_count += 1 + payload.extend( + NewProtocolMessageBody.pack( + param=NewProtocolTags.light, + value=bytearray([0x01 if self.light else 0x00]), + ) + ) + payload[0] = pack_count + return payload + + +class A1GeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[1] & 0x01) > 0 + self.mode = body[2] & 0x0F + self.fan_speed = body[3] & 0x7F + self.target_humidity = 35 if (body[7] < 35) else body[7] + self.child_lock = (body[8] & 0x80) > 0 + self.anion = (body[9] & 0x40) > 0 + self.tank = body[10] & 0x7F + self.water_level_set = body[15] + self.current_humidity = body[16] + self.current_temperature = (body[17] - 50) / 2 + self.swing = (body[19] & 0x20) > 0 + if self.fan_speed < 5: + self.fan_speed = 1 + + +class A1NewProtocolMessageBody(NewProtocolMessageBody): + def __init__(self, body, bt): + super().__init__(body, bt) + params = self.parse() + if NewProtocolTags.light in params: + self.light = params[NewProtocolTags.light][0] > 0 + + +class MessageA1Response(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify1, + ]: + if self.body_type in [0xB0, 0xB1, 0xB5]: + self.set_body(A1NewProtocolMessageBody(super().body, self.body_type)) + else: + self.set_body(A1GeneralMessageBody(super().body)) + elif self.message_type == MessageType.notify2 and self.body_type == 0xA0: + self.set_body(A1GeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ac/device.py b/custom_components/midea_ac_lan/midea/devices/ac/device.py index 98025841..62e2e384 100644 --- a/custom_components/midea_ac_lan/midea/devices/ac/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ac/device.py @@ -1,375 +1,375 @@ -import json -import logging - -from .message import ( - MessageACResponse, - MessageGeneralSet, - MessageNewProtocolQuery, - MessageNewProtocolSet, - MessagePowerQuery, - MessageQuery, - MessageSubProtocolQuery, - MessageSubProtocolSet, - MessageToggleDisplay, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - prompt_tone = "prompt_tone" - power = "power" - mode = "mode" - target_temperature = "target_temperature" - fan_speed = "fan_speed" - swing_vertical = "swing_vertical" - swing_horizontal = "swing_horizontal" - boost_mode = "boost_mode" - smart_eye = "smart_eye" - dry = "dry" - eco_mode = "eco_mode" - aux_heating = "aux_heating" - sleep_mode = "sleep_mode" - natural_wind = "natural_wind" - temp_fahrenheit = "temp_fahrenheit" - screen_display = "screen_display" - screen_display_alternate = "screen_display_alternate" - full_dust = "full_dust" - frost_protect = "frost_protect" - comfort_mode = "comfort_mode" - indoor_temperature = "indoor_temperature" - outdoor_temperature = "outdoor_temperature" - indirect_wind = "indirect_wind" - indoor_humidity = "indoor_humidity" - breezeless = "breezeless" - fresh_air_power = "fresh_air_power" - fresh_air_fan_speed = "fresh_air_fan_speed" - fresh_air_mode = "fresh_air_mode" - fresh_air_1 = "fresh_air_1" - fresh_air_2 = "fresh_air_2" - total_energy_consumption = "total_energy_consumption" - current_energy_consumption = "current_energy_consumption" - realtime_power = "realtime_power" - - -class MideaACDevice(MiedaDevice): - _fresh_air_fan_speeds = { - 0: "Off", - 20: "Silent", - 40: "Low", - 60: "Medium", - 80: "High", - 100: "Full", - } - _fresh_air_fan_speeds_rev = dict(reversed(_fresh_air_fan_speeds.items())) - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xAC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.prompt_tone: True, - DeviceAttributes.power: False, - DeviceAttributes.mode: 0, - DeviceAttributes.target_temperature: 24.0, - DeviceAttributes.fan_speed: 102, - DeviceAttributes.swing_vertical: False, - DeviceAttributes.swing_horizontal: False, - DeviceAttributes.smart_eye: False, - DeviceAttributes.dry: False, - DeviceAttributes.aux_heating: False, - DeviceAttributes.boost_mode: False, - DeviceAttributes.sleep_mode: False, - DeviceAttributes.frost_protect: False, - DeviceAttributes.comfort_mode: False, - DeviceAttributes.eco_mode: False, - DeviceAttributes.natural_wind: False, - DeviceAttributes.temp_fahrenheit: False, - DeviceAttributes.screen_display: False, - DeviceAttributes.screen_display_alternate: False, - DeviceAttributes.full_dust: False, - DeviceAttributes.indoor_temperature: None, - DeviceAttributes.outdoor_temperature: None, - DeviceAttributes.indirect_wind: False, - DeviceAttributes.indoor_humidity: None, - DeviceAttributes.breezeless: False, - DeviceAttributes.total_energy_consumption: None, - DeviceAttributes.current_energy_consumption: None, - DeviceAttributes.realtime_power: None, - DeviceAttributes.fresh_air_power: False, - DeviceAttributes.fresh_air_fan_speed: 0, - DeviceAttributes.fresh_air_mode: None, - DeviceAttributes.fresh_air_1: None, - DeviceAttributes.fresh_air_2: None, - }, - ) - self._fresh_air_version = None - self._default_temperature_step = 0.5 - self._temperature_step = None - self._used_subprotocol = False - self._bb_sn8_flag = False - self._bb_timer = False - self._power_analysis_method = None - self._default_power_analysis_method = 1 - self.set_customize(customize) - - @property - def temperature_step(self): - return self._temperature_step - - @property - def fresh_air_fan_speeds(self): - return list(MideaACDevice._fresh_air_fan_speeds.values()) - - def build_query(self): - if self._used_subprotocol: - return [ - MessageSubProtocolQuery(self._protocol_version, 0x10), - MessageSubProtocolQuery(self._protocol_version, 0x11), - MessageSubProtocolQuery(self._protocol_version, 0x30), - ] - return [ - MessageQuery(self._protocol_version), - MessageNewProtocolQuery(self._protocol_version), - MessagePowerQuery(self._protocol_version), - ] - - def process_message(self, msg): - message = MessageACResponse(msg, self._power_analysis_method) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - has_fresh_air = False - if hasattr(message, "used_subprotocol"): - self._used_subprotocol = True - if hasattr(message, "sn8_flag"): - self._bb_sn8_flag = message.sn8_flag - if hasattr(message, "timer"): - self._bb_timer = message.timer - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.fresh_air_power: - has_fresh_air = True - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - if has_fresh_air: - if self._attributes[DeviceAttributes.fresh_air_power]: - for k, v in MideaACDevice._fresh_air_fan_speeds_rev.items(): - if self._attributes[DeviceAttributes.fresh_air_fan_speed] > k: - break - else: - self._attributes[DeviceAttributes.fresh_air_mode] = v - else: - self._attributes[DeviceAttributes.fresh_air_mode] = "Off" - new_status[DeviceAttributes.fresh_air_mode.value] = self._attributes[ - DeviceAttributes.fresh_air_mode - ] - if not self._attributes[DeviceAttributes.power] or ( - DeviceAttributes.swing_vertical in new_status - and self._attributes[DeviceAttributes.swing_vertical] - ): - self._attributes[DeviceAttributes.indirect_wind] = False - new_status[DeviceAttributes.indirect_wind.value] = False - if not self._attributes[DeviceAttributes.power]: - self._attributes[DeviceAttributes.screen_display] = False - new_status[DeviceAttributes.screen_display.value] = False - if self._attributes[DeviceAttributes.fresh_air_1] is not None: - self._fresh_air_version = DeviceAttributes.fresh_air_1 - elif self._attributes[DeviceAttributes.fresh_air_2] is not None: - self._fresh_air_version = DeviceAttributes.fresh_air_2 - return new_status - - def make_message_set(self): - message = MessageGeneralSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - message.fan_speed = self._attributes[DeviceAttributes.fan_speed] - message.swing_vertical = self._attributes[DeviceAttributes.swing_vertical] - message.swing_horizontal = self._attributes[DeviceAttributes.swing_horizontal] - message.boost_mode = self._attributes[DeviceAttributes.boost_mode] - message.smart_eye = self._attributes[DeviceAttributes.smart_eye] - message.dry = self._attributes[DeviceAttributes.dry] - message.eco_mode = self._attributes[DeviceAttributes.eco_mode] - message.aux_heating = self._attributes[DeviceAttributes.aux_heating] - message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] - message.natural_wind = self._attributes[DeviceAttributes.natural_wind] - message.temp_fahrenheit = self._attributes[DeviceAttributes.temp_fahrenheit] - message.frost_protect = self._attributes[DeviceAttributes.frost_protect] - message.comfort_mode = self._attributes[DeviceAttributes.comfort_mode] - return message - - def make_subptotocol_message_set(self): - message = MessageSubProtocolSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.aux_heating = self._attributes[DeviceAttributes.aux_heating] - message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - message.fan_speed = self._attributes[DeviceAttributes.fan_speed] - message.boost_mode = self._attributes[DeviceAttributes.boost_mode] - message.dry = self._attributes[DeviceAttributes.dry] - message.eco_mode = self._attributes[DeviceAttributes.eco_mode] - message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] - message.sn8_flag = self._bb_sn8_flag - message.timer = self._bb_timer - return message - - def make_message_uniq_set(self): - if self._used_subprotocol: - message = self.make_subptotocol_message_set() - else: - message = self.make_message_set() - return message - - def set_attribute(self, attr, value): - # if nat a sensor - message = None - if attr not in [ - DeviceAttributes.indoor_temperature, - DeviceAttributes.outdoor_temperature, - DeviceAttributes.indoor_humidity, - DeviceAttributes.full_dust, - DeviceAttributes.total_energy_consumption, - DeviceAttributes.current_energy_consumption, - DeviceAttributes.realtime_power, - ]: - if attr == DeviceAttributes.prompt_tone: - self._attributes[DeviceAttributes.prompt_tone] = value - self.update_all({DeviceAttributes.prompt_tone.value: value}) - elif attr == DeviceAttributes.screen_display: - message = MessageToggleDisplay(self._protocol_version) - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - elif attr in [ - DeviceAttributes.indirect_wind, - DeviceAttributes.breezeless, - DeviceAttributes.screen_display_alternate, - ]: - message = MessageNewProtocolSet(self._protocol_version) - setattr(message, str(attr), value) - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - elif attr == DeviceAttributes.fresh_air_power: - if self._fresh_air_version is not None: - message = MessageNewProtocolSet(self._protocol_version) - setattr( - message, - str(self._fresh_air_version), - [value, self._attributes[DeviceAttributes.fresh_air_fan_speed]], - ) - elif attr == DeviceAttributes.fresh_air_mode: - if value in MideaACDevice._fresh_air_fan_speeds.values(): - speed = list(MideaACDevice._fresh_air_fan_speeds.keys())[ - list(MideaACDevice._fresh_air_fan_speeds.values()).index(value) - ] - fresh_air = ( - [True, speed] - if speed > 0 - else [ - False, - self._attributes[DeviceAttributes.fresh_air_fan_speed], - ] - ) - message = MessageNewProtocolSet(self._protocol_version) - setattr(message, str(self._fresh_air_version), fresh_air) - elif not value: - message = MessageNewProtocolSet(self._protocol_version) - setattr( - message, - str(self._fresh_air_version), - [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]], - ) - elif attr == DeviceAttributes.fresh_air_fan_speed: - if self._fresh_air_version is not None: - message = MessageNewProtocolSet(self._protocol_version) - fresh_air = ( - [True, value] - if value > 0 - else [ - False, - self._attributes[DeviceAttributes.fresh_air_fan_speed], - ] - ) - setattr(message, str(self._fresh_air_version), fresh_air) - elif attr in self._attributes.keys(): - message = self.make_message_uniq_set() - if attr in [ - DeviceAttributes.boost_mode, - DeviceAttributes.sleep_mode, - DeviceAttributes.frost_protect, - DeviceAttributes.comfort_mode, - DeviceAttributes.eco_mode, - ]: - message.boost_mode = False - message.sleep_mode = False - message.comfort_mode = False - message.eco_mode = False - message.frost_protect = False - setattr(message, str(attr), value) - if attr == DeviceAttributes.mode: - setattr(message, str(DeviceAttributes.power.value), True) - if message is not None: - self.build_send(message) - - def set_target_temperature(self, target_temperature, mode): - message = self.make_message_uniq_set() - message.target_temperature = target_temperature - if mode is not None: - message.power = True - message.mode = mode - self.build_send(message) - - def set_swing(self, swing_vertical, swing_horizontal): - message = self.make_message_uniq_set() - message.swing_vertical = swing_vertical - message.swing_horizontal = swing_horizontal - self.build_send(message) - - def set_customize(self, customize): - self._temperature_step = self._default_temperature_step - self._power_analysis_method = self._default_power_analysis_method - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "temperature_step" in params: - self._temperature_step = params.get("temperature_step") - if params and "power_analysis_method" in params: - self._power_analysis_method = params.get("power_analysis_method") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"temperature_step": self._temperature_step}) - - -class MideaAppliance(MideaACDevice): - pass +import json +import logging + +from .message import ( + MessageACResponse, + MessageGeneralSet, + MessageNewProtocolQuery, + MessageNewProtocolSet, + MessagePowerQuery, + MessageQuery, + MessageSubProtocolQuery, + MessageSubProtocolSet, + MessageToggleDisplay, +) + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + prompt_tone = "prompt_tone" + power = "power" + mode = "mode" + target_temperature = "target_temperature" + fan_speed = "fan_speed" + swing_vertical = "swing_vertical" + swing_horizontal = "swing_horizontal" + boost_mode = "boost_mode" + smart_eye = "smart_eye" + dry = "dry" + eco_mode = "eco_mode" + aux_heating = "aux_heating" + sleep_mode = "sleep_mode" + natural_wind = "natural_wind" + temp_fahrenheit = "temp_fahrenheit" + screen_display = "screen_display" + screen_display_alternate = "screen_display_alternate" + full_dust = "full_dust" + frost_protect = "frost_protect" + comfort_mode = "comfort_mode" + indoor_temperature = "indoor_temperature" + outdoor_temperature = "outdoor_temperature" + indirect_wind = "indirect_wind" + indoor_humidity = "indoor_humidity" + breezeless = "breezeless" + fresh_air_power = "fresh_air_power" + fresh_air_fan_speed = "fresh_air_fan_speed" + fresh_air_mode = "fresh_air_mode" + fresh_air_1 = "fresh_air_1" + fresh_air_2 = "fresh_air_2" + total_energy_consumption = "total_energy_consumption" + current_energy_consumption = "current_energy_consumption" + realtime_power = "realtime_power" + + +class MideaACDevice(MiedaDevice): + _fresh_air_fan_speeds = { + 0: "Off", + 20: "Silent", + 40: "Low", + 60: "Medium", + 80: "High", + 100: "Full", + } + _fresh_air_fan_speeds_rev = dict(reversed(_fresh_air_fan_speeds.items())) + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xAC, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.prompt_tone: True, + DeviceAttributes.power: False, + DeviceAttributes.mode: 0, + DeviceAttributes.target_temperature: 24.0, + DeviceAttributes.fan_speed: 102, + DeviceAttributes.swing_vertical: False, + DeviceAttributes.swing_horizontal: False, + DeviceAttributes.smart_eye: False, + DeviceAttributes.dry: False, + DeviceAttributes.aux_heating: False, + DeviceAttributes.boost_mode: False, + DeviceAttributes.sleep_mode: False, + DeviceAttributes.frost_protect: False, + DeviceAttributes.comfort_mode: False, + DeviceAttributes.eco_mode: False, + DeviceAttributes.natural_wind: False, + DeviceAttributes.temp_fahrenheit: False, + DeviceAttributes.screen_display: False, + DeviceAttributes.screen_display_alternate: False, + DeviceAttributes.full_dust: False, + DeviceAttributes.indoor_temperature: None, + DeviceAttributes.outdoor_temperature: None, + DeviceAttributes.indirect_wind: False, + DeviceAttributes.indoor_humidity: None, + DeviceAttributes.breezeless: False, + DeviceAttributes.total_energy_consumption: None, + DeviceAttributes.current_energy_consumption: None, + DeviceAttributes.realtime_power: None, + DeviceAttributes.fresh_air_power: False, + DeviceAttributes.fresh_air_fan_speed: 0, + DeviceAttributes.fresh_air_mode: None, + DeviceAttributes.fresh_air_1: None, + DeviceAttributes.fresh_air_2: None, + }, + ) + self._fresh_air_version = None + self._default_temperature_step = 0.5 + self._temperature_step = None + self._used_subprotocol = False + self._bb_sn8_flag = False + self._bb_timer = False + self._power_analysis_method = None + self._default_power_analysis_method = 1 + self.set_customize(customize) + + @property + def temperature_step(self): + return self._temperature_step + + @property + def fresh_air_fan_speeds(self): + return list(MideaACDevice._fresh_air_fan_speeds.values()) + + def build_query(self): + if self._used_subprotocol: + return [ + MessageSubProtocolQuery(self._protocol_version, 0x10), + MessageSubProtocolQuery(self._protocol_version, 0x11), + MessageSubProtocolQuery(self._protocol_version, 0x30), + ] + return [ + MessageQuery(self._protocol_version), + MessageNewProtocolQuery(self._protocol_version), + MessagePowerQuery(self._protocol_version), + ] + + def process_message(self, msg): + message = MessageACResponse(msg, self._power_analysis_method) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + has_fresh_air = False + if hasattr(message, "used_subprotocol"): + self._used_subprotocol = True + if hasattr(message, "sn8_flag"): + self._bb_sn8_flag = message.sn8_flag + if hasattr(message, "timer"): + self._bb_timer = message.timer + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.fresh_air_power: + has_fresh_air = True + self._attributes[status] = value + new_status[str(status)] = self._attributes[status] + if has_fresh_air: + if self._attributes[DeviceAttributes.fresh_air_power]: + for k, v in MideaACDevice._fresh_air_fan_speeds_rev.items(): + if self._attributes[DeviceAttributes.fresh_air_fan_speed] > k: + break + else: + self._attributes[DeviceAttributes.fresh_air_mode] = v + else: + self._attributes[DeviceAttributes.fresh_air_mode] = "Off" + new_status[DeviceAttributes.fresh_air_mode.value] = self._attributes[ + DeviceAttributes.fresh_air_mode + ] + if not self._attributes[DeviceAttributes.power] or ( + DeviceAttributes.swing_vertical in new_status + and self._attributes[DeviceAttributes.swing_vertical] + ): + self._attributes[DeviceAttributes.indirect_wind] = False + new_status[DeviceAttributes.indirect_wind.value] = False + if not self._attributes[DeviceAttributes.power]: + self._attributes[DeviceAttributes.screen_display] = False + new_status[DeviceAttributes.screen_display.value] = False + if self._attributes[DeviceAttributes.fresh_air_1] is not None: + self._fresh_air_version = DeviceAttributes.fresh_air_1 + elif self._attributes[DeviceAttributes.fresh_air_2] is not None: + self._fresh_air_version = DeviceAttributes.fresh_air_2 + return new_status + + def make_message_set(self): + message = MessageGeneralSet(self._protocol_version) + message.power = self._attributes[DeviceAttributes.power] + message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] + message.mode = self._attributes[DeviceAttributes.mode] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] + message.fan_speed = self._attributes[DeviceAttributes.fan_speed] + message.swing_vertical = self._attributes[DeviceAttributes.swing_vertical] + message.swing_horizontal = self._attributes[DeviceAttributes.swing_horizontal] + message.boost_mode = self._attributes[DeviceAttributes.boost_mode] + message.smart_eye = self._attributes[DeviceAttributes.smart_eye] + message.dry = self._attributes[DeviceAttributes.dry] + message.eco_mode = self._attributes[DeviceAttributes.eco_mode] + message.aux_heating = self._attributes[DeviceAttributes.aux_heating] + message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] + message.natural_wind = self._attributes[DeviceAttributes.natural_wind] + message.temp_fahrenheit = self._attributes[DeviceAttributes.temp_fahrenheit] + message.frost_protect = self._attributes[DeviceAttributes.frost_protect] + message.comfort_mode = self._attributes[DeviceAttributes.comfort_mode] + return message + + def make_subptotocol_message_set(self): + message = MessageSubProtocolSet(self._protocol_version) + message.power = self._attributes[DeviceAttributes.power] + message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] + message.aux_heating = self._attributes[DeviceAttributes.aux_heating] + message.mode = self._attributes[DeviceAttributes.mode] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] + message.fan_speed = self._attributes[DeviceAttributes.fan_speed] + message.boost_mode = self._attributes[DeviceAttributes.boost_mode] + message.dry = self._attributes[DeviceAttributes.dry] + message.eco_mode = self._attributes[DeviceAttributes.eco_mode] + message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] + message.sn8_flag = self._bb_sn8_flag + message.timer = self._bb_timer + return message + + def make_message_uniq_set(self): + if self._used_subprotocol: + message = self.make_subptotocol_message_set() + else: + message = self.make_message_set() + return message + + def set_attribute(self, attr, value): + # if nat a sensor + message = None + if attr not in [ + DeviceAttributes.indoor_temperature, + DeviceAttributes.outdoor_temperature, + DeviceAttributes.indoor_humidity, + DeviceAttributes.full_dust, + DeviceAttributes.total_energy_consumption, + DeviceAttributes.current_energy_consumption, + DeviceAttributes.realtime_power, + ]: + if attr == DeviceAttributes.prompt_tone: + self._attributes[DeviceAttributes.prompt_tone] = value + self.update_all({DeviceAttributes.prompt_tone.value: value}) + elif attr == DeviceAttributes.screen_display: + message = MessageToggleDisplay(self._protocol_version) + message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] + elif attr in [ + DeviceAttributes.indirect_wind, + DeviceAttributes.breezeless, + DeviceAttributes.screen_display_alternate, + ]: + message = MessageNewProtocolSet(self._protocol_version) + setattr(message, str(attr), value) + message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] + elif attr == DeviceAttributes.fresh_air_power: + if self._fresh_air_version is not None: + message = MessageNewProtocolSet(self._protocol_version) + setattr( + message, + str(self._fresh_air_version), + [value, self._attributes[DeviceAttributes.fresh_air_fan_speed]], + ) + elif attr == DeviceAttributes.fresh_air_mode: + if value in MideaACDevice._fresh_air_fan_speeds.values(): + speed = list(MideaACDevice._fresh_air_fan_speeds.keys())[ + list(MideaACDevice._fresh_air_fan_speeds.values()).index(value) + ] + fresh_air = ( + [True, speed] + if speed > 0 + else [ + False, + self._attributes[DeviceAttributes.fresh_air_fan_speed], + ] + ) + message = MessageNewProtocolSet(self._protocol_version) + setattr(message, str(self._fresh_air_version), fresh_air) + elif not value: + message = MessageNewProtocolSet(self._protocol_version) + setattr( + message, + str(self._fresh_air_version), + [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]], + ) + elif attr == DeviceAttributes.fresh_air_fan_speed: + if self._fresh_air_version is not None: + message = MessageNewProtocolSet(self._protocol_version) + fresh_air = ( + [True, value] + if value > 0 + else [ + False, + self._attributes[DeviceAttributes.fresh_air_fan_speed], + ] + ) + setattr(message, str(self._fresh_air_version), fresh_air) + elif attr in self._attributes.keys(): + message = self.make_message_uniq_set() + if attr in [ + DeviceAttributes.boost_mode, + DeviceAttributes.sleep_mode, + DeviceAttributes.frost_protect, + DeviceAttributes.comfort_mode, + DeviceAttributes.eco_mode, + ]: + message.boost_mode = False + message.sleep_mode = False + message.comfort_mode = False + message.eco_mode = False + message.frost_protect = False + setattr(message, str(attr), value) + if attr == DeviceAttributes.mode: + setattr(message, str(DeviceAttributes.power.value), True) + if message is not None: + self.build_send(message) + + def set_target_temperature(self, target_temperature, mode): + message = self.make_message_uniq_set() + message.target_temperature = target_temperature + if mode is not None: + message.power = True + message.mode = mode + self.build_send(message) + + def set_swing(self, swing_vertical, swing_horizontal): + message = self.make_message_uniq_set() + message.swing_vertical = swing_vertical + message.swing_horizontal = swing_horizontal + self.build_send(message) + + def set_customize(self, customize): + self._temperature_step = self._default_temperature_step + self._power_analysis_method = self._default_power_analysis_method + if customize and len(customize) > 0: + try: + params = json.loads(customize) + if params and "temperature_step" in params: + self._temperature_step = params.get("temperature_step") + if params and "power_analysis_method" in params: + self._power_analysis_method = params.get("power_analysis_method") + except Exception as e: + _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") + self.update_all({"temperature_step": self._temperature_step}) + + +class MideaAppliance(MideaACDevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/ac/message.py b/custom_components/midea_ac_lan/midea/devices/ac/message.py index c6c26f6d..9c5816b3 100644 --- a/custom_components/midea_ac_lan/midea/devices/ac/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ac/message.py @@ -1,721 +1,721 @@ -from enum import IntEnum - -from ...core.crc8 import calculate -from ...core.message import ( - MessageBody, - MessageRequest, - MessageResponse, - MessageType, - NewProtocolMessageBody, -) - -BB_AC_MODES = [0, 3, 1, 2, 4, 5] - - -class NewProtocolTags(IntEnum): - indoor_humidity = 0x0015 - screen_display = 0x0017 - breezeless = 0x0018 - prompt_tone = 0x001A - indirect_wind = 0x0042 - fresh_air_1 = 0x0233 - fresh_air_2 = 0x004B - - -class MessageACBase(MessageRequest): - _message_serial = 0 - - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xAC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - MessageACBase._message_serial += 1 - if MessageACBase._message_serial >= 254: - MessageACBase._message_serial = 1 - self._message_id = MessageACBase._message_serial - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) - body.append(calculate(body)) - return body - - -class MessageQuery(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray( - [ - 0x81, - 0x00, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessagePowerQuery(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray([0x21, 0x01, 0x44, 0x00, 0x01]) - - @property - def body(self): - body = bytearray([self.body_type]) + self._body - body.append(calculate(body)) - return body - - -class MessageToggleDisplay(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - self.prompt_tone = False - - @property - def _body(self): - prompt_tone = 0x40 if self.prompt_tone else 0 - return bytearray( - [ - 0x02 | prompt_tone, - 0x00, - 0xFF, - 0x02, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageNewProtocolQuery(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0xB1, - ) - - @property - def _body(self): - query_params = [ - NewProtocolTags.indirect_wind, - NewProtocolTags.breezeless, - NewProtocolTags.indoor_humidity, - NewProtocolTags.screen_display, - NewProtocolTags.fresh_air_1, - NewProtocolTags.fresh_air_2, - ] - - _body = bytearray([len(query_params)]) - for param in query_params: - _body.extend([param & 0xFF, param >> 8]) - return _body - - -class MessageSubProtocol(MessageACBase): - def __init__(self, protocol_version, message_type, subprotocol_query_type): - super().__init__( - protocol_version=protocol_version, message_type=message_type, body_type=0xAA - ) - self._subprotocol_query_type = subprotocol_query_type - - @property - def _subprotocol_body(self): - return bytes([]) - - @property - def body(self): - body = bytearray([self.body_type]) + self._body - body.append(calculate(body)) - body.append(self.checksum(body)) - return body - - @property - def _body(self): - _subprotocol_body = self._subprotocol_body - _body = bytearray( - [ - 6 - + 2 - + (len(_subprotocol_body) if _subprotocol_body is not None else 0), - 0x00, - 0xFF, - 0xFF, - self._subprotocol_query_type, - ] - ) - if _subprotocol_body is not None: - _body.extend(_subprotocol_body) - return _body - - -class MessageSubProtocolQuery(MessageSubProtocol): - def __init__(self, protocol_version, subprotocol_query_type): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - subprotocol_query_type=subprotocol_query_type, - ) - - -class MessageSubProtocolSet(MessageSubProtocol): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - subprotocol_query_type=0x20, - ) - self.power = False - self.mode = 0 - self.target_temperature = 20.0 - self.fan_speed = 102 - self.boost_mode = False - self.aux_heating = False - self.dry = False - self.eco_mode = False - self.sleep_mode = False - self.sn8_flag = False - self.timer = False - self.prompt_tone = False - - @property - def _subprotocol_body(self): - power = 0x01 if self.power else 0 - dry = 0x10 if self.power and self.dry else 0 - boost_mode = 0x20 if self.boost_mode else 0 - aux_heating = 0x40 if self.aux_heating else 0x80 - sleep_mode = 0x80 if self.sleep_mode else 0 - try: - mode = 0 if self.mode == 0 else BB_AC_MODES[self.mode] - 1 - except IndexError: - mode = 2 # set Auto if invalid mode - target_temperature = int(self.target_temperature * 2 + 30) - water_model_temperature_set = int((self.target_temperature - 1) * 2 + 50) - fan_speed = self.fan_speed - eco = 0x40 if self.eco_mode else 0 - - prompt_tone = 0x01 if self.prompt_tone else 0 - timer = 0x04 if (self.sn8_flag and self.timer) else 0 - return bytearray( - [ - 0x02 | boost_mode | power | dry, - aux_heating, - sleep_mode, - 0x00, - 0x00, - mode, - target_temperature, - fan_speed, - 0x32, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0x01, - 0x00, - 0x01, - water_model_temperature_set, - prompt_tone, - target_temperature, - 0x32, - 0x66, - 0x00, - eco | timer, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x08, - ] - ) - - -class MessageGeneralSet(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x40, - ) - self.power = False - self.prompt_tone = True - self.mode = 0 - self.target_temperature = 20.0 - self.fan_speed = 102 - self.swing_vertical = False - self.swing_horizontal = False - self.boost_mode = False - self.smart_eye = False - self.dry = False - self.aux_heating = False - self.eco_mode = False - self.temp_fahrenheit = False - self.sleep_mode = False - self.natural_wind = False - self.frost_protect = False - self.comfort_mode = False - - @property - def _body(self): - # Byte1, Power, prompt_tone - power = 0x01 if self.power else 0 - prompt_tone = 0x40 if self.prompt_tone else 0 - # Byte2, mode target_temperature - mode = (self.mode << 5) & 0xE0 - target_temperature = (int(self.target_temperature) & 0xF) | ( - 0x10 if int(round(self.target_temperature * 2)) % 2 != 0 else 0 - ) - # Byte 3, fan_speed - fan_speed = self.fan_speed & 0x7F - # Byte 7, swing_mode - swing_mode = ( - 0x30 - | (0x0C if self.swing_vertical else 0) - | (0x03 if self.swing_horizontal else 0) - ) - # Byte 8, turbo - boost_mode = 0x20 if self.boost_mode else 0 - # Byte 9 aux_heating eco_mode - smart_eye = 0x01 if self.smart_eye else 0 - dry = 0x04 if self.dry else 0 - aux_heating = 0x08 if self.aux_heating else 0 - eco_mode = 0x80 if self.eco_mode else 0 - # Byte 10 temp_fahrenheit - temp_fahrenheit = 0x04 if self.temp_fahrenheit else 0 - sleep_mode = 0x01 if self.sleep_mode else 0 - boost_mode_1 = 0x02 if self.boost_mode else 0 - # Byte 17 natural_wind - natural_wind = 0x40 if self.natural_wind else 0 - # Byte 21 frost_protect - frost_protect = 0x80 if self.frost_protect else 0 - # Byte 22 comfort_mode - comfort_mode = 0x01 if self.comfort_mode else 0 - - return bytearray( - [ - power | prompt_tone, - mode | target_temperature, - fan_speed, - 0x00, - 0x00, - 0x00, - swing_mode, - boost_mode, - smart_eye | dry | aux_heating | eco_mode, - temp_fahrenheit | sleep_mode | boost_mode_1, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - natural_wind, - 0x00, - 0x00, - 0x00, - frost_protect, - comfort_mode, - ] - ) - - -class MessageNewProtocolSet(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0xB0, - ) - self.indirect_wind = None - self.prompt_tone = None - self.breezeless = None - self.screen_display_alternate = None - self.fresh_air_1 = None - self.fresh_air_2 = None - - @property - def _body(self): - pack_count = 0 - payload = bytearray([0x00]) - if self.breezeless is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.breezeless, - value=bytearray([0x01 if self.breezeless else 0x00]), - ) - ) - if self.indirect_wind is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.indirect_wind, - value=bytearray([0x02 if self.indirect_wind else 0x01]), - ) - ) - if self.prompt_tone is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.prompt_tone, - value=bytearray([0x01 if self.prompt_tone else 0x00]), - ) - ) - if self.screen_display_alternate is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.screen_display, - value=bytearray([0x64 if self.screen_display_alternate else 0x00]), - ) - ) - if self.fresh_air_1 is not None and len(self.fresh_air_1) == 2: - pack_count += 1 - fresh_air_power = 2 if self.fresh_air_1[0] > 0 else 1 - fresh_air_fan_speed = self.fresh_air_1[1] - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.fresh_air_1, - value=bytearray( - [ - fresh_air_power, - fresh_air_fan_speed, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ), - ) - ) - if self.fresh_air_2 is not None and len(self.fresh_air_2) == 2: - pack_count += 1 - fresh_air_power = 1 if self.fresh_air_2[0] > 0 else 0 - fresh_air_fan_speed = self.fresh_air_2[1] - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.fresh_air_2, - value=bytearray([fresh_air_power, fresh_air_fan_speed, 0xFF]), - ) - ) - payload[0] = pack_count - return payload - - -class XA0MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x1) > 0 - self.target_temperature = ( - ((body[1] & 0x3E) >> 1) - 4 + 16.0 + (0.5 if body[1] & 0x40 > 0 else 0.0) - ) - self.mode = (body[2] & 0xE0) >> 5 - self.fan_speed = body[3] & 0x7F - self.swing_vertical = (body[7] & 0xC) > 0 - self.swing_horizontal = (body[7] & 0x3) > 0 - self.boost_mode = ((body[8] & 0x20) > 0) or ((body[10] & 0x2) > 0) - self.smart_eye = (body[9] & 0x01) > 0 - self.dry = (body[9] & 0x04) > 0 - self.aux_heating = (body[9] & 0x08) > 0 - self.eco_mode = (body[9] & 0x10) > 0 - self.sleep_mode = (body[10] & 0x01) > 0 - self.natural_wind = (body[10] & 0x40) > 0 - self.full_dust = (body[13] & 0x20) > 0 - self.comfort_mode = (body[14] & 0x1) > 0 if len(body) > 16 else False - - -class XA1MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[13] != 0xFF: - temp_integer = int((body[13] - 50) / 2) - temp_decimal = ((body[18] & 0xF) * 0.1) if len(body) > 20 else 0 - if body[13] > 49: - self.indoor_temperature = temp_integer + temp_decimal - else: - self.indoor_temperature = temp_integer - temp_decimal - if body[14] == 0xFF: - self.outdoor_temperature = None - else: - temp_integer = int((body[14] - 50) / 2) - temp_decimal = (((body[18] & 0xF0) >> 4) * 0.1) if len(body) > 20 else 0 - if body[14] > 49: - self.outdoor_temperature = temp_integer + temp_decimal - else: - self.outdoor_temperature = temp_integer - temp_decimal - self.indoor_humidity = body[17] - - -class XBXMessageBody(NewProtocolMessageBody): - def __init__(self, body, bt): - super().__init__(body, bt) - params = self.parse() - if NewProtocolTags.indirect_wind in params: - self.indirect_wind = params[NewProtocolTags.indirect_wind][0] == 0x02 - if NewProtocolTags.indoor_humidity in params: - self.indoor_humidity = params[NewProtocolTags.indoor_humidity][0] - if NewProtocolTags.breezeless in params: - self.breezeless = params[NewProtocolTags.breezeless][0] == 1 - if NewProtocolTags.screen_display in params: - self.screen_display_alternate = ( - params[NewProtocolTags.screen_display][0] > 0 - ) - self.screen_display_new = True - if NewProtocolTags.fresh_air_1 in params: - self.fresh_air_1 = True - data = params[NewProtocolTags.fresh_air_1] - self.fresh_air_power = data[0] == 0x02 - self.fresh_air_fan_speed = data[1] - if NewProtocolTags.fresh_air_2 in params: - self.fresh_air_2 = True - data = params[NewProtocolTags.fresh_air_2] - self.fresh_air_power = data[0] > 0 - self.fresh_air_fan_speed = data[1] - - -class XC0MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x1) > 0 - self.mode = (body[2] & 0xE0) >> 5 - self.target_temperature = ( - (body[2] & 0x0F) + 16.0 + (0.5 if body[0x02] & 0x10 > 0 else 0.0) - ) - self.fan_speed = body[3] & 0x7F - self.swing_vertical = (body[7] & 0x0C) > 0 - self.swing_horizontal = (body[7] & 0x03) > 0 - self.boost_mode = ((body[8] & 0x20) > 0) or ((body[10] & 0x2) > 0) - self.smart_eye = (body[8] & 0x40) > 0 - self.natural_wind = (body[9] & 0x2) > 0 - self.dry = (body[9] & 0x4) > 0 - self.eco_mode = (body[9] & 0x10) > 0 - self.aux_heating = (body[9] & 0x08) > 0 - self.temp_fahrenheit = (body[10] & 0x04) > 0 - self.sleep_mode = (body[10] & 0x01) > 0 - if body[11] != 0xFF: - temp_integer = int((body[11] - 50) / 2) - temp_decimal = (body[15] & 0x0F) * 0.1 - if body[11] > 49: - self.indoor_temperature = temp_integer + temp_decimal - else: - self.indoor_temperature = temp_integer - temp_decimal - if body[12] == 0xFF: - self.outdoor_temperature = None - else: - temp_integer = int((body[12] - 50) / 2) - temp_decimal = ((body[15] & 0xF0) >> 4) * 0.1 - if body[12] > 49: - self.outdoor_temperature = temp_integer + temp_decimal - else: - self.outdoor_temperature = temp_integer - temp_decimal - self.full_dust = (body[13] & 0x20) > 0 - self.screen_display = ((body[14] >> 4 & 0x7) != 0x07) and self.power - self.frost_protect = (body[21] & 0x80) > 0 if len(body) >= 22 else False - self.comfort_mode = (body[22] & 0x1) > 0 if len(body) >= 23 else False - - -class XC1MessageBody(MessageBody): - def __init__(self, body, analysis_method=3): - super().__init__(body) - if body[3] == 0x44: - self.total_energy_consumption = XC1MessageBody.parse_consumption( - analysis_method, body[4], body[5], body[6], body[7] - ) - self.current_energy_consumption = XC1MessageBody.parse_consumption( - analysis_method, body[12], body[13], body[14], body[15] - ) - self.realtime_power = XC1MessageBody.parse_power( - analysis_method, body[16], body[17], body[18] - ) - elif body[3] == 0x40: - pass - - @staticmethod - def parse_value(byte): - return (byte >> 4) * 10 + (byte & 0x0F) - - @staticmethod - def parse_power(analysis_method, byte1, byte2, byte3): - if analysis_method == 1: - return ( - float( - XC1MessageBody.parse_value(byte1) * 10000 - + XC1MessageBody.parse_value(byte2) * 100 - + XC1MessageBody.parse_value(byte3) - ) - / 10 - ) - elif analysis_method == 2: - return float((byte1 << 16) + (byte2 << 8) + byte3) / 10 - else: - return float(byte1 * 10000 + byte2 * 100 + byte3) / 10 - - @staticmethod - def parse_consumption(analysis_method, byte1, byte2, byte3, byte4): - if analysis_method == 1: - return ( - float( - XC1MessageBody.parse_value(byte1) * 1000000 - + XC1MessageBody.parse_value(byte2) * 10000 - + XC1MessageBody.parse_value(byte3) * 100 - + XC1MessageBody.parse_value(byte4) - ) - / 100 - ) - elif analysis_method == 2: - return float((byte1 << 32) + (byte2 << 16) + (byte3 << 8) + byte4) / 10 - else: - return float(byte1 * 1000000 + byte2 * 10000 + byte3 * 100 + byte4) / 100 - - -class XBBMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - subprotocol_head = body[:6] - subprotocol_body = body[6:] - data_type = subprotocol_head[-1] - subprotocol_body_len = len(subprotocol_body) - if data_type == 0x20 or data_type == 0x11: - self.power = (subprotocol_body[0] & 0x1) > 0 - self.dry = (subprotocol_body[0] & 0x10) > 0 - self.boost_mode = (subprotocol_body[0] & 0x20) > 0 - self.aux_heating = (subprotocol_body[1] & 0x40) > 0 - self.sleep_mode = (subprotocol_body[2] & 0x80) > 0 - try: - self.mode = BB_AC_MODES.index(subprotocol_body[5] + 1) - except ValueError: - self.mode = 0 - self.target_temperature = (subprotocol_body[6] - 30) / 2 - self.fan_speed = subprotocol_body[7] - self.timer = ( - (subprotocol_body[25] & 0x04) > 0 - if subprotocol_body_len > 27 - else False - ) - self.eco_mode = ( - (subprotocol_body[25] & 0x40) > 0 - if subprotocol_body_len > 27 - else False - ) - elif data_type == 0x10: - if subprotocol_body[8] & 0x80 == 0x80: - self.indoor_temperature = ( - 0 - (~(subprotocol_body[7] + subprotocol_body[8] * 256) + 1) - & 0xFFFF - ) / 100 - else: - self.indoor_temperature = ( - subprotocol_body[7] + subprotocol_body[8] * 256 - ) / 100 - self.indoor_humidity = subprotocol_body[30] - self.sn8_flag = subprotocol_body[80] == 0x31 - elif data_type == 0x12: - pass - elif data_type == 0x30: - if subprotocol_body[6] & 0x80 == 0x80: - self.outdoor_temperature = ( - 0 - (~(subprotocol_body[5] + subprotocol_body[6] * 256) + 1) - & 0xFFFF - ) / 100 - else: - self.outdoor_temperature = ( - subprotocol_body[5] + subprotocol_body[6] * 256 - ) / 100 - elif data_type == 0x13 or data_type == 0x21: - pass - - -class MessageACResponse(MessageResponse): - def __init__(self, message, power_analysis_method=3): - super().__init__(message) - if self.message_type == MessageType.notify2 and self.body_type == 0xA0: - self.set_body(XA0MessageBody(super().body)) - elif self.message_type == MessageType.notify1 and self.body_type == 0xA1: - self.set_body(XA1MessageBody(super().body)) - elif self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify2, - ] and self.body_type in [0xB0, 0xB1, 0xB5]: - self.set_body(XBXMessageBody(super().body, self.body_type)) - elif ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0xC0 - ): - self.set_body(XC0MessageBody(super().body)) - elif self.message_type == MessageType.query and self.body_type == 0xC1: - self.set_body(XC1MessageBody(super().body, power_analysis_method)) - elif ( - self.message_type - in [MessageType.set, MessageType.query, MessageType.notify2] - and self.body_type == 0xBB - and len(super().body) >= 21 - ): - self.used_subprotocol = True - self.set_body(XBBMessageBody(super().body)) - self.set_attr() +from enum import IntEnum + +from ...core.crc8 import calculate +from ...core.message import ( + MessageBody, + MessageRequest, + MessageResponse, + MessageType, + NewProtocolMessageBody, +) + +BB_AC_MODES = [0, 3, 1, 2, 4, 5] + + +class NewProtocolTags(IntEnum): + indoor_humidity = 0x0015 + screen_display = 0x0017 + breezeless = 0x0018 + prompt_tone = 0x001A + indirect_wind = 0x0042 + fresh_air_1 = 0x0233 + fresh_air_2 = 0x004B + + +class MessageACBase(MessageRequest): + _message_serial = 0 + + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xAC, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + MessageACBase._message_serial += 1 + if MessageACBase._message_serial >= 254: + MessageACBase._message_serial = 1 + self._message_id = MessageACBase._message_serial + + @property + def _body(self): + raise NotImplementedError + + @property + def body(self): + body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) + body.append(calculate(body)) + return body + + +class MessageQuery(MessageACBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x41, + ) + + @property + def _body(self): + return bytearray( + [ + 0x81, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class MessagePowerQuery(MessageACBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x41, + ) + + @property + def _body(self): + return bytearray([0x21, 0x01, 0x44, 0x00, 0x01]) + + @property + def body(self): + body = bytearray([self.body_type]) + self._body + body.append(calculate(body)) + return body + + +class MessageToggleDisplay(MessageACBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x41, + ) + self.prompt_tone = False + + @property + def _body(self): + prompt_tone = 0x40 if self.prompt_tone else 0 + return bytearray( + [ + 0x02 | prompt_tone, + 0x00, + 0xFF, + 0x02, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class MessageNewProtocolQuery(MessageACBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0xB1, + ) + + @property + def _body(self): + query_params = [ + NewProtocolTags.indirect_wind, + NewProtocolTags.breezeless, + NewProtocolTags.indoor_humidity, + NewProtocolTags.screen_display, + NewProtocolTags.fresh_air_1, + NewProtocolTags.fresh_air_2, + ] + + _body = bytearray([len(query_params)]) + for param in query_params: + _body.extend([param & 0xFF, param >> 8]) + return _body + + +class MessageSubProtocol(MessageACBase): + def __init__(self, protocol_version, message_type, subprotocol_query_type): + super().__init__( + protocol_version=protocol_version, message_type=message_type, body_type=0xAA + ) + self._subprotocol_query_type = subprotocol_query_type + + @property + def _subprotocol_body(self): + return bytes([]) + + @property + def body(self): + body = bytearray([self.body_type]) + self._body + body.append(calculate(body)) + body.append(self.checksum(body)) + return body + + @property + def _body(self): + _subprotocol_body = self._subprotocol_body + _body = bytearray( + [ + 6 + + 2 + + (len(_subprotocol_body) if _subprotocol_body is not None else 0), + 0x00, + 0xFF, + 0xFF, + self._subprotocol_query_type, + ] + ) + if _subprotocol_body is not None: + _body.extend(_subprotocol_body) + return _body + + +class MessageSubProtocolQuery(MessageSubProtocol): + def __init__(self, protocol_version, subprotocol_query_type): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + subprotocol_query_type=subprotocol_query_type, + ) + + +class MessageSubProtocolSet(MessageSubProtocol): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + subprotocol_query_type=0x20, + ) + self.power = False + self.mode = 0 + self.target_temperature = 20.0 + self.fan_speed = 102 + self.boost_mode = False + self.aux_heating = False + self.dry = False + self.eco_mode = False + self.sleep_mode = False + self.sn8_flag = False + self.timer = False + self.prompt_tone = False + + @property + def _subprotocol_body(self): + power = 0x01 if self.power else 0 + dry = 0x10 if self.power and self.dry else 0 + boost_mode = 0x20 if self.boost_mode else 0 + aux_heating = 0x40 if self.aux_heating else 0x80 + sleep_mode = 0x80 if self.sleep_mode else 0 + try: + mode = 0 if self.mode == 0 else BB_AC_MODES[self.mode] - 1 + except IndexError: + mode = 2 # set Auto if invalid mode + target_temperature = int(self.target_temperature * 2 + 30) + water_model_temperature_set = int((self.target_temperature - 1) * 2 + 50) + fan_speed = self.fan_speed + eco = 0x40 if self.eco_mode else 0 + + prompt_tone = 0x01 if self.prompt_tone else 0 + timer = 0x04 if (self.sn8_flag and self.timer) else 0 + return bytearray( + [ + 0x02 | boost_mode | power | dry, + aux_heating, + sleep_mode, + 0x00, + 0x00, + mode, + target_temperature, + fan_speed, + 0x32, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x01, + 0x00, + 0x01, + water_model_temperature_set, + prompt_tone, + target_temperature, + 0x32, + 0x66, + 0x00, + eco | timer, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + ] + ) + + +class MessageGeneralSet(MessageACBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x40, + ) + self.power = False + self.prompt_tone = True + self.mode = 0 + self.target_temperature = 20.0 + self.fan_speed = 102 + self.swing_vertical = False + self.swing_horizontal = False + self.boost_mode = False + self.smart_eye = False + self.dry = False + self.aux_heating = False + self.eco_mode = False + self.temp_fahrenheit = False + self.sleep_mode = False + self.natural_wind = False + self.frost_protect = False + self.comfort_mode = False + + @property + def _body(self): + # Byte1, Power, prompt_tone + power = 0x01 if self.power else 0 + prompt_tone = 0x40 if self.prompt_tone else 0 + # Byte2, mode target_temperature + mode = (self.mode << 5) & 0xE0 + target_temperature = (int(self.target_temperature) & 0xF) | ( + 0x10 if int(round(self.target_temperature * 2)) % 2 != 0 else 0 + ) + # Byte 3, fan_speed + fan_speed = self.fan_speed & 0x7F + # Byte 7, swing_mode + swing_mode = ( + 0x30 + | (0x0C if self.swing_vertical else 0) + | (0x03 if self.swing_horizontal else 0) + ) + # Byte 8, turbo + boost_mode = 0x20 if self.boost_mode else 0 + # Byte 9 aux_heating eco_mode + smart_eye = 0x01 if self.smart_eye else 0 + dry = 0x04 if self.dry else 0 + aux_heating = 0x08 if self.aux_heating else 0 + eco_mode = 0x80 if self.eco_mode else 0 + # Byte 10 temp_fahrenheit + temp_fahrenheit = 0x04 if self.temp_fahrenheit else 0 + sleep_mode = 0x01 if self.sleep_mode else 0 + boost_mode_1 = 0x02 if self.boost_mode else 0 + # Byte 17 natural_wind + natural_wind = 0x40 if self.natural_wind else 0 + # Byte 21 frost_protect + frost_protect = 0x80 if self.frost_protect else 0 + # Byte 22 comfort_mode + comfort_mode = 0x01 if self.comfort_mode else 0 + + return bytearray( + [ + power | prompt_tone, + mode | target_temperature, + fan_speed, + 0x00, + 0x00, + 0x00, + swing_mode, + boost_mode, + smart_eye | dry | aux_heating | eco_mode, + temp_fahrenheit | sleep_mode | boost_mode_1, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + natural_wind, + 0x00, + 0x00, + 0x00, + frost_protect, + comfort_mode, + ] + ) + + +class MessageNewProtocolSet(MessageACBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0xB0, + ) + self.indirect_wind = None + self.prompt_tone = None + self.breezeless = None + self.screen_display_alternate = None + self.fresh_air_1 = None + self.fresh_air_2 = None + + @property + def _body(self): + pack_count = 0 + payload = bytearray([0x00]) + if self.breezeless is not None: + pack_count += 1 + payload.extend( + NewProtocolMessageBody.pack( + param=NewProtocolTags.breezeless, + value=bytearray([0x01 if self.breezeless else 0x00]), + ) + ) + if self.indirect_wind is not None: + pack_count += 1 + payload.extend( + NewProtocolMessageBody.pack( + param=NewProtocolTags.indirect_wind, + value=bytearray([0x02 if self.indirect_wind else 0x01]), + ) + ) + if self.prompt_tone is not None: + pack_count += 1 + payload.extend( + NewProtocolMessageBody.pack( + param=NewProtocolTags.prompt_tone, + value=bytearray([0x01 if self.prompt_tone else 0x00]), + ) + ) + if self.screen_display_alternate is not None: + pack_count += 1 + payload.extend( + NewProtocolMessageBody.pack( + param=NewProtocolTags.screen_display, + value=bytearray([0x64 if self.screen_display_alternate else 0x00]), + ) + ) + if self.fresh_air_1 is not None and len(self.fresh_air_1) == 2: + pack_count += 1 + fresh_air_power = 2 if self.fresh_air_1[0] > 0 else 1 + fresh_air_fan_speed = self.fresh_air_1[1] + payload.extend( + NewProtocolMessageBody.pack( + param=NewProtocolTags.fresh_air_1, + value=bytearray( + [ + fresh_air_power, + fresh_air_fan_speed, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ), + ) + ) + if self.fresh_air_2 is not None and len(self.fresh_air_2) == 2: + pack_count += 1 + fresh_air_power = 1 if self.fresh_air_2[0] > 0 else 0 + fresh_air_fan_speed = self.fresh_air_2[1] + payload.extend( + NewProtocolMessageBody.pack( + param=NewProtocolTags.fresh_air_2, + value=bytearray([fresh_air_power, fresh_air_fan_speed, 0xFF]), + ) + ) + payload[0] = pack_count + return payload + + +class XA0MessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[1] & 0x1) > 0 + self.target_temperature = ( + ((body[1] & 0x3E) >> 1) - 4 + 16.0 + (0.5 if body[1] & 0x40 > 0 else 0.0) + ) + self.mode = (body[2] & 0xE0) >> 5 + self.fan_speed = body[3] & 0x7F + self.swing_vertical = (body[7] & 0xC) > 0 + self.swing_horizontal = (body[7] & 0x3) > 0 + self.boost_mode = ((body[8] & 0x20) > 0) or ((body[10] & 0x2) > 0) + self.smart_eye = (body[9] & 0x01) > 0 + self.dry = (body[9] & 0x04) > 0 + self.aux_heating = (body[9] & 0x08) > 0 + self.eco_mode = (body[9] & 0x10) > 0 + self.sleep_mode = (body[10] & 0x01) > 0 + self.natural_wind = (body[10] & 0x40) > 0 + self.full_dust = (body[13] & 0x20) > 0 + self.comfort_mode = (body[14] & 0x1) > 0 if len(body) > 16 else False + + +class XA1MessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + if body[13] != 0xFF: + temp_integer = int((body[13] - 50) / 2) + temp_decimal = ((body[18] & 0xF) * 0.1) if len(body) > 20 else 0 + if body[13] > 49: + self.indoor_temperature = temp_integer + temp_decimal + else: + self.indoor_temperature = temp_integer - temp_decimal + if body[14] == 0xFF: + self.outdoor_temperature = None + else: + temp_integer = int((body[14] - 50) / 2) + temp_decimal = (((body[18] & 0xF0) >> 4) * 0.1) if len(body) > 20 else 0 + if body[14] > 49: + self.outdoor_temperature = temp_integer + temp_decimal + else: + self.outdoor_temperature = temp_integer - temp_decimal + self.indoor_humidity = body[17] + + +class XBXMessageBody(NewProtocolMessageBody): + def __init__(self, body, bt): + super().__init__(body, bt) + params = self.parse() + if NewProtocolTags.indirect_wind in params: + self.indirect_wind = params[NewProtocolTags.indirect_wind][0] == 0x02 + if NewProtocolTags.indoor_humidity in params: + self.indoor_humidity = params[NewProtocolTags.indoor_humidity][0] + if NewProtocolTags.breezeless in params: + self.breezeless = params[NewProtocolTags.breezeless][0] == 1 + if NewProtocolTags.screen_display in params: + self.screen_display_alternate = ( + params[NewProtocolTags.screen_display][0] > 0 + ) + self.screen_display_new = True + if NewProtocolTags.fresh_air_1 in params: + self.fresh_air_1 = True + data = params[NewProtocolTags.fresh_air_1] + self.fresh_air_power = data[0] == 0x02 + self.fresh_air_fan_speed = data[1] + if NewProtocolTags.fresh_air_2 in params: + self.fresh_air_2 = True + data = params[NewProtocolTags.fresh_air_2] + self.fresh_air_power = data[0] > 0 + self.fresh_air_fan_speed = data[1] + + +class XC0MessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[1] & 0x1) > 0 + self.mode = (body[2] & 0xE0) >> 5 + self.target_temperature = ( + (body[2] & 0x0F) + 16.0 + (0.5 if body[0x02] & 0x10 > 0 else 0.0) + ) + self.fan_speed = body[3] & 0x7F + self.swing_vertical = (body[7] & 0x0C) > 0 + self.swing_horizontal = (body[7] & 0x03) > 0 + self.boost_mode = ((body[8] & 0x20) > 0) or ((body[10] & 0x2) > 0) + self.smart_eye = (body[8] & 0x40) > 0 + self.natural_wind = (body[9] & 0x2) > 0 + self.dry = (body[9] & 0x4) > 0 + self.eco_mode = (body[9] & 0x10) > 0 + self.aux_heating = (body[9] & 0x08) > 0 + self.temp_fahrenheit = (body[10] & 0x04) > 0 + self.sleep_mode = (body[10] & 0x01) > 0 + if body[11] != 0xFF: + temp_integer = int((body[11] - 50) / 2) + temp_decimal = (body[15] & 0x0F) * 0.1 + if body[11] > 49: + self.indoor_temperature = temp_integer + temp_decimal + else: + self.indoor_temperature = temp_integer - temp_decimal + if body[12] == 0xFF: + self.outdoor_temperature = None + else: + temp_integer = int((body[12] - 50) / 2) + temp_decimal = ((body[15] & 0xF0) >> 4) * 0.1 + if body[12] > 49: + self.outdoor_temperature = temp_integer + temp_decimal + else: + self.outdoor_temperature = temp_integer - temp_decimal + self.full_dust = (body[13] & 0x20) > 0 + self.screen_display = ((body[14] >> 4 & 0x7) != 0x07) and self.power + self.frost_protect = (body[21] & 0x80) > 0 if len(body) >= 22 else False + self.comfort_mode = (body[22] & 0x1) > 0 if len(body) >= 23 else False + + +class XC1MessageBody(MessageBody): + def __init__(self, body, analysis_method=3): + super().__init__(body) + if body[3] == 0x44: + self.total_energy_consumption = XC1MessageBody.parse_consumption( + analysis_method, body[4], body[5], body[6], body[7] + ) + self.current_energy_consumption = XC1MessageBody.parse_consumption( + analysis_method, body[12], body[13], body[14], body[15] + ) + self.realtime_power = XC1MessageBody.parse_power( + analysis_method, body[16], body[17], body[18] + ) + elif body[3] == 0x40: + pass + + @staticmethod + def parse_value(byte): + return (byte >> 4) * 10 + (byte & 0x0F) + + @staticmethod + def parse_power(analysis_method, byte1, byte2, byte3): + if analysis_method == 1: + return ( + float( + XC1MessageBody.parse_value(byte1) * 10000 + + XC1MessageBody.parse_value(byte2) * 100 + + XC1MessageBody.parse_value(byte3) + ) + / 10 + ) + elif analysis_method == 2: + return float((byte1 << 16) + (byte2 << 8) + byte3) / 10 + else: + return float(byte1 * 10000 + byte2 * 100 + byte3) / 10 + + @staticmethod + def parse_consumption(analysis_method, byte1, byte2, byte3, byte4): + if analysis_method == 1: + return ( + float( + XC1MessageBody.parse_value(byte1) * 1000000 + + XC1MessageBody.parse_value(byte2) * 10000 + + XC1MessageBody.parse_value(byte3) * 100 + + XC1MessageBody.parse_value(byte4) + ) + / 100 + ) + elif analysis_method == 2: + return float((byte1 << 32) + (byte2 << 16) + (byte3 << 8) + byte4) / 10 + else: + return float(byte1 * 1000000 + byte2 * 10000 + byte3 * 100 + byte4) / 100 + + +class XBBMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + subprotocol_head = body[:6] + subprotocol_body = body[6:] + data_type = subprotocol_head[-1] + subprotocol_body_len = len(subprotocol_body) + if data_type == 0x20 or data_type == 0x11: + self.power = (subprotocol_body[0] & 0x1) > 0 + self.dry = (subprotocol_body[0] & 0x10) > 0 + self.boost_mode = (subprotocol_body[0] & 0x20) > 0 + self.aux_heating = (subprotocol_body[1] & 0x40) > 0 + self.sleep_mode = (subprotocol_body[2] & 0x80) > 0 + try: + self.mode = BB_AC_MODES.index(subprotocol_body[5] + 1) + except ValueError: + self.mode = 0 + self.target_temperature = (subprotocol_body[6] - 30) / 2 + self.fan_speed = subprotocol_body[7] + self.timer = ( + (subprotocol_body[25] & 0x04) > 0 + if subprotocol_body_len > 27 + else False + ) + self.eco_mode = ( + (subprotocol_body[25] & 0x40) > 0 + if subprotocol_body_len > 27 + else False + ) + elif data_type == 0x10: + if subprotocol_body[8] & 0x80 == 0x80: + self.indoor_temperature = ( + 0 - (~(subprotocol_body[7] + subprotocol_body[8] * 256) + 1) + & 0xFFFF + ) / 100 + else: + self.indoor_temperature = ( + subprotocol_body[7] + subprotocol_body[8] * 256 + ) / 100 + self.indoor_humidity = subprotocol_body[30] + self.sn8_flag = subprotocol_body[80] == 0x31 + elif data_type == 0x12: + pass + elif data_type == 0x30: + if subprotocol_body[6] & 0x80 == 0x80: + self.outdoor_temperature = ( + 0 - (~(subprotocol_body[5] + subprotocol_body[6] * 256) + 1) + & 0xFFFF + ) / 100 + else: + self.outdoor_temperature = ( + subprotocol_body[5] + subprotocol_body[6] * 256 + ) / 100 + elif data_type == 0x13 or data_type == 0x21: + pass + + +class MessageACResponse(MessageResponse): + def __init__(self, message, power_analysis_method=3): + super().__init__(message) + if self.message_type == MessageType.notify2 and self.body_type == 0xA0: + self.set_body(XA0MessageBody(super().body)) + elif self.message_type == MessageType.notify1 and self.body_type == 0xA1: + self.set_body(XA1MessageBody(super().body)) + elif self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify2, + ] and self.body_type in [0xB0, 0xB1, 0xB5]: + self.set_body(XBXMessageBody(super().body, self.body_type)) + elif ( + self.message_type in [MessageType.query, MessageType.set] + and self.body_type == 0xC0 + ): + self.set_body(XC0MessageBody(super().body)) + elif self.message_type == MessageType.query and self.body_type == 0xC1: + self.set_body(XC1MessageBody(super().body, power_analysis_method)) + elif ( + self.message_type + in [MessageType.set, MessageType.query, MessageType.notify2] + and self.body_type == 0xBB + and len(super().body) >= 21 + ): + self.used_subprotocol = True + self.set_body(XBBMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/b6/device.py b/custom_components/midea_ac_lan/midea/devices/b6/device.py index 059b4282..00147b08 100644 --- a/custom_components/midea_ac_lan/midea/devices/b6/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b6/device.py @@ -1,170 +1,170 @@ -import json -import logging - -from .message import MessageB6Response, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - light = "light" - mode = "mode" - fan_level = "fan_level" - fan_speed = "fan_speed" - oilcup_full = "oilcup_full" - cleaning_reminder = "cleaning_reminder" - - -class MideaB6Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xB6, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.light: None, - DeviceAttributes.mode: None, - DeviceAttributes.fan_level: 0, - DeviceAttributes.fan_speed: 0, - DeviceAttributes.oilcup_full: False, - DeviceAttributes.cleaning_reminder: False, - }, - ) - self._default_speeds = {0: "Off", 1: "Level 1", 2: "Level 2"} - self._default_power_speed = 2 - self._power_speed = self._default_power_speed - self._speeds = self._default_speeds - self.set_customize(customize) - - @property - def speed_count(self): - return len(self._speeds) - 1 - - @property - def preset_modes(self): - return list(self._speeds.values()) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageB6Response(msg) - self._protocol_version = message.protocol_version - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.fan_level: - if value in self._speeds.keys(): - self._attributes[DeviceAttributes.mode] = self._speeds.get( - value - ) - self._attributes[DeviceAttributes.fan_speed] = list( - self._speeds.keys() - ).index(value) - else: - self._attributes[DeviceAttributes.mode] = None - self._attributes[DeviceAttributes.fan_speed] = 0 - new_status[DeviceAttributes.mode.value] = self._attributes[ - DeviceAttributes.mode - ] - new_status[DeviceAttributes.fan_speed.value] = self._attributes[ - DeviceAttributes.fan_speed - ] - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - message = None - if attr == DeviceAttributes.fan_speed: - if value < len(self._speeds): - message = MessageSet(self._protocol_version) - message.fan_level = list(self._speeds.keys())[value] - elif attr == DeviceAttributes.mode: - if value in self._speeds.values(): - message = MessageSet(self._protocol_version) - message.fan_level = list(self._speeds.keys())[ - list(self._speeds.values()).index(value) - ] - elif not value: - message = MessageSet(self._protocol_version) - message.power = False - elif attr == DeviceAttributes.power: - message = MessageSet(self._protocol_version) - message.power = value - message.fan_level = self._power_speed - elif attr == DeviceAttributes.light: - message = MessageSet(self._protocol_version) - message.light = value - if message is not None: - self.build_send(message) - - def turn_on(self, fan_speed=None, mode=None): - message = MessageSet(self._protocol_version) - message.power = True - if fan_speed is not None and fan_speed < len(self._speeds): - message.fan_level = list(self._speeds.keys())[fan_speed] - else: - message.fan_level = self._power_speed - if mode is not None in self._speeds.values(): - message.fan_level = list(self._speeds.keys())[ - list(self._speeds.values()).index(mode) - ] - self.build_send(message) - - def set_customize(self, customize): - self._speeds = self._default_speeds - self._power_speed = self._default_power_speed - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params: - if "default_speed" in params: - self._power_speed = int(params.get("default_speed")) - if "speeds" in params: - self._speeds = {} - speeds = {} - for k, v in params.get("speeds").items(): - speeds[int(k)] = v - keys = sorted(speeds.keys()) - for k in keys: - self._speeds[k] = speeds[k] - self.update_all( - {"speeds": self._speeds, "default_speed": self._power_speed} - ) - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - - -class MideaAppliance(MideaB6Device): - pass +import json +import logging + +from .message import MessageB6Response, MessageQuery, MessageSet + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + light = "light" + mode = "mode" + fan_level = "fan_level" + fan_speed = "fan_speed" + oilcup_full = "oilcup_full" + cleaning_reminder = "cleaning_reminder" + + +class MideaB6Device(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xB6, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.light: None, + DeviceAttributes.mode: None, + DeviceAttributes.fan_level: 0, + DeviceAttributes.fan_speed: 0, + DeviceAttributes.oilcup_full: False, + DeviceAttributes.cleaning_reminder: False, + }, + ) + self._default_speeds = {0: "Off", 1: "Level 1", 2: "Level 2"} + self._default_power_speed = 2 + self._power_speed = self._default_power_speed + self._speeds = self._default_speeds + self.set_customize(customize) + + @property + def speed_count(self): + return len(self._speeds) - 1 + + @property + def preset_modes(self): + return list(self._speeds.values()) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageB6Response(msg) + self._protocol_version = message.protocol_version + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.fan_level: + if value in self._speeds.keys(): + self._attributes[DeviceAttributes.mode] = self._speeds.get( + value + ) + self._attributes[DeviceAttributes.fan_speed] = list( + self._speeds.keys() + ).index(value) + else: + self._attributes[DeviceAttributes.mode] = None + self._attributes[DeviceAttributes.fan_speed] = 0 + new_status[DeviceAttributes.mode.value] = self._attributes[ + DeviceAttributes.mode + ] + new_status[DeviceAttributes.fan_speed.value] = self._attributes[ + DeviceAttributes.fan_speed + ] + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = self._attributes[status] + return new_status + + def set_attribute(self, attr, value): + message = None + if attr == DeviceAttributes.fan_speed: + if value < len(self._speeds): + message = MessageSet(self._protocol_version) + message.fan_level = list(self._speeds.keys())[value] + elif attr == DeviceAttributes.mode: + if value in self._speeds.values(): + message = MessageSet(self._protocol_version) + message.fan_level = list(self._speeds.keys())[ + list(self._speeds.values()).index(value) + ] + elif not value: + message = MessageSet(self._protocol_version) + message.power = False + elif attr == DeviceAttributes.power: + message = MessageSet(self._protocol_version) + message.power = value + message.fan_level = self._power_speed + elif attr == DeviceAttributes.light: + message = MessageSet(self._protocol_version) + message.light = value + if message is not None: + self.build_send(message) + + def turn_on(self, fan_speed=None, mode=None): + message = MessageSet(self._protocol_version) + message.power = True + if fan_speed is not None and fan_speed < len(self._speeds): + message.fan_level = list(self._speeds.keys())[fan_speed] + else: + message.fan_level = self._power_speed + if mode is not None in self._speeds.values(): + message.fan_level = list(self._speeds.keys())[ + list(self._speeds.values()).index(mode) + ] + self.build_send(message) + + def set_customize(self, customize): + self._speeds = self._default_speeds + self._power_speed = self._default_power_speed + if customize and len(customize) > 0: + try: + params = json.loads(customize) + if params: + if "default_speed" in params: + self._power_speed = int(params.get("default_speed")) + if "speeds" in params: + self._speeds = {} + speeds = {} + for k, v in params.get("speeds").items(): + speeds[int(k)] = v + keys = sorted(speeds.keys()) + for k in keys: + self._speeds[k] = speeds[k] + self.update_all( + {"speeds": self._speeds, "default_speed": self._power_speed} + ) + except Exception as e: + _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") + + +class MideaAppliance(MideaB6Device): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/b6/message.py b/custom_components/midea_ac_lan/midea/devices/b6/message.py index a780dad4..53a1b78e 100644 --- a/custom_components/midea_ac_lan/midea/devices/b6/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b6/message.py @@ -1,219 +1,219 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageB6Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xB6, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageB6Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x11 if protocol_version == 2 else 0x31, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageQueryTips(MessageB6Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x02, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessageSet(MessageB6Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x22 if protocol_version in [0x00, 0x01] else 0x11, - ) - self.light = None - self.power = None - self.fan_level = None - - @property - def _body(self): - if self.protocol_version in [0x00, 0x01]: - light = 0xFF - value2 = 0xFF - value3 = 0xFF - if self.light is not None: - if self.light: - light = 0x1A - else: - light = 0 - elif self.power is not None: - if self.power: - value2 = 0x02 - if self.fan_level is not None: - value3 = self.fan_level - else: - value3 = 0x01 - else: - value2 = 0x03 - elif self.fan_level is not None: - if self.fan_level == 0: - value2 = 0x03 - else: - value2 = 0x02 - value3 = self.fan_level - return bytearray( - [0x01, light, value2, value3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] - ) - else: - value13 = 0xFF - value14 = 0xFF - value15 = 0xFF - value16 = 0xFF - if self.power is not None: - value13 = 0x01 - if self.power: - value15 = 0x02 - if self.fan_level is not None: - value16 = self.fan_level - else: - value16 = 0x01 - else: - value15 = 0x01 - elif self.fan_level is not None: - value13 = 0x01 - if self.fan_level == 0: - value15 = 0x01 - else: - value15 = 0x02 - value16 = self.fan_level - elif self.light is not None: - value13 = 0x02 - value14 = 0x02 - value15 = 0x01 if self.light else 0x00 - return bytearray([0x01, value13, value14, value15, value16, 0xFF, 0xFF]) - - -class B6FeedbackBody(MessageBody): - def __init__(self, body): - super().__init__(body) - - -class B6GeneralBody(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[1] != 0xFF: - self.light = body[1] > 0x00 - self.power = False - fan_level = None - if body[2] != 0xFF: - self.power = body[2] in [0x02, 0x06, 0x07, 0x14, 0x15, 0x16] - if body[2] in [0x14, 0x16]: - fan_level = 0x16 - if fan_level is None and body[3] != 0xFF: - fan_level = body[3] - if fan_level > 100: - if fan_level < 130: - fan_level = 1 - elif fan_level < 140: - fan_level = 2 - elif fan_level < 170: - fan_level = 3 - else: - fan_level = 4 - else: - self.fan_level = fan_level - self.fan_level = 0 if fan_level is None else fan_level - self.oilcup_full = (body[5] & 0x01) > 0 - self.cleaning_reminder = (body[5] & 0x02) > 0 - - -class B6NewProtocolBody(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[1] == 0x01: - pack_bytes = body[3 : 3 + body[2]] - if pack_bytes[1] != 0xFF: - self.power = True - self.power = pack_bytes[1] not in [0x00, 0x01, 0x05, 0x07] - if pack_bytes[2] != 0xFF: - self.fan_level = pack_bytes[2] - if pack_bytes[6] != 0xFF: - self.light = pack_bytes[6] > 0 - self.oilcup_full = (pack_bytes[18] & 0x02) > 0 - self.cleaning_reminder = (pack_bytes[18] & 0x04) > 0 - - -class B6SpecialBody(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[2] != 0xFF: - self.light = body[2] > 0x00 - self.power = False - if body[3] != 0xFF: - self.power = body[3] in [0x00, 0x02, 0x04] - if body[4] != 0xFF: - self.fan_level = body[4] - - -class B6ExceptionBody(MessageBody): - def __init__(self, body): - super().__init__(body) - - -class MessageB6Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type == MessageType.set - and self.body_type == 0x22 - and super().body[1] == 0x01 - ): - self.set_body(B6SpecialBody(super().body)) - elif ( - self.message_type == MessageType.set - and self.body_type == 0x11 - and super().body[1] == 0x01 - ): - ############################# - pass - elif self.message_type == MessageType.query: - if self.body_type in [0x11, 0x31]: - if self.protocol_version in [0, 1]: - self.set_body(B6GeneralBody(super().body)) - else: - self.set_body(B6NewProtocolBody(super().body)) - elif self.body_type == 0x32 and super().body[1] == 0x01: - self.set_body(B6ExceptionBody(super().body)) - elif self.message_type == MessageType.notify1: - if self.body_type in [0x11, 0x41]: - if self.protocol_version in [0, 1]: - self.set_body(B6GeneralBody(super().body)) - else: - self.set_body(B6NewProtocolBody(super().body)) - elif self.body_type == 0x0A: - if super().body[1] == 0xA1: - self.set_body(B6ExceptionBody(super().body)) - elif super().body[1] == 0xA2: - self.oilcup_full = (super().body[2] & 0x01) > 0 - self.cleaning_reminder = (super().body[2] & 0x02) > 0 - elif self.message_type == MessageType.exception2 and self.body_type == 0xA1: - pass - - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageB6Base(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xB6, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageB6Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x11 if protocol_version == 2 else 0x31, + ) + + @property + def _body(self): + return bytearray([]) + + +class MessageQueryTips(MessageB6Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x02, + ) + + @property + def _body(self): + return bytearray([0x01]) + + +class MessageSet(MessageB6Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x22 if protocol_version in [0x00, 0x01] else 0x11, + ) + self.light = None + self.power = None + self.fan_level = None + + @property + def _body(self): + if self.protocol_version in [0x00, 0x01]: + light = 0xFF + value2 = 0xFF + value3 = 0xFF + if self.light is not None: + if self.light: + light = 0x1A + else: + light = 0 + elif self.power is not None: + if self.power: + value2 = 0x02 + if self.fan_level is not None: + value3 = self.fan_level + else: + value3 = 0x01 + else: + value2 = 0x03 + elif self.fan_level is not None: + if self.fan_level == 0: + value2 = 0x03 + else: + value2 = 0x02 + value3 = self.fan_level + return bytearray( + [0x01, light, value2, value3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] + ) + else: + value13 = 0xFF + value14 = 0xFF + value15 = 0xFF + value16 = 0xFF + if self.power is not None: + value13 = 0x01 + if self.power: + value15 = 0x02 + if self.fan_level is not None: + value16 = self.fan_level + else: + value16 = 0x01 + else: + value15 = 0x01 + elif self.fan_level is not None: + value13 = 0x01 + if self.fan_level == 0: + value15 = 0x01 + else: + value15 = 0x02 + value16 = self.fan_level + elif self.light is not None: + value13 = 0x02 + value14 = 0x02 + value15 = 0x01 if self.light else 0x00 + return bytearray([0x01, value13, value14, value15, value16, 0xFF, 0xFF]) + + +class B6FeedbackBody(MessageBody): + def __init__(self, body): + super().__init__(body) + + +class B6GeneralBody(MessageBody): + def __init__(self, body): + super().__init__(body) + if body[1] != 0xFF: + self.light = body[1] > 0x00 + self.power = False + fan_level = None + if body[2] != 0xFF: + self.power = body[2] in [0x02, 0x06, 0x07, 0x14, 0x15, 0x16] + if body[2] in [0x14, 0x16]: + fan_level = 0x16 + if fan_level is None and body[3] != 0xFF: + fan_level = body[3] + if fan_level > 100: + if fan_level < 130: + fan_level = 1 + elif fan_level < 140: + fan_level = 2 + elif fan_level < 170: + fan_level = 3 + else: + fan_level = 4 + else: + self.fan_level = fan_level + self.fan_level = 0 if fan_level is None else fan_level + self.oilcup_full = (body[5] & 0x01) > 0 + self.cleaning_reminder = (body[5] & 0x02) > 0 + + +class B6NewProtocolBody(MessageBody): + def __init__(self, body): + super().__init__(body) + if body[1] == 0x01: + pack_bytes = body[3 : 3 + body[2]] + if pack_bytes[1] != 0xFF: + self.power = True + self.power = pack_bytes[1] not in [0x00, 0x01, 0x05, 0x07] + if pack_bytes[2] != 0xFF: + self.fan_level = pack_bytes[2] + if pack_bytes[6] != 0xFF: + self.light = pack_bytes[6] > 0 + self.oilcup_full = (pack_bytes[18] & 0x02) > 0 + self.cleaning_reminder = (pack_bytes[18] & 0x04) > 0 + + +class B6SpecialBody(MessageBody): + def __init__(self, body): + super().__init__(body) + if body[2] != 0xFF: + self.light = body[2] > 0x00 + self.power = False + if body[3] != 0xFF: + self.power = body[3] in [0x00, 0x02, 0x04] + if body[4] != 0xFF: + self.fan_level = body[4] + + +class B6ExceptionBody(MessageBody): + def __init__(self, body): + super().__init__(body) + + +class MessageB6Response(MessageResponse): + def __init__(self, message): + super().__init__(message) + if ( + self.message_type == MessageType.set + and self.body_type == 0x22 + and super().body[1] == 0x01 + ): + self.set_body(B6SpecialBody(super().body)) + elif ( + self.message_type == MessageType.set + and self.body_type == 0x11 + and super().body[1] == 0x01 + ): + ############################# + pass + elif self.message_type == MessageType.query: + if self.body_type in [0x11, 0x31]: + if self.protocol_version in [0, 1]: + self.set_body(B6GeneralBody(super().body)) + else: + self.set_body(B6NewProtocolBody(super().body)) + elif self.body_type == 0x32 and super().body[1] == 0x01: + self.set_body(B6ExceptionBody(super().body)) + elif self.message_type == MessageType.notify1: + if self.body_type in [0x11, 0x41]: + if self.protocol_version in [0, 1]: + self.set_body(B6GeneralBody(super().body)) + else: + self.set_body(B6NewProtocolBody(super().body)) + elif self.body_type == 0x0A: + if super().body[1] == 0xA1: + self.set_body(B6ExceptionBody(super().body)) + elif super().body[1] == 0xA2: + self.oilcup_full = (super().body[2] & 0x01) > 0 + self.cleaning_reminder = (super().body[2] & 0x02) > 0 + elif self.message_type == MessageType.exception2 and self.body_type == 0xA1: + pass + + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/c3/device.py b/custom_components/midea_ac_lan/midea/devices/c3/device.py index 9bf79b77..150532b0 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/device.py @@ -1,288 +1,288 @@ -import logging - -from .message import ( - MessageC3Response, - MessageQuery, - MessageSet, - MessageSetECO, - MessageSetSilent, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - zone1_power = "zone1_power" - zone2_power = "zone2_power" - dhw_power = "dhw_power" - zone1_curve = "zone1_curve" - zone2_curve = "zone2_curve" - disinfect = "disinfect" - fast_dhw = "fast_dhw" - zone_temp_type = "zone_temp_type" - zone1_room_temp_mode = "zone1_room_temp_mode" - zone2_room_temp_mode = "zone2_room_temp_mode" - zone1_water_temp_mode = "zone1_water_temp_mode" - zone2_water_temp_mode = "zone2_water_temp_mode" - mode = "mode" - mode_auto = "mode_auto" - zone_target_temp = "zone_target_temp" - dhw_target_temp = "dhw_target_temp" - room_target_temp = "room_target_temp" - zone_heating_temp_max = "zone_heating_temp_max" - zone_heating_temp_min = "zone_heating_temp_min" - zone_cooling_temp_max = "zone_cooling_temp_max" - zone_cooling_temp_min = "zone_cooling_temp_min" - tank_actual_temperature = "tank_actual_temperature" - room_temp_max = "room_temp_max" - room_temp_min = "room_temp_min" - dhw_temp_max = "dhw_temp_max" - dhw_temp_min = "dhw_temp_min" - target_temperature = "target_temperature" - temperature_max = "temperature_max" - temperature_min = "temperature_min" - status_heating = "status_heating" - status_dhw = "status_dhw" - status_tbh = "status_tbh" - status_ibh = "status_ibh" - total_energy_consumption = "total_energy_consumption" - total_produced_energy = "total_produced_energy" - outdoor_temperature = "outdoor_temperature" - silent_mode = "silent_mode" - eco_mode = "eco_mode" - tbh = "tbh" - error_code = "error_code" - - -class MideaC3Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xC3, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.zone1_power: False, - DeviceAttributes.zone2_power: False, - DeviceAttributes.dhw_power: False, - DeviceAttributes.zone1_curve: False, - DeviceAttributes.zone2_curve: False, - DeviceAttributes.disinfect: False, - DeviceAttributes.fast_dhw: False, - DeviceAttributes.zone_temp_type: [False, False], - DeviceAttributes.zone1_room_temp_mode: False, - DeviceAttributes.zone2_room_temp_mode: False, - DeviceAttributes.zone1_water_temp_mode: False, - DeviceAttributes.zone2_water_temp_mode: False, - DeviceAttributes.silent_mode: False, - DeviceAttributes.eco_mode: False, - DeviceAttributes.tbh: False, - DeviceAttributes.mode: 1, - DeviceAttributes.mode_auto: 1, - DeviceAttributes.zone_target_temp: [25, 25], - DeviceAttributes.dhw_target_temp: 25, - DeviceAttributes.room_target_temp: 30, - DeviceAttributes.zone_heating_temp_max: [55, 55], - DeviceAttributes.zone_heating_temp_min: [25, 25], - DeviceAttributes.zone_cooling_temp_max: [25, 25], - DeviceAttributes.zone_cooling_temp_min: [5, 5], - DeviceAttributes.room_temp_max: 60, - DeviceAttributes.room_temp_min: 34, - DeviceAttributes.dhw_temp_max: 60, - DeviceAttributes.dhw_temp_min: 20, - DeviceAttributes.tank_actual_temperature: None, - DeviceAttributes.target_temperature: [25, 25], - DeviceAttributes.temperature_max: [0, 0], - DeviceAttributes.temperature_min: [0, 0], - DeviceAttributes.total_energy_consumption: None, - DeviceAttributes.status_heating: None, - DeviceAttributes.status_dhw: None, - DeviceAttributes.status_tbh: None, - DeviceAttributes.status_ibh: None, - DeviceAttributes.total_produced_energy: None, - DeviceAttributes.outdoor_temperature: None, - DeviceAttributes.error_code: 0, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageC3Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - if "zone_temp_type" in new_status: - for zone in [0, 1]: - if self._attributes[DeviceAttributes.zone_temp_type][ - zone - ]: # Water temp mode - self._attributes[DeviceAttributes.target_temperature][zone] = ( - self._attributes[DeviceAttributes.zone_target_temp][zone] - ) - if ( - self._attributes[DeviceAttributes.mode_auto] == 2 - ): # cooling mode - self._attributes[DeviceAttributes.temperature_max][zone] = ( - self._attributes[DeviceAttributes.zone_cooling_temp_max][ - zone - ] - ) - self._attributes[DeviceAttributes.temperature_min][zone] = ( - self._attributes[DeviceAttributes.zone_cooling_temp_min][ - zone - ] - ) - elif self._attributes[DeviceAttributes.mode] == 3: # heating mode - self._attributes[DeviceAttributes.temperature_max][zone] = ( - self._attributes[DeviceAttributes.zone_heating_temp_max][ - zone - ] - ) - self._attributes[DeviceAttributes.temperature_min][zone] = ( - self._attributes[DeviceAttributes.zone_heating_temp_min][ - zone - ] - ) - else: # Room temp mode - self._attributes[DeviceAttributes.target_temperature][zone] = ( - self._attributes[DeviceAttributes.room_target_temp] - ) - self._attributes[DeviceAttributes.temperature_max][zone] = ( - self._attributes[DeviceAttributes.room_temp_max] - ) - self._attributes[DeviceAttributes.temperature_min][zone] = ( - self._attributes[DeviceAttributes.room_temp_min] - ) - if self._attributes[DeviceAttributes.zone1_power]: - if self._attributes[DeviceAttributes.zone_temp_type][zone]: - self._attributes[DeviceAttributes.zone1_water_temp_mode] = True - self._attributes[DeviceAttributes.zone1_room_temp_mode] = False - else: - self._attributes[DeviceAttributes.zone1_water_temp_mode] = False - self._attributes[DeviceAttributes.zone1_room_temp_mode] = True - else: - self._attributes[DeviceAttributes.zone1_water_temp_mode] = False - self._attributes[DeviceAttributes.zone1_room_temp_mode] = False - if self._attributes[DeviceAttributes.zone2_power]: - if self._attributes[DeviceAttributes.zone_temp_type][zone]: - self._attributes[DeviceAttributes.zone2_water_temp_mode] = True - self._attributes[DeviceAttributes.zone2_room_temp_mode] = False - else: - self._attributes[DeviceAttributes.zone2_water_temp_mode] = False - self._attributes[DeviceAttributes.zone2_room_temp_mode] = True - else: - self._attributes[DeviceAttributes.zone2_water_temp_mode] = False - self._attributes[DeviceAttributes.zone2_room_temp_mode] = False - new_status[DeviceAttributes.zone1_water_temp_mode.value] = self._attributes[ - DeviceAttributes.zone1_water_temp_mode - ] - new_status[DeviceAttributes.zone2_water_temp_mode.value] = self._attributes[ - DeviceAttributes.zone2_water_temp_mode - ] - new_status[DeviceAttributes.zone1_room_temp_mode.value] = self._attributes[ - DeviceAttributes.zone1_room_temp_mode - ] - new_status[DeviceAttributes.zone2_room_temp_mode.value] = self._attributes[ - DeviceAttributes.zone2_room_temp_mode - ] - - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.zone1_power = self._attributes[DeviceAttributes.zone1_power] - message.zone2_power = self._attributes[DeviceAttributes.zone2_power] - message.dhw_power = self._attributes[DeviceAttributes.dhw_power] - message.mode = self._attributes[DeviceAttributes.mode] - message.zone_target_temp = self._attributes[DeviceAttributes.zone_target_temp] - message.dhw_target_temp = self._attributes[DeviceAttributes.dhw_target_temp] - message.room_target_temp = self._attributes[DeviceAttributes.room_target_temp] - message.zone1_curve = self._attributes[DeviceAttributes.zone1_curve] - message.zone2_curve = self._attributes[DeviceAttributes.zone2_curve] - message.disinfect = self._attributes[DeviceAttributes.disinfect] - message.tbh = self._attributes[DeviceAttributes.tbh] - message.fast_dhw = self._attributes[DeviceAttributes.fast_dhw] - return message - - def set_attribute(self, attr, value): - - message = None - - if attr in [ - DeviceAttributes.zone1_power, - DeviceAttributes.zone2_power, - DeviceAttributes.dhw_power, - DeviceAttributes.zone1_curve, - DeviceAttributes.zone2_curve, - DeviceAttributes.disinfect, - DeviceAttributes.fast_dhw, - DeviceAttributes.dhw_target_temp, - DeviceAttributes.tbh, - ]: - message = self.make_message_set() - setattr(message, str(attr), value) - elif attr == DeviceAttributes.eco_mode: - message = MessageSetECO(self._protocol_version) - setattr(message, str(attr), value) - elif attr == DeviceAttributes.silent_mode: - message = MessageSetSilent(self._protocol_version) - setattr(message, str(attr), value) - if message is not None: - self.build_send(message) - - def set_mode(self, zone, mode): - message = self.make_message_set() - if zone == 0: - message.zone1_power = True - else: - message.zone2_power = True - message.mode = mode - self.build_send(message) - - def set_target_temperature(self, zone, target_temperature, mode): - message = self.make_message_set() - if self._attributes[DeviceAttributes.zone_temp_type][zone]: - message.zone_target_temp[zone] = target_temperature - else: - message.room_target_temp = target_temperature - if mode is not None: - if zone == 0: - message.zone1_power = True - else: - message.zone2_power = True - message.mode = mode - self.build_send(message) - - -class MideaAppliance(MideaC3Device): - pass +import logging + +from .message import ( + MessageC3Response, + MessageQuery, + MessageSet, + MessageSetECO, + MessageSetSilent, +) + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + zone1_power = "zone1_power" + zone2_power = "zone2_power" + dhw_power = "dhw_power" + zone1_curve = "zone1_curve" + zone2_curve = "zone2_curve" + disinfect = "disinfect" + fast_dhw = "fast_dhw" + zone_temp_type = "zone_temp_type" + zone1_room_temp_mode = "zone1_room_temp_mode" + zone2_room_temp_mode = "zone2_room_temp_mode" + zone1_water_temp_mode = "zone1_water_temp_mode" + zone2_water_temp_mode = "zone2_water_temp_mode" + mode = "mode" + mode_auto = "mode_auto" + zone_target_temp = "zone_target_temp" + dhw_target_temp = "dhw_target_temp" + room_target_temp = "room_target_temp" + zone_heating_temp_max = "zone_heating_temp_max" + zone_heating_temp_min = "zone_heating_temp_min" + zone_cooling_temp_max = "zone_cooling_temp_max" + zone_cooling_temp_min = "zone_cooling_temp_min" + tank_actual_temperature = "tank_actual_temperature" + room_temp_max = "room_temp_max" + room_temp_min = "room_temp_min" + dhw_temp_max = "dhw_temp_max" + dhw_temp_min = "dhw_temp_min" + target_temperature = "target_temperature" + temperature_max = "temperature_max" + temperature_min = "temperature_min" + status_heating = "status_heating" + status_dhw = "status_dhw" + status_tbh = "status_tbh" + status_ibh = "status_ibh" + total_energy_consumption = "total_energy_consumption" + total_produced_energy = "total_produced_energy" + outdoor_temperature = "outdoor_temperature" + silent_mode = "silent_mode" + eco_mode = "eco_mode" + tbh = "tbh" + error_code = "error_code" + + +class MideaC3Device(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xC3, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.zone1_power: False, + DeviceAttributes.zone2_power: False, + DeviceAttributes.dhw_power: False, + DeviceAttributes.zone1_curve: False, + DeviceAttributes.zone2_curve: False, + DeviceAttributes.disinfect: False, + DeviceAttributes.fast_dhw: False, + DeviceAttributes.zone_temp_type: [False, False], + DeviceAttributes.zone1_room_temp_mode: False, + DeviceAttributes.zone2_room_temp_mode: False, + DeviceAttributes.zone1_water_temp_mode: False, + DeviceAttributes.zone2_water_temp_mode: False, + DeviceAttributes.silent_mode: False, + DeviceAttributes.eco_mode: False, + DeviceAttributes.tbh: False, + DeviceAttributes.mode: 1, + DeviceAttributes.mode_auto: 1, + DeviceAttributes.zone_target_temp: [25, 25], + DeviceAttributes.dhw_target_temp: 25, + DeviceAttributes.room_target_temp: 30, + DeviceAttributes.zone_heating_temp_max: [55, 55], + DeviceAttributes.zone_heating_temp_min: [25, 25], + DeviceAttributes.zone_cooling_temp_max: [25, 25], + DeviceAttributes.zone_cooling_temp_min: [5, 5], + DeviceAttributes.room_temp_max: 60, + DeviceAttributes.room_temp_min: 34, + DeviceAttributes.dhw_temp_max: 60, + DeviceAttributes.dhw_temp_min: 20, + DeviceAttributes.tank_actual_temperature: None, + DeviceAttributes.target_temperature: [25, 25], + DeviceAttributes.temperature_max: [0, 0], + DeviceAttributes.temperature_min: [0, 0], + DeviceAttributes.total_energy_consumption: None, + DeviceAttributes.status_heating: None, + DeviceAttributes.status_dhw: None, + DeviceAttributes.status_tbh: None, + DeviceAttributes.status_ibh: None, + DeviceAttributes.total_produced_energy: None, + DeviceAttributes.outdoor_temperature: None, + DeviceAttributes.error_code: 0, + }, + ) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageC3Response(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = getattr(message, str(status)) + if "zone_temp_type" in new_status: + for zone in [0, 1]: + if self._attributes[DeviceAttributes.zone_temp_type][ + zone + ]: # Water temp mode + self._attributes[DeviceAttributes.target_temperature][zone] = ( + self._attributes[DeviceAttributes.zone_target_temp][zone] + ) + if ( + self._attributes[DeviceAttributes.mode_auto] == 2 + ): # cooling mode + self._attributes[DeviceAttributes.temperature_max][zone] = ( + self._attributes[DeviceAttributes.zone_cooling_temp_max][ + zone + ] + ) + self._attributes[DeviceAttributes.temperature_min][zone] = ( + self._attributes[DeviceAttributes.zone_cooling_temp_min][ + zone + ] + ) + elif self._attributes[DeviceAttributes.mode] == 3: # heating mode + self._attributes[DeviceAttributes.temperature_max][zone] = ( + self._attributes[DeviceAttributes.zone_heating_temp_max][ + zone + ] + ) + self._attributes[DeviceAttributes.temperature_min][zone] = ( + self._attributes[DeviceAttributes.zone_heating_temp_min][ + zone + ] + ) + else: # Room temp mode + self._attributes[DeviceAttributes.target_temperature][zone] = ( + self._attributes[DeviceAttributes.room_target_temp] + ) + self._attributes[DeviceAttributes.temperature_max][zone] = ( + self._attributes[DeviceAttributes.room_temp_max] + ) + self._attributes[DeviceAttributes.temperature_min][zone] = ( + self._attributes[DeviceAttributes.room_temp_min] + ) + if self._attributes[DeviceAttributes.zone1_power]: + if self._attributes[DeviceAttributes.zone_temp_type][zone]: + self._attributes[DeviceAttributes.zone1_water_temp_mode] = True + self._attributes[DeviceAttributes.zone1_room_temp_mode] = False + else: + self._attributes[DeviceAttributes.zone1_water_temp_mode] = False + self._attributes[DeviceAttributes.zone1_room_temp_mode] = True + else: + self._attributes[DeviceAttributes.zone1_water_temp_mode] = False + self._attributes[DeviceAttributes.zone1_room_temp_mode] = False + if self._attributes[DeviceAttributes.zone2_power]: + if self._attributes[DeviceAttributes.zone_temp_type][zone]: + self._attributes[DeviceAttributes.zone2_water_temp_mode] = True + self._attributes[DeviceAttributes.zone2_room_temp_mode] = False + else: + self._attributes[DeviceAttributes.zone2_water_temp_mode] = False + self._attributes[DeviceAttributes.zone2_room_temp_mode] = True + else: + self._attributes[DeviceAttributes.zone2_water_temp_mode] = False + self._attributes[DeviceAttributes.zone2_room_temp_mode] = False + new_status[DeviceAttributes.zone1_water_temp_mode.value] = self._attributes[ + DeviceAttributes.zone1_water_temp_mode + ] + new_status[DeviceAttributes.zone2_water_temp_mode.value] = self._attributes[ + DeviceAttributes.zone2_water_temp_mode + ] + new_status[DeviceAttributes.zone1_room_temp_mode.value] = self._attributes[ + DeviceAttributes.zone1_room_temp_mode + ] + new_status[DeviceAttributes.zone2_room_temp_mode.value] = self._attributes[ + DeviceAttributes.zone2_room_temp_mode + ] + + return new_status + + def make_message_set(self): + message = MessageSet(self._protocol_version) + message.zone1_power = self._attributes[DeviceAttributes.zone1_power] + message.zone2_power = self._attributes[DeviceAttributes.zone2_power] + message.dhw_power = self._attributes[DeviceAttributes.dhw_power] + message.mode = self._attributes[DeviceAttributes.mode] + message.zone_target_temp = self._attributes[DeviceAttributes.zone_target_temp] + message.dhw_target_temp = self._attributes[DeviceAttributes.dhw_target_temp] + message.room_target_temp = self._attributes[DeviceAttributes.room_target_temp] + message.zone1_curve = self._attributes[DeviceAttributes.zone1_curve] + message.zone2_curve = self._attributes[DeviceAttributes.zone2_curve] + message.disinfect = self._attributes[DeviceAttributes.disinfect] + message.tbh = self._attributes[DeviceAttributes.tbh] + message.fast_dhw = self._attributes[DeviceAttributes.fast_dhw] + return message + + def set_attribute(self, attr, value): + + message = None + + if attr in [ + DeviceAttributes.zone1_power, + DeviceAttributes.zone2_power, + DeviceAttributes.dhw_power, + DeviceAttributes.zone1_curve, + DeviceAttributes.zone2_curve, + DeviceAttributes.disinfect, + DeviceAttributes.fast_dhw, + DeviceAttributes.dhw_target_temp, + DeviceAttributes.tbh, + ]: + message = self.make_message_set() + setattr(message, str(attr), value) + elif attr == DeviceAttributes.eco_mode: + message = MessageSetECO(self._protocol_version) + setattr(message, str(attr), value) + elif attr == DeviceAttributes.silent_mode: + message = MessageSetSilent(self._protocol_version) + setattr(message, str(attr), value) + if message is not None: + self.build_send(message) + + def set_mode(self, zone, mode): + message = self.make_message_set() + if zone == 0: + message.zone1_power = True + else: + message.zone2_power = True + message.mode = mode + self.build_send(message) + + def set_target_temperature(self, zone, target_temperature, mode): + message = self.make_message_set() + if self._attributes[DeviceAttributes.zone_temp_type][zone]: + message.zone_target_temp[zone] = target_temperature + else: + message.room_target_temp = target_temperature + if mode is not None: + if zone == 0: + message.zone1_power = True + else: + message.zone2_power = True + message.mode = mode + self.build_send(message) + + +class MideaAppliance(MideaC3Device): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/c3/message.py b/custom_components/midea_ac_lan/midea/devices/c3/message.py index bfae8488..cd2d4243 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/message.py @@ -1,185 +1,185 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageC3Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xC3, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageC3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageC3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x01, - ) - self.zone1_power = False - self.zone2_power = False - self.dhw_power = False - self.mode = 0 - self.zone_target_temp = [25, 25] - self.dhw_target_temp = 40 - self.room_target_temp = 25 - self.zone1_curve = False - self.zone2_curve = False - self.disinfect = False - self.fast_dhw = False - self.tbh = False - - @property - def _body(self): - # Byte 1 - zone1_power = 0x01 if self.zone1_power else 0x00 - zone2_power = 0x02 if self.zone2_power else 0x00 - dhw_power = 0x04 if self.dhw_power else 0x00 - # Byte 7 - zone1_curve = 0x01 if self.zone1_curve else 0x00 - zone2_curve = 0x02 if self.zone2_curve else 0x00 - disinfect = 0x04 if self.disinfect or self.tbh else 0x00 - fast_dhw = 0x08 if self.fast_dhw else 0x00 - room_target_temp = int(self.room_target_temp * 2) - zone1_target_temp = int(self.zone_target_temp[0]) - zone2_target_temp = int(self.zone_target_temp[1]) - dhw_target_temp = int(self.dhw_target_temp) - return bytearray( - [ - zone1_power | zone2_power | dhw_power, - self.mode, - zone1_target_temp, - zone2_target_temp, - dhw_target_temp, - room_target_temp, - zone1_curve | zone2_curve | disinfect | fast_dhw, - ] - ) - - -class MessageSetSilent(MessageC3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x05, - ) - self.silent_mode = False - self.super_silent = False - - @property - def _body(self): - silent_mode = 0x01 if self.silent_mode else 0 - super_silent = 0x02 if self.super_silent else 0 - - return bytearray( - [silent_mode | super_silent, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - ) - - -class MessageSetECO(MessageC3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x07, - ) - self.eco_mode = False - - @property - def _body(self): - eco_mode = 0x01 if self.eco_mode else 0 - - return bytearray([eco_mode, 0x00, 0x00, 0x00, 0x00, 0x00]) - - -class C3MessageBody(MessageBody): - def __init__(self, body, data_offset=0): - super().__init__(body) - self.zone1_power = body[data_offset + 0] & 0x01 > 0 - self.zone2_power = body[data_offset + 0] & 0x02 > 0 - self.dhw_power = body[data_offset + 0] & 0x04 > 0 - self.zone1_curve = body[data_offset + 0] & 0x08 > 0 - self.zone2_curve = body[data_offset + 0] & 0x10 > 0 - self.disinfect = body[data_offset + 0] & 0x20 > 0 - self.tbh = body[data_offset + 0] & 0x20 > 0 - self.fast_dhw = body[data_offset + 0] & 0x40 > 0 - self.zone_temp_type = [ - body[data_offset + 1] & 0x10 > 0, - body[data_offset + 1] & 0x20 > 0, - ] - self.silent_mode = body[data_offset + 2] & 0x02 > 0 - self.eco_mode = body[data_offset + 2] & 0x08 > 0 - self.mode = body[data_offset + 3] - self.mode_auto = body[data_offset + 4] - self.zone_target_temp = [body[data_offset + 5], body[data_offset + 6]] - self.dhw_target_temp = body[data_offset + 7] - self.room_target_temp = body[data_offset + 8] / 2 - self.zone_heating_temp_max = [body[data_offset + 9], body[data_offset + 13]] - self.zone_heating_temp_min = [body[data_offset + 10], body[data_offset + 14]] - self.zone_cooling_temp_max = [body[data_offset + 11], body[data_offset + 15]] - self.zone_cooling_temp_min = [body[data_offset + 12], body[data_offset + 16]] - self.room_temp_max = body[data_offset + 17] / 2 - self.room_temp_min = body[data_offset + 18] / 2 - self.dhw_temp_max = body[data_offset + 19] - self.dhw_temp_min = body[data_offset + 20] - self.tank_actual_temperature = body[data_offset + 21] - self.error_code = body[data_offset + 22] - - -class C3Notify1MessageBody(MessageBody): - def __init__(self, body, data_offset=0): - super().__init__(body) - status_byte = body[data_offset] - self.status_tbh = (status_byte & 0x08) > 0 - self.status_dhw = (status_byte & 0x04) > 0 - self.status_ibh = (status_byte & 0x02) > 0 - self.status_heating = (status_byte & 0x01) > 0 - - self.total_energy_consumption = ( - (body[data_offset + 1] << 32) - + (body[data_offset + 2] << 16) - + (body[data_offset + 3] << 8) - + (body[data_offset + 4]) - ) - - self.total_produced_energy = ( - (body[data_offset + 5] << 32) - + (body[data_offset + 6] << 16) - + (body[data_offset + 7] << 8) - + (body[data_offset + 8]) - ) - self.outdoor_temperature = int(body[data_offset + 9]) - - -class MessageC3Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type - in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01 - ) or self.message_type == MessageType.notify2: - self.set_body(C3MessageBody(super().body, data_offset=1)) - elif self.message_type == MessageType.notify1 and self.body_type == 0x04: - self.set_body(C3Notify1MessageBody(super().body, data_offset=1)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageC3Base(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xC3, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageC3Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x01, + ) + + @property + def _body(self): + return bytearray([]) + + +class MessageSet(MessageC3Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x01, + ) + self.zone1_power = False + self.zone2_power = False + self.dhw_power = False + self.mode = 0 + self.zone_target_temp = [25, 25] + self.dhw_target_temp = 40 + self.room_target_temp = 25 + self.zone1_curve = False + self.zone2_curve = False + self.disinfect = False + self.fast_dhw = False + self.tbh = False + + @property + def _body(self): + # Byte 1 + zone1_power = 0x01 if self.zone1_power else 0x00 + zone2_power = 0x02 if self.zone2_power else 0x00 + dhw_power = 0x04 if self.dhw_power else 0x00 + # Byte 7 + zone1_curve = 0x01 if self.zone1_curve else 0x00 + zone2_curve = 0x02 if self.zone2_curve else 0x00 + disinfect = 0x04 if self.disinfect or self.tbh else 0x00 + fast_dhw = 0x08 if self.fast_dhw else 0x00 + room_target_temp = int(self.room_target_temp * 2) + zone1_target_temp = int(self.zone_target_temp[0]) + zone2_target_temp = int(self.zone_target_temp[1]) + dhw_target_temp = int(self.dhw_target_temp) + return bytearray( + [ + zone1_power | zone2_power | dhw_power, + self.mode, + zone1_target_temp, + zone2_target_temp, + dhw_target_temp, + room_target_temp, + zone1_curve | zone2_curve | disinfect | fast_dhw, + ] + ) + + +class MessageSetSilent(MessageC3Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x05, + ) + self.silent_mode = False + self.super_silent = False + + @property + def _body(self): + silent_mode = 0x01 if self.silent_mode else 0 + super_silent = 0x02 if self.super_silent else 0 + + return bytearray( + [silent_mode | super_silent, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + ) + + +class MessageSetECO(MessageC3Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x07, + ) + self.eco_mode = False + + @property + def _body(self): + eco_mode = 0x01 if self.eco_mode else 0 + + return bytearray([eco_mode, 0x00, 0x00, 0x00, 0x00, 0x00]) + + +class C3MessageBody(MessageBody): + def __init__(self, body, data_offset=0): + super().__init__(body) + self.zone1_power = body[data_offset + 0] & 0x01 > 0 + self.zone2_power = body[data_offset + 0] & 0x02 > 0 + self.dhw_power = body[data_offset + 0] & 0x04 > 0 + self.zone1_curve = body[data_offset + 0] & 0x08 > 0 + self.zone2_curve = body[data_offset + 0] & 0x10 > 0 + self.disinfect = body[data_offset + 0] & 0x20 > 0 + self.tbh = body[data_offset + 0] & 0x20 > 0 + self.fast_dhw = body[data_offset + 0] & 0x40 > 0 + self.zone_temp_type = [ + body[data_offset + 1] & 0x10 > 0, + body[data_offset + 1] & 0x20 > 0, + ] + self.silent_mode = body[data_offset + 2] & 0x02 > 0 + self.eco_mode = body[data_offset + 2] & 0x08 > 0 + self.mode = body[data_offset + 3] + self.mode_auto = body[data_offset + 4] + self.zone_target_temp = [body[data_offset + 5], body[data_offset + 6]] + self.dhw_target_temp = body[data_offset + 7] + self.room_target_temp = body[data_offset + 8] / 2 + self.zone_heating_temp_max = [body[data_offset + 9], body[data_offset + 13]] + self.zone_heating_temp_min = [body[data_offset + 10], body[data_offset + 14]] + self.zone_cooling_temp_max = [body[data_offset + 11], body[data_offset + 15]] + self.zone_cooling_temp_min = [body[data_offset + 12], body[data_offset + 16]] + self.room_temp_max = body[data_offset + 17] / 2 + self.room_temp_min = body[data_offset + 18] / 2 + self.dhw_temp_max = body[data_offset + 19] + self.dhw_temp_min = body[data_offset + 20] + self.tank_actual_temperature = body[data_offset + 21] + self.error_code = body[data_offset + 22] + + +class C3Notify1MessageBody(MessageBody): + def __init__(self, body, data_offset=0): + super().__init__(body) + status_byte = body[data_offset] + self.status_tbh = (status_byte & 0x08) > 0 + self.status_dhw = (status_byte & 0x04) > 0 + self.status_ibh = (status_byte & 0x02) > 0 + self.status_heating = (status_byte & 0x01) > 0 + + self.total_energy_consumption = ( + (body[data_offset + 1] << 32) + + (body[data_offset + 2] << 16) + + (body[data_offset + 3] << 8) + + (body[data_offset + 4]) + ) + + self.total_produced_energy = ( + (body[data_offset + 5] << 32) + + (body[data_offset + 6] << 16) + + (body[data_offset + 7] << 8) + + (body[data_offset + 8]) + ) + self.outdoor_temperature = int(body[data_offset + 9]) + + +class MessageC3Response(MessageResponse): + def __init__(self, message): + super().__init__(message) + if ( + self.message_type + in [MessageType.set, MessageType.notify1, MessageType.query] + and self.body_type == 0x01 + ) or self.message_type == MessageType.notify2: + self.set_body(C3MessageBody(super().body, data_offset=1)) + elif self.message_type == MessageType.notify1 and self.body_type == 0x04: + self.set_body(C3Notify1MessageBody(super().body, data_offset=1)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ca/device.py b/custom_components/midea_ac_lan/midea/devices/ca/device.py index 102b30c2..2fd98d67 100644 --- a/custom_components/midea_ac_lan/midea/devices/ca/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ca/device.py @@ -1,101 +1,101 @@ -import logging - -from .message import MessageCAResponse, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - mode = "mode" - energy_consumption = "energy_consumption" - refrigerator_actual_temp = "refrigerator_actual_temp" - freezer_actual_temp = "freezer_actual_temp" - flex_zone_actual_temp = "flex_zone_actual_temp" - right_flex_zone_actual_temp = "right_flex_zone_actual_temp" - refrigerator_setting_temp = "refrigerator_setting_temp" - freezer_setting_temp = "freezer_setting_temp" - flex_zone_setting_temp = "flex_zone_setting_temp" - right_flex_zone_setting_temp = "right_flex_zone_setting_temp" - refrigerator_door_overtime = "refrigerator_door_overtime" - freezer_door_overtime = "freezer_door_overtime" - bar_door_overtime = "bar_door_overtime" - flex_zone_door_overtime = "flex_zone_door_overtime" - refrigerator_door = "refrigerator_door" - freezer_door = "freezer_door" - bar_door = "bar_door" - flex_zone_door = "flex_zone_door" - - -class MideaCADevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xCA, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.energy_consumption: None, - DeviceAttributes.refrigerator_actual_temp: None, - DeviceAttributes.freezer_actual_temp: None, - DeviceAttributes.flex_zone_actual_temp: None, - DeviceAttributes.right_flex_zone_actual_temp: None, - DeviceAttributes.refrigerator_setting_temp: None, - DeviceAttributes.freezer_setting_temp: None, - DeviceAttributes.flex_zone_setting_temp: None, - DeviceAttributes.right_flex_zone_setting_temp: None, - DeviceAttributes.refrigerator_door_overtime: False, - DeviceAttributes.freezer_door_overtime: False, - DeviceAttributes.bar_door_overtime: False, - DeviceAttributes.flex_zone_door_overtime: False, - DeviceAttributes.refrigerator_door: False, - DeviceAttributes.freezer_door: False, - DeviceAttributes.bar_door: False, - DeviceAttributes.flex_zone_door: False, - }, - ) - self._modes = [""] - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageCAResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaCADevice): - pass +import logging + +from .message import MessageCAResponse, MessageQuery + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + mode = "mode" + energy_consumption = "energy_consumption" + refrigerator_actual_temp = "refrigerator_actual_temp" + freezer_actual_temp = "freezer_actual_temp" + flex_zone_actual_temp = "flex_zone_actual_temp" + right_flex_zone_actual_temp = "right_flex_zone_actual_temp" + refrigerator_setting_temp = "refrigerator_setting_temp" + freezer_setting_temp = "freezer_setting_temp" + flex_zone_setting_temp = "flex_zone_setting_temp" + right_flex_zone_setting_temp = "right_flex_zone_setting_temp" + refrigerator_door_overtime = "refrigerator_door_overtime" + freezer_door_overtime = "freezer_door_overtime" + bar_door_overtime = "bar_door_overtime" + flex_zone_door_overtime = "flex_zone_door_overtime" + refrigerator_door = "refrigerator_door" + freezer_door = "freezer_door" + bar_door = "bar_door" + flex_zone_door = "flex_zone_door" + + +class MideaCADevice(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xCA, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.energy_consumption: None, + DeviceAttributes.refrigerator_actual_temp: None, + DeviceAttributes.freezer_actual_temp: None, + DeviceAttributes.flex_zone_actual_temp: None, + DeviceAttributes.right_flex_zone_actual_temp: None, + DeviceAttributes.refrigerator_setting_temp: None, + DeviceAttributes.freezer_setting_temp: None, + DeviceAttributes.flex_zone_setting_temp: None, + DeviceAttributes.right_flex_zone_setting_temp: None, + DeviceAttributes.refrigerator_door_overtime: False, + DeviceAttributes.freezer_door_overtime: False, + DeviceAttributes.bar_door_overtime: False, + DeviceAttributes.flex_zone_door_overtime: False, + DeviceAttributes.refrigerator_door: False, + DeviceAttributes.freezer_door: False, + DeviceAttributes.bar_door: False, + DeviceAttributes.flex_zone_door: False, + }, + ) + self._modes = [""] + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageCAResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = getattr(message, str(status)) + return new_status + + def set_attribute(self, attr, value): + pass + + +class MideaAppliance(MideaCADevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/ca/message.py b/custom_components/midea_ac_lan/midea/devices/ca/message.py index 8f95db0b..0611a875 100644 --- a/custom_components/midea_ac_lan/midea/devices/ca/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ca/message.py @@ -1,121 +1,121 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageCABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xCA, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageCABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x00, - ) - - @property - def _body(self): - return bytearray([]) - - -class CAGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.refrigerator_setting_temp = body[2] & 0x0F - self.freezer_setting_temp = -12 - ((body[2] & 0xF0) >> 4) - flex_zone_setting_temp = body[3] - right_flex_zone_setting_temp = body[4] - - if 1 <= flex_zone_setting_temp <= 29: - self.flex_zone_setting_temp = flex_zone_setting_temp - 19 - elif 49 <= flex_zone_setting_temp <= 54: - self.flex_zone_setting_temp = 30 - flex_zone_setting_temp - else: - self.flex_zone_setting_temp = 0 - if 1 <= right_flex_zone_setting_temp <= 29: - self.right_flex_zone_setting_temp = right_flex_zone_setting_temp - 19 - elif 49 <= right_flex_zone_setting_temp <= 54: - self.right_flex_zone_setting_temp = 30 - right_flex_zone_setting_temp - else: - self.right_flex_zone_setting_temp = 0 - - self.energy_consumption = (body[13] << 8) + body[12] - self.refrigerator_actual_temp = (body[17] - 100) / 2 - self.freezer_actual_temp = (body[18] - 100) / 2 - self.flex_zone_actual_temp = (body[19] - 100) / 2 - self.right_flex_zone_actual_temp = (body[20] - 100) / 2 - - -class CAExceptionMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.refrigerator_door_overtime = (body[1] & 0x01) > 0 - self.freezer_door_overtime = (body[1] & 0x02) > 0 - self.bar_door_overtime = (body[1] & 0x04) > 0 - self.flex_zone_door_overtime = (body[1] & 0x08) > 0 - - -class CANotify00MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.refrigerator_door = (body[1] & 0x01) > 0 - self.freezer_door = (body[1] & 0x02) > 0 - self.bar_door = (body[1] & 0x04) > 0 - self.flex_zone_door = (body[1] & 0x010) > 0 - - -class CANotify01MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.refrigerator_setting_temp = body[37] - self.freezer_setting_temp = -12 - body[38] - flex_zone_setting_temp = body[39] - right_flex_zone_setting_temp = body[40] - - if 1 <= flex_zone_setting_temp <= 29: - self.flex_zone_setting_temp = flex_zone_setting_temp - 19 - elif 49 <= flex_zone_setting_temp <= 54: - self.flex_zone_setting_temp = 30 - flex_zone_setting_temp - else: - self.flex_zone_setting_temp = 0 - if 1 <= right_flex_zone_setting_temp <= 29: - self.right_flex_zone_setting_temp = right_flex_zone_setting_temp - 19 - elif 49 <= right_flex_zone_setting_temp <= 54: - self.right_flex_zone_setting_temp = 30 - right_flex_zone_setting_temp - else: - self.right_flex_zone_setting_temp = 0 - - -class MessageCAResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0x00 - ) - or (self.message_type == MessageType.notify1 and self.body_type == 0x02) - ) and len(super().body) > 20: - self.set_body(CAGeneralMessageBody(super().body)) - elif ( - self.message_type == MessageType.exception and self.body_type == 0x01 - ) or (self.message_type == 0x03 and self.body_type == 0x02): - self.set_body(CAExceptionMessageBody(super().body)) - elif self.message_type == MessageType.notify1 and self.body_type == 0x00: - self.set_body(CANotify00MessageBody(super().body)) - elif ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0x01 - ): - self.set_body(CANotify01MessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageCABase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xCA, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageCABase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x00, + ) + + @property + def _body(self): + return bytearray([]) + + +class CAGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.refrigerator_setting_temp = body[2] & 0x0F + self.freezer_setting_temp = -12 - ((body[2] & 0xF0) >> 4) + flex_zone_setting_temp = body[3] + right_flex_zone_setting_temp = body[4] + + if 1 <= flex_zone_setting_temp <= 29: + self.flex_zone_setting_temp = flex_zone_setting_temp - 19 + elif 49 <= flex_zone_setting_temp <= 54: + self.flex_zone_setting_temp = 30 - flex_zone_setting_temp + else: + self.flex_zone_setting_temp = 0 + if 1 <= right_flex_zone_setting_temp <= 29: + self.right_flex_zone_setting_temp = right_flex_zone_setting_temp - 19 + elif 49 <= right_flex_zone_setting_temp <= 54: + self.right_flex_zone_setting_temp = 30 - right_flex_zone_setting_temp + else: + self.right_flex_zone_setting_temp = 0 + + self.energy_consumption = (body[13] << 8) + body[12] + self.refrigerator_actual_temp = (body[17] - 100) / 2 + self.freezer_actual_temp = (body[18] - 100) / 2 + self.flex_zone_actual_temp = (body[19] - 100) / 2 + self.right_flex_zone_actual_temp = (body[20] - 100) / 2 + + +class CAExceptionMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.refrigerator_door_overtime = (body[1] & 0x01) > 0 + self.freezer_door_overtime = (body[1] & 0x02) > 0 + self.bar_door_overtime = (body[1] & 0x04) > 0 + self.flex_zone_door_overtime = (body[1] & 0x08) > 0 + + +class CANotify00MessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.refrigerator_door = (body[1] & 0x01) > 0 + self.freezer_door = (body[1] & 0x02) > 0 + self.bar_door = (body[1] & 0x04) > 0 + self.flex_zone_door = (body[1] & 0x010) > 0 + + +class CANotify01MessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.refrigerator_setting_temp = body[37] + self.freezer_setting_temp = -12 - body[38] + flex_zone_setting_temp = body[39] + right_flex_zone_setting_temp = body[40] + + if 1 <= flex_zone_setting_temp <= 29: + self.flex_zone_setting_temp = flex_zone_setting_temp - 19 + elif 49 <= flex_zone_setting_temp <= 54: + self.flex_zone_setting_temp = 30 - flex_zone_setting_temp + else: + self.flex_zone_setting_temp = 0 + if 1 <= right_flex_zone_setting_temp <= 29: + self.right_flex_zone_setting_temp = right_flex_zone_setting_temp - 19 + elif 49 <= right_flex_zone_setting_temp <= 54: + self.right_flex_zone_setting_temp = 30 - right_flex_zone_setting_temp + else: + self.right_flex_zone_setting_temp = 0 + + +class MessageCAResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if ( + ( + self.message_type in [MessageType.query, MessageType.set] + and self.body_type == 0x00 + ) + or (self.message_type == MessageType.notify1 and self.body_type == 0x02) + ) and len(super().body) > 20: + self.set_body(CAGeneralMessageBody(super().body)) + elif ( + self.message_type == MessageType.exception and self.body_type == 0x01 + ) or (self.message_type == 0x03 and self.body_type == 0x02): + self.set_body(CAExceptionMessageBody(super().body)) + elif self.message_type == MessageType.notify1 and self.body_type == 0x00: + self.set_body(CANotify00MessageBody(super().body)) + elif ( + self.message_type in [MessageType.query, MessageType.notify1] + and self.body_type == 0x01 + ): + self.set_body(CANotify01MessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cc/device.py b/custom_components/midea_ac_lan/midea/devices/cc/device.py index 7ae56f66..803984cc 100644 --- a/custom_components/midea_ac_lan/midea/devices/cc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cc/device.py @@ -1,200 +1,200 @@ -import logging - -from .message import MessageCCResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - target_temperature = "target_temperature" - fan_speed = "fan_speed" - eco_mode = "eco_mode" - sleep_mode = "sleep_mode" - night_light = "night_light" - aux_heating = "aux_heating" - swing = "swing" - ventilation = "ventilation" - temperature_precision = "temperature_precision" - fan_speed_level = "fan_speed_level" - indoor_temperature = "indoor_temperature" - aux_heat_status = "aux_heat_status" - auto_aux_heat_running = "auto_aux_heat_running" - temp_fahrenheit = "temp_fahrenheit" - - -class MideaCCDevice(MiedaDevice): - _fan_speeds_7level = { - 0x01: "Level 1", - 0x02: "Level 2", - 0x04: "Level 3", - 0x08: "Level 4", - 0x10: "Level 5", - 0x20: "Level 6", - 0x40: "Level 7", - 0x80: "Auto", - } - _fan_speeds_3level = {0x01: "Low", 0x08: "Medium", 0x40: "High", 0x80: "Auto"} - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xCC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: 1, - DeviceAttributes.target_temperature: 26.0, - DeviceAttributes.fan_speed: 0x80, - DeviceAttributes.sleep_mode: False, - DeviceAttributes.eco_mode: False, - DeviceAttributes.night_light: False, - DeviceAttributes.ventilation: False, - DeviceAttributes.aux_heating: False, - DeviceAttributes.aux_heat_status: 0, - DeviceAttributes.auto_aux_heat_running: False, - DeviceAttributes.swing: False, - DeviceAttributes.fan_speed_level: None, - DeviceAttributes.indoor_temperature: None, - DeviceAttributes.temperature_precision: 1, - DeviceAttributes.temp_fahrenheit: False, - }, - ) - self._fan_speeds = None - - @property - def fan_modes(self): - return None if self._fan_speeds is None else list(self._fan_speeds.values()) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageCCResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - fan_speed = None - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.fan_speed: - fan_speed = value - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - if ( - fan_speed is not None - and self._attributes[DeviceAttributes.fan_speed_level] is not None - ): - if self._fan_speeds is None: - if self._attributes[DeviceAttributes.fan_speed_level]: - self._fan_speeds = MideaCCDevice._fan_speeds_3level - else: - self._fan_speeds = MideaCCDevice._fan_speeds_7level - if fan_speed in self._fan_speeds.keys(): - self._attributes[DeviceAttributes.fan_speed] = self._fan_speeds.get( - fan_speed - ) - else: - self._attributes[DeviceAttributes.fan_speed] = None - new_status[DeviceAttributes.fan_speed.value] = self._attributes[ - DeviceAttributes.fan_speed - ] - aux_heating = ( - self._attributes[DeviceAttributes.aux_heat_status] == 1 - or self._attributes[DeviceAttributes.auto_aux_heat_running] - ) - if self._attributes[DeviceAttributes.aux_heating] != aux_heating: - self._attributes[DeviceAttributes.aux_heating] = aux_heating - new_status[DeviceAttributes.aux_heating.value] = self._attributes[ - DeviceAttributes.aux_heating - ] - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - message.fan_speed = list(self._fan_speeds.keys())[ - list(self._fan_speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - message.eco_mode = self._attributes[DeviceAttributes.eco_mode] - message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] - message.night_light = self._attributes[DeviceAttributes.night_light] - message.aux_heat_status = self._attributes[DeviceAttributes.aux_heat_status] - message.swing = self._attributes[DeviceAttributes.swing] - return message - - def set_target_temperature(self, target_temperature, mode): - message = self.make_message_set() - message.target_temperature = target_temperature - if mode is not None: - message.power = True - message.mode = mode - self.build_send(message) - - def set_attribute(self, attr, value): - # if nat a sensor - if attr not in [ - DeviceAttributes.indoor_temperature, - DeviceAttributes.temperature_precision, - DeviceAttributes.fan_speed_level, - DeviceAttributes.aux_heat_status, - DeviceAttributes.auto_aux_heat_running, - ]: - message = self.make_message_set() - if attr == DeviceAttributes.fan_speed: - if value in self._fan_speeds.values(): - message.fan_speed = list(self._fan_speeds.keys())[ - list(self._fan_speeds.values()).index(value) - ] - else: - setattr(message, str(attr), value) - if attr == DeviceAttributes.mode: - setattr(message, str(DeviceAttributes.power.value), True) - elif attr == DeviceAttributes.eco_mode and value: - setattr(message, str(DeviceAttributes.sleep_mode.value), False) - elif attr == DeviceAttributes.sleep_mode and value: - setattr(message, str(DeviceAttributes.eco_mode.value), False) - elif attr == DeviceAttributes.aux_heating: - if value: - setattr(message, DeviceAttributes.aux_heat_status, 1) - else: - setattr(message, DeviceAttributes.aux_heat_status, 2) - self.build_send(message) - - -class MideaAppliance(MideaCCDevice): - pass +import logging + +from .message import MessageCCResponse, MessageQuery, MessageSet + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + mode = "mode" + target_temperature = "target_temperature" + fan_speed = "fan_speed" + eco_mode = "eco_mode" + sleep_mode = "sleep_mode" + night_light = "night_light" + aux_heating = "aux_heating" + swing = "swing" + ventilation = "ventilation" + temperature_precision = "temperature_precision" + fan_speed_level = "fan_speed_level" + indoor_temperature = "indoor_temperature" + aux_heat_status = "aux_heat_status" + auto_aux_heat_running = "auto_aux_heat_running" + temp_fahrenheit = "temp_fahrenheit" + + +class MideaCCDevice(MiedaDevice): + _fan_speeds_7level = { + 0x01: "Level 1", + 0x02: "Level 2", + 0x04: "Level 3", + 0x08: "Level 4", + 0x10: "Level 5", + 0x20: "Level 6", + 0x40: "Level 7", + 0x80: "Auto", + } + _fan_speeds_3level = {0x01: "Low", 0x08: "Medium", 0x40: "High", 0x80: "Auto"} + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xCC, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.mode: 1, + DeviceAttributes.target_temperature: 26.0, + DeviceAttributes.fan_speed: 0x80, + DeviceAttributes.sleep_mode: False, + DeviceAttributes.eco_mode: False, + DeviceAttributes.night_light: False, + DeviceAttributes.ventilation: False, + DeviceAttributes.aux_heating: False, + DeviceAttributes.aux_heat_status: 0, + DeviceAttributes.auto_aux_heat_running: False, + DeviceAttributes.swing: False, + DeviceAttributes.fan_speed_level: None, + DeviceAttributes.indoor_temperature: None, + DeviceAttributes.temperature_precision: 1, + DeviceAttributes.temp_fahrenheit: False, + }, + ) + self._fan_speeds = None + + @property + def fan_modes(self): + return None if self._fan_speeds is None else list(self._fan_speeds.values()) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageCCResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + fan_speed = None + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.fan_speed: + fan_speed = value + else: + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = getattr(message, str(status)) + if ( + fan_speed is not None + and self._attributes[DeviceAttributes.fan_speed_level] is not None + ): + if self._fan_speeds is None: + if self._attributes[DeviceAttributes.fan_speed_level]: + self._fan_speeds = MideaCCDevice._fan_speeds_3level + else: + self._fan_speeds = MideaCCDevice._fan_speeds_7level + if fan_speed in self._fan_speeds.keys(): + self._attributes[DeviceAttributes.fan_speed] = self._fan_speeds.get( + fan_speed + ) + else: + self._attributes[DeviceAttributes.fan_speed] = None + new_status[DeviceAttributes.fan_speed.value] = self._attributes[ + DeviceAttributes.fan_speed + ] + aux_heating = ( + self._attributes[DeviceAttributes.aux_heat_status] == 1 + or self._attributes[DeviceAttributes.auto_aux_heat_running] + ) + if self._attributes[DeviceAttributes.aux_heating] != aux_heating: + self._attributes[DeviceAttributes.aux_heating] = aux_heating + new_status[DeviceAttributes.aux_heating.value] = self._attributes[ + DeviceAttributes.aux_heating + ] + return new_status + + def make_message_set(self): + message = MessageSet(self._protocol_version) + message.power = self._attributes[DeviceAttributes.power] + message.mode = self._attributes[DeviceAttributes.mode] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] + message.fan_speed = list(self._fan_speeds.keys())[ + list(self._fan_speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + ) + ] + message.eco_mode = self._attributes[DeviceAttributes.eco_mode] + message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] + message.night_light = self._attributes[DeviceAttributes.night_light] + message.aux_heat_status = self._attributes[DeviceAttributes.aux_heat_status] + message.swing = self._attributes[DeviceAttributes.swing] + return message + + def set_target_temperature(self, target_temperature, mode): + message = self.make_message_set() + message.target_temperature = target_temperature + if mode is not None: + message.power = True + message.mode = mode + self.build_send(message) + + def set_attribute(self, attr, value): + # if nat a sensor + if attr not in [ + DeviceAttributes.indoor_temperature, + DeviceAttributes.temperature_precision, + DeviceAttributes.fan_speed_level, + DeviceAttributes.aux_heat_status, + DeviceAttributes.auto_aux_heat_running, + ]: + message = self.make_message_set() + if attr == DeviceAttributes.fan_speed: + if value in self._fan_speeds.values(): + message.fan_speed = list(self._fan_speeds.keys())[ + list(self._fan_speeds.values()).index(value) + ] + else: + setattr(message, str(attr), value) + if attr == DeviceAttributes.mode: + setattr(message, str(DeviceAttributes.power.value), True) + elif attr == DeviceAttributes.eco_mode and value: + setattr(message, str(DeviceAttributes.sleep_mode.value), False) + elif attr == DeviceAttributes.sleep_mode and value: + setattr(message, str(DeviceAttributes.eco_mode.value), False) + elif attr == DeviceAttributes.aux_heating: + if value: + setattr(message, DeviceAttributes.aux_heat_status, 1) + else: + setattr(message, DeviceAttributes.aux_heat_status, 2) + self.build_send(message) + + +class MideaAppliance(MideaCCDevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/cc/message.py b/custom_components/midea_ac_lan/midea/devices/cc/message.py index eb1dba47..f94a7c34 100644 --- a/custom_components/midea_ac_lan/midea/devices/cc/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cc/message.py @@ -1,143 +1,143 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageCCBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xCC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageCCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([0x00] * 23) - - -class MessageSet(MessageCCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0xC3, - ) - self.power = False - self.mode = 4 - self.fan_speed = 0x80 - self.target_temperature = 26 - self.eco_mode = False - self.sleep_mode = False - self.night_light = False - self.ventilation = False - self.aux_heat_status = 0 - self.auto_aux_heat_running = False - self.swing = False - - @property - def _body(self): - # Byte1, Power Mode - power = 0x80 if self.power else 0 - mode = 1 << (self.mode - 1) - # Byte2 fan_speed - fan_speed = self.fan_speed - # Byte3 Integer of target_temperature - temperature_integer = int(self.target_temperature) & 0xFF - # Byte6 eco_mode ventilation aux_heating - eco_mode = 0x01 if self.eco_mode else 0 - if self.aux_heat_status == 1: - aux_heating = 0x10 - elif self.aux_heat_status == 2: - aux_heating = 0x20 - else: - aux_heating = 0 - swing = 0x04 if self.swing else 0 - ventilation = 0x08 if self.ventilation else 0 - # Byte8 sleep_mode night_light - sleep_mode = 0x10 if self.sleep_mode else 0 - night_light = 0x08 if self.night_light else 0 - # Byte11 Dot of target_temperature - temperature_dot = ( - int((self.target_temperature - temperature_integer) * 10) & 0xFF - ) - return bytearray( - [ - power | mode, - fan_speed, - temperature_integer, - # timer - 0x00, - 0x00, - eco_mode | ventilation | swing | aux_heating, - # non-stepless fan speed - 0xFF, - sleep_mode | night_light, - 0x00, - 0x00, - temperature_dot, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class CCGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x80) > 0 - mode = body[1] & 0x1F - self.mode = 0 - while mode >= 1: - mode /= 2 - self.mode += 1 - self.fan_speed = body[2] - self.target_temperature = body[3] + body[19] / 10 - self.indoor_temperature = (body[4] - 40) / 2 - self.eco_mode = (body[13] & 0x01) > 0 - self.sleep_mode = (body[14] & 0x10) > 0 - self.night_light = (body[14] & 0x08) > 0 - self.ventilation = (body[13] & 0x08) > 0 - self.aux_heat_status = (body[14] & 0x60) >> 5 - self.auto_aux_heat_running = (body[13] & 0x02) > 0 - self.fan_speed_level = (body[13] & 0x40) > 0 - self.temperature_precision = 1 if (body[14] & 0x80) > 0 else 0.5 - self.swing = (body[13] & 0x04) > 0 - self.temp_fahrenheit = (body[20] & 0x80) > 0 - - -class MessageCCResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - (self.message_type == MessageType.query and self.body_type == 0x01) - or ( - self.message_type in [MessageType.notify1, MessageType.notify2] - and self.body_type == 0x01 - ) - or (self.message_type == MessageType.set and self.body_type == 0xC3) - ): - self.set_body(CCGeneralMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageCCBase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xCC, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageCCBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x01, + ) + + @property + def _body(self): + return bytearray([0x00] * 23) + + +class MessageSet(MessageCCBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0xC3, + ) + self.power = False + self.mode = 4 + self.fan_speed = 0x80 + self.target_temperature = 26 + self.eco_mode = False + self.sleep_mode = False + self.night_light = False + self.ventilation = False + self.aux_heat_status = 0 + self.auto_aux_heat_running = False + self.swing = False + + @property + def _body(self): + # Byte1, Power Mode + power = 0x80 if self.power else 0 + mode = 1 << (self.mode - 1) + # Byte2 fan_speed + fan_speed = self.fan_speed + # Byte3 Integer of target_temperature + temperature_integer = int(self.target_temperature) & 0xFF + # Byte6 eco_mode ventilation aux_heating + eco_mode = 0x01 if self.eco_mode else 0 + if self.aux_heat_status == 1: + aux_heating = 0x10 + elif self.aux_heat_status == 2: + aux_heating = 0x20 + else: + aux_heating = 0 + swing = 0x04 if self.swing else 0 + ventilation = 0x08 if self.ventilation else 0 + # Byte8 sleep_mode night_light + sleep_mode = 0x10 if self.sleep_mode else 0 + night_light = 0x08 if self.night_light else 0 + # Byte11 Dot of target_temperature + temperature_dot = ( + int((self.target_temperature - temperature_integer) * 10) & 0xFF + ) + return bytearray( + [ + power | mode, + fan_speed, + temperature_integer, + # timer + 0x00, + 0x00, + eco_mode | ventilation | swing | aux_heating, + # non-stepless fan speed + 0xFF, + sleep_mode | night_light, + 0x00, + 0x00, + temperature_dot, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class CCGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[1] & 0x80) > 0 + mode = body[1] & 0x1F + self.mode = 0 + while mode >= 1: + mode /= 2 + self.mode += 1 + self.fan_speed = body[2] + self.target_temperature = body[3] + body[19] / 10 + self.indoor_temperature = (body[4] - 40) / 2 + self.eco_mode = (body[13] & 0x01) > 0 + self.sleep_mode = (body[14] & 0x10) > 0 + self.night_light = (body[14] & 0x08) > 0 + self.ventilation = (body[13] & 0x08) > 0 + self.aux_heat_status = (body[14] & 0x60) >> 5 + self.auto_aux_heat_running = (body[13] & 0x02) > 0 + self.fan_speed_level = (body[13] & 0x40) > 0 + self.temperature_precision = 1 if (body[14] & 0x80) > 0 else 0.5 + self.swing = (body[13] & 0x04) > 0 + self.temp_fahrenheit = (body[20] & 0x80) > 0 + + +class MessageCCResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if ( + (self.message_type == MessageType.query and self.body_type == 0x01) + or ( + self.message_type in [MessageType.notify1, MessageType.notify2] + and self.body_type == 0x01 + ) + or (self.message_type == MessageType.set and self.body_type == 0xC3) + ): + self.set_body(CCGeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ce/message.py b/custom_components/midea_ac_lan/midea/devices/ce/message.py index 91c995a0..0f6afe3f 100644 --- a/custom_components/midea_ac_lan/midea/devices/ce/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ce/message.py @@ -1,135 +1,135 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xCE, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageFABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageFABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x01, - ) - - self.power = False - self.fan_speed = 0 - self.link_to_ac = False - self.sleep_mode = False - self.eco_mode = False - self.aux_heating = False - self.powerful_purify = False - self.scheduled = False - self.child_lock = False - - @property - def _body(self): - power = 0x80 if self.power else 0x00 - link_to_ac = 0x01 if self.link_to_ac else 0x00 - sleep_mode = 0x02 if self.sleep_mode else 0x00 - eco_mode = 0x04 if self.eco_mode else 0x00 - aux_heating = 0x08 if self.aux_heating else 0x00 - powerful_purify = 0x10 if self.powerful_purify else 0x00 - scheduled = 0x01 if self.scheduled else 0x00 - child_lock = 0x7F if self.child_lock else 0x00 - return bytearray( - [ - power | 0x01, - self.fan_speed, - link_to_ac | sleep_mode | eco_mode | aux_heating | powerful_purify, - scheduled, - 0x00, - child_lock, - ] - ) - - -class CEGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x80) > 0 - self.child_lock = (body[1] & 0x20) > 0 - self.scheduled = (body[1] & 0x40) > 0 - self.fan_speed = body[2] - self.pm25 = (body[3] << 8) + body[4] - self.co2 = (body[5] << 8) + body[6] - if body[7] != 0xFF: - self.current_humidity = (body[7] << 8) + body[8] / 10 - else: - self.current_humidity = None - if body[9] != 0xFF: - self.current_temperature = (body[9] << 8) + (body[10] - 60) / 2 - else: - self.current_temperature = None - if body[11] != 0xFF: - self.hcho = (body[11] << 8) + body[12] / 1000 - else: - self.hcho = None - self.link_to_ac = (body[17] & 0x01) > 0 - self.sleep_mode = (body[17] & 0x02) > 0 - self.eco_mode = (body[17] & 0x04) > 0 - if (body[19] & 0x02) > 0: - self.aux_heating = (body[17] & 0x08) > 0 - else: - self.aux_heating = None - self.powerful_purify = (body[17] & 0x10) > 0 - self.filter_cleaning_reminder = (body[18] & 0x01) > 0 - self.filter_change_reminder = (body[18] & 0x02) > 0 - self.error_code = body[24] - - -class CENotifyMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.pm25 = (body[1] << 8) + body[2] - self.co2 = (body[3] << 8) + body[4] - if body[5] != 0xFF: - self.current_humidity = (body[5] << 8) + body[6] / 10 - else: - self.current_humidity = None - if body[7] != 0xFF: - self.current_temperature = (body[7] << 8) + (body[8] - 60) / 2 - else: - self.current_temperature = None - if body[9] != 0xFF: - self.hcho = (body[9] << 8) + body[10] / 1000 - else: - self.hcho = None - self.error_code = body[12] - - -class MessageCEResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0x01 - ) or (self.message_type == MessageType.notify1 and self.body_type == 0x02): - self.set_body(CEGeneralMessageBody(super().body)) - elif self.message_type == MessageType.notify1 and self.body_type == 0x01: - self.set_body(CENotifyMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageFABase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xCE, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageFABase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x01, + ) + + @property + def _body(self): + return bytearray([]) + + +class MessageSet(MessageFABase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x01, + ) + + self.power = False + self.fan_speed = 0 + self.link_to_ac = False + self.sleep_mode = False + self.eco_mode = False + self.aux_heating = False + self.powerful_purify = False + self.scheduled = False + self.child_lock = False + + @property + def _body(self): + power = 0x80 if self.power else 0x00 + link_to_ac = 0x01 if self.link_to_ac else 0x00 + sleep_mode = 0x02 if self.sleep_mode else 0x00 + eco_mode = 0x04 if self.eco_mode else 0x00 + aux_heating = 0x08 if self.aux_heating else 0x00 + powerful_purify = 0x10 if self.powerful_purify else 0x00 + scheduled = 0x01 if self.scheduled else 0x00 + child_lock = 0x7F if self.child_lock else 0x00 + return bytearray( + [ + power | 0x01, + self.fan_speed, + link_to_ac | sleep_mode | eco_mode | aux_heating | powerful_purify, + scheduled, + 0x00, + child_lock, + ] + ) + + +class CEGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[1] & 0x80) > 0 + self.child_lock = (body[1] & 0x20) > 0 + self.scheduled = (body[1] & 0x40) > 0 + self.fan_speed = body[2] + self.pm25 = (body[3] << 8) + body[4] + self.co2 = (body[5] << 8) + body[6] + if body[7] != 0xFF: + self.current_humidity = (body[7] << 8) + body[8] / 10 + else: + self.current_humidity = None + if body[9] != 0xFF: + self.current_temperature = (body[9] << 8) + (body[10] - 60) / 2 + else: + self.current_temperature = None + if body[11] != 0xFF: + self.hcho = (body[11] << 8) + body[12] / 1000 + else: + self.hcho = None + self.link_to_ac = (body[17] & 0x01) > 0 + self.sleep_mode = (body[17] & 0x02) > 0 + self.eco_mode = (body[17] & 0x04) > 0 + if (body[19] & 0x02) > 0: + self.aux_heating = (body[17] & 0x08) > 0 + else: + self.aux_heating = None + self.powerful_purify = (body[17] & 0x10) > 0 + self.filter_cleaning_reminder = (body[18] & 0x01) > 0 + self.filter_change_reminder = (body[18] & 0x02) > 0 + self.error_code = body[24] + + +class CENotifyMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.pm25 = (body[1] << 8) + body[2] + self.co2 = (body[3] << 8) + body[4] + if body[5] != 0xFF: + self.current_humidity = (body[5] << 8) + body[6] / 10 + else: + self.current_humidity = None + if body[7] != 0xFF: + self.current_temperature = (body[7] << 8) + (body[8] - 60) / 2 + else: + self.current_temperature = None + if body[9] != 0xFF: + self.hcho = (body[9] << 8) + body[10] / 1000 + else: + self.hcho = None + self.error_code = body[12] + + +class MessageCEResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if ( + self.message_type in [MessageType.query, MessageType.set] + and self.body_type == 0x01 + ) or (self.message_type == MessageType.notify1 and self.body_type == 0x02): + self.set_body(CEGeneralMessageBody(super().body)) + elif self.message_type == MessageType.notify1 and self.body_type == 0x01: + self.set_body(CENotifyMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cf/device.py b/custom_components/midea_ac_lan/midea/devices/cf/device.py index fada640d..b9a291aa 100644 --- a/custom_components/midea_ac_lan/midea/devices/cf/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cf/device.py @@ -1,100 +1,100 @@ -import logging - -from .message import MessageCFResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - target_temperature = "target_temperature" - aux_heating = "aux_heating" - current_temperature = "current_temperature" - max_temperature = "max_temperature" - min_temperature = "min_temperature" - - -class MideaCFDevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xCF, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: 0, - DeviceAttributes.target_temperature: None, - DeviceAttributes.aux_heating: False, - DeviceAttributes.current_temperature: 0, - DeviceAttributes.max_temperature: 55, - DeviceAttributes.min_temperature: 5, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageCFResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - return new_status - - def set_target_temperature(self, target_temperature, mode): - message = MessageSet(self._protocol_version) - message.power = True - message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = target_temperature - if mode is not None: - message.mode = mode - self.build_send(message) - - def set_attribute(self, attr, value): - message = MessageSet(self._protocol_version) - message.power = True - message.mode = self._attributes[DeviceAttributes.mode] - if attr == DeviceAttributes.power: - message.power = value - elif attr == DeviceAttributes.mode: - message.power = True - message.mode = value - elif attr == DeviceAttributes.target_temperature: - message.target_temperature = value - elif attr == DeviceAttributes.aux_heating: - message.aux_heating = value - self.build_send(message) - - -class MideaAppliance(MideaCFDevice): - pass +import logging + +from .message import MessageCFResponse, MessageQuery, MessageSet + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + mode = "mode" + target_temperature = "target_temperature" + aux_heating = "aux_heating" + current_temperature = "current_temperature" + max_temperature = "max_temperature" + min_temperature = "min_temperature" + + +class MideaCFDevice(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xCF, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.mode: 0, + DeviceAttributes.target_temperature: None, + DeviceAttributes.aux_heating: False, + DeviceAttributes.current_temperature: 0, + DeviceAttributes.max_temperature: 55, + DeviceAttributes.min_temperature: 5, + }, + ) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageCFResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = getattr(message, str(status)) + return new_status + + def set_target_temperature(self, target_temperature, mode): + message = MessageSet(self._protocol_version) + message.power = True + message.mode = self._attributes[DeviceAttributes.mode] + message.target_temperature = target_temperature + if mode is not None: + message.mode = mode + self.build_send(message) + + def set_attribute(self, attr, value): + message = MessageSet(self._protocol_version) + message.power = True + message.mode = self._attributes[DeviceAttributes.mode] + if attr == DeviceAttributes.power: + message.power = value + elif attr == DeviceAttributes.mode: + message.power = True + message.mode = value + elif attr == DeviceAttributes.target_temperature: + message.target_temperature = value + elif attr == DeviceAttributes.aux_heating: + message.aux_heating = value + self.build_send(message) + + +class MideaAppliance(MideaCFDevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/cf/message.py b/custom_components/midea_ac_lan/midea/devices/cf/message.py index 0ca806fb..0bf0fb3f 100644 --- a/custom_components/midea_ac_lan/midea/devices/cf/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cf/message.py @@ -1,88 +1,88 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageCFBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xCF, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageCFBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageCFBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x01, - ) - self.power = False - self.mode = 0 # 1 自动 2 制冷 3 制热 - self.target_temperature = None - self.aux_heating = None - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - mode = self.mode - target_temperature = ( - 0xFF - if self.target_temperature is None - else (int(self.target_temperature) & 0xFF) - ) - aux_heating = ( - 0xFF if self.aux_heating is None else (0x01 if self.aux_heating else 0x00) - ) - return bytearray([power, mode, target_temperature, aux_heating]) - - -class CFMessageBody(MessageBody): - def __init__(self, body, data_offset=0): - super().__init__(body) - self.power = (body[data_offset + 0] & 0x01) > 0 - self.aux_heating = (body[data_offset + 0] & 0x02) > 0 - self.silent = (body[data_offset + 0] & 0x04) > 0 - self.mode = body[data_offset + 3] - self.target_temperature = body[data_offset + 4] - self.current_temperature = body[data_offset + 5] - if self.mode == 2: - self.max_temperature = body[data_offset + 8] - self.min_temperature = body[data_offset + 9] - elif self.mode == 3: - self.max_temperature = body[data_offset + 6] - self.min_temperature = body[data_offset + 7] - else: - self.max_temperature = body[data_offset + 6] - self.min_temperature = body[data_offset + 9] - - -class MessageCFResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0x01 - ): - self.set_body(CFMessageBody(super().body, data_offset=1)) - elif self.message_type in [MessageType.notify1, MessageType.notify2]: - self.set_body(CFMessageBody(super().body, data_offset=0)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageCFBase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xCF, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageCFBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x01, + ) + + @property + def _body(self): + return bytearray([]) + + +class MessageSet(MessageCFBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x01, + ) + self.power = False + self.mode = 0 # 1 自动 2 制冷 3 制热 + self.target_temperature = None + self.aux_heating = None + + @property + def _body(self): + power = 0x01 if self.power else 0x00 + mode = self.mode + target_temperature = ( + 0xFF + if self.target_temperature is None + else (int(self.target_temperature) & 0xFF) + ) + aux_heating = ( + 0xFF if self.aux_heating is None else (0x01 if self.aux_heating else 0x00) + ) + return bytearray([power, mode, target_temperature, aux_heating]) + + +class CFMessageBody(MessageBody): + def __init__(self, body, data_offset=0): + super().__init__(body) + self.power = (body[data_offset + 0] & 0x01) > 0 + self.aux_heating = (body[data_offset + 0] & 0x02) > 0 + self.silent = (body[data_offset + 0] & 0x04) > 0 + self.mode = body[data_offset + 3] + self.target_temperature = body[data_offset + 4] + self.current_temperature = body[data_offset + 5] + if self.mode == 2: + self.max_temperature = body[data_offset + 8] + self.min_temperature = body[data_offset + 9] + elif self.mode == 3: + self.max_temperature = body[data_offset + 6] + self.min_temperature = body[data_offset + 7] + else: + self.max_temperature = body[data_offset + 6] + self.min_temperature = body[data_offset + 9] + + +class MessageCFResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if ( + self.message_type in [MessageType.query, MessageType.set] + and self.body_type == 0x01 + ): + self.set_body(CFMessageBody(super().body, data_offset=1)) + elif self.message_type in [MessageType.notify1, MessageType.notify2]: + self.set_body(CFMessageBody(super().body, data_offset=0)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/da/device.py b/custom_components/midea_ac_lan/midea/devices/da/device.py index 579e8827..e67ed58a 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/device.py +++ b/custom_components/midea_ac_lan/midea/devices/da/device.py @@ -1,171 +1,171 @@ -import logging - -from .message import MessageDAResponse, MessagePower, MessageQuery, MessageStart - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - start = "start" - washing_data = "washing_data" - program = "program" - progress = "progress" - time_remaining = "time_remaining" - wash_time = "wash_time" - soak_time = "soak_time" - dehydration_time = "dehydration_time" - dehydration_speed = "dehydration_speed" - error_code = "error_code" - rinse_count = "rinse_count" - rinse_level = "rinse_level" - wash_level = "wash_level" - wash_strength = "wash_strength" - softener = "softener" - detergent = "detergent" - - -class MideaDADevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xDA, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.start: False, - DeviceAttributes.error_code: None, - DeviceAttributes.washing_data: bytearray([]), - DeviceAttributes.program: None, - DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None, - DeviceAttributes.wash_time: None, - DeviceAttributes.soak_time: None, - DeviceAttributes.dehydration_time: None, - DeviceAttributes.dehydration_speed: None, - DeviceAttributes.rinse_count: None, - DeviceAttributes.rinse_level: None, - DeviceAttributes.wash_level: None, - DeviceAttributes.wash_strength: None, - DeviceAttributes.softener: None, - DeviceAttributes.detergent: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageDAResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - progress = ["Idle", "Spin", "Rinse", "Wash", "Weight", "Unknown", "Dry", "Soak"] - program = [ - "Standard", - "Fast", - "Blanket", - "Wool", - "embathe", - "Memory", - "Child", - "Down Jacket", - "Stir", - "Mute", - "Bucket Self Clean", - "Air Dry", - ] - speed = ["-", "Low", "Medium", "High"] - strength = ["-", "Week", "Medium", "Strong"] - detergent = [ - "No", - "Less", - "Medium", - "More", - "4", - "5", - "6", - "7", - "8", - "Insufficient", - ] - softener = [ - "No", - "Intelligent", - "Programed", - "3", - "4", - "5", - "6", - "7", - "8", - "Insufficient", - ] - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if status == DeviceAttributes.progress: - self._attributes[status] = progress[getattr(message, str(status))] - elif status == DeviceAttributes.program: - self._attributes[status] = program[getattr(message, str(status))] - elif status == DeviceAttributes.rinse_level: - temp_rinse_level = getattr(message, str(status)) - if temp_rinse_level == 15: - self._attributes[status] = "-" - else: - self._attributes[status] = temp_rinse_level - elif status == DeviceAttributes.dehydration_speed: - temp_speed = getattr(message, str(status)) - if temp_speed == 15: - self._attributes[status] = "-" - else: - self._attributes[status] = speed[temp_speed] - elif status == DeviceAttributes.detergent: - self._attributes[status] = detergent[getattr(message, str(status))] - elif status == DeviceAttributes.softener: - self._attributes[status] = softener[getattr(message, str(status))] - elif status == DeviceAttributes.wash_strength: - self._attributes[status] = strength[getattr(message, str(status))] - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - self.build_send(message) - elif attr == DeviceAttributes.start: - message = MessageStart(self._protocol_version) - message.start = value - message.washing_data = self._attributes[DeviceAttributes.washing_data] - self.build_send(message) - - -class MideaAppliance(MideaDADevice): - pass +import logging + +from .message import MessageDAResponse, MessagePower, MessageQuery, MessageStart + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + start = "start" + washing_data = "washing_data" + program = "program" + progress = "progress" + time_remaining = "time_remaining" + wash_time = "wash_time" + soak_time = "soak_time" + dehydration_time = "dehydration_time" + dehydration_speed = "dehydration_speed" + error_code = "error_code" + rinse_count = "rinse_count" + rinse_level = "rinse_level" + wash_level = "wash_level" + wash_strength = "wash_strength" + softener = "softener" + detergent = "detergent" + + +class MideaDADevice(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xDA, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.start: False, + DeviceAttributes.error_code: None, + DeviceAttributes.washing_data: bytearray([]), + DeviceAttributes.program: None, + DeviceAttributes.progress: "Unknown", + DeviceAttributes.time_remaining: None, + DeviceAttributes.wash_time: None, + DeviceAttributes.soak_time: None, + DeviceAttributes.dehydration_time: None, + DeviceAttributes.dehydration_speed: None, + DeviceAttributes.rinse_count: None, + DeviceAttributes.rinse_level: None, + DeviceAttributes.wash_level: None, + DeviceAttributes.wash_strength: None, + DeviceAttributes.softener: None, + DeviceAttributes.detergent: None, + }, + ) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageDAResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + progress = ["Idle", "Spin", "Rinse", "Wash", "Weight", "Unknown", "Dry", "Soak"] + program = [ + "Standard", + "Fast", + "Blanket", + "Wool", + "embathe", + "Memory", + "Child", + "Down Jacket", + "Stir", + "Mute", + "Bucket Self Clean", + "Air Dry", + ] + speed = ["-", "Low", "Medium", "High"] + strength = ["-", "Week", "Medium", "Strong"] + detergent = [ + "No", + "Less", + "Medium", + "More", + "4", + "5", + "6", + "7", + "8", + "Insufficient", + ] + softener = [ + "No", + "Intelligent", + "Programed", + "3", + "4", + "5", + "6", + "7", + "8", + "Insufficient", + ] + for status in self._attributes.keys(): + if hasattr(message, str(status)): + if status == DeviceAttributes.progress: + self._attributes[status] = progress[getattr(message, str(status))] + elif status == DeviceAttributes.program: + self._attributes[status] = program[getattr(message, str(status))] + elif status == DeviceAttributes.rinse_level: + temp_rinse_level = getattr(message, str(status)) + if temp_rinse_level == 15: + self._attributes[status] = "-" + else: + self._attributes[status] = temp_rinse_level + elif status == DeviceAttributes.dehydration_speed: + temp_speed = getattr(message, str(status)) + if temp_speed == 15: + self._attributes[status] = "-" + else: + self._attributes[status] = speed[temp_speed] + elif status == DeviceAttributes.detergent: + self._attributes[status] = detergent[getattr(message, str(status))] + elif status == DeviceAttributes.softener: + self._attributes[status] = softener[getattr(message, str(status))] + elif status == DeviceAttributes.wash_strength: + self._attributes[status] = strength[getattr(message, str(status))] + else: + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = self._attributes[status] + return new_status + + def set_attribute(self, attr, value): + if attr == DeviceAttributes.power: + message = MessagePower(self._protocol_version) + message.power = value + self.build_send(message) + elif attr == DeviceAttributes.start: + message = MessageStart(self._protocol_version) + message.start = value + message.washing_data = self._attributes[DeviceAttributes.washing_data] + self.build_send(message) + + +class MideaAppliance(MideaDADevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/da/message.py b/custom_components/midea_ac_lan/midea/devices/da/message.py index 89064e77..5578d46b 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/message.py +++ b/custom_components/midea_ac_lan/midea/devices/da/message.py @@ -1,101 +1,101 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageDABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xDA, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageDABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x03, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessagePower(MessageDABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - return bytearray([power, 0xFF]) - - -class MessageStart(MessageDABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.start = False - self.washing_data = bytearray([]) - - @property - def _body(self): - if self.start: - return bytearray([0xFF, 0x01]) + self.washing_data - else: - # Stop - return bytearray([0xFF, 0x00]) - - -class DAGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = body[1] > 0 - self.start = True if body[2] in [2, 6] else False - self.error_code = body[24] - self.program = body[4] - self.wash_time = body[9] - self.soak_time = body[12] - self.dehydration_time = (body[10] & 0xF0) >> 4 - self.dehydration_speed = (body[6] & 0xF0) >> 4 - self.rinse_count = body[10] & 0xF - self.rinse_level = (body[5] & 0xF0) >> 4 - self.wash_level = body[5] & 0xF - self.wash_strength = body[6] & 0xF - self.softener = (body[8] & 0xF0) >> 4 - self.detergent = body[8] & 0x0F - self.washing_data = body[3:15] - self.progress = 0 - for i in range(1, 7): - if (body[16] & (1 << i)) > 0: - self.progress = i - break - if self.power: - self.time_remaining = body[17] + body[18] * 60 - else: - self.time_remaining = None - - -class MessageDAResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or ( - self.message_type == MessageType.notify1 and self.body_type == 0x04 - ): - self.set_body(DAGeneralMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageDABase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xDA, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageDABase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x03, + ) + + @property + def _body(self): + return bytearray([]) + + +class MessagePower(MessageDABase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x02, + ) + self.power = False + + @property + def _body(self): + power = 0x01 if self.power else 0x00 + return bytearray([power, 0xFF]) + + +class MessageStart(MessageDABase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x02, + ) + self.start = False + self.washing_data = bytearray([]) + + @property + def _body(self): + if self.start: + return bytearray([0xFF, 0x01]) + self.washing_data + else: + # Stop + return bytearray([0xFF, 0x00]) + + +class DAGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = body[1] > 0 + self.start = True if body[2] in [2, 6] else False + self.error_code = body[24] + self.program = body[4] + self.wash_time = body[9] + self.soak_time = body[12] + self.dehydration_time = (body[10] & 0xF0) >> 4 + self.dehydration_speed = (body[6] & 0xF0) >> 4 + self.rinse_count = body[10] & 0xF + self.rinse_level = (body[5] & 0xF0) >> 4 + self.wash_level = body[5] & 0xF + self.wash_strength = body[6] & 0xF + self.softener = (body[8] & 0xF0) >> 4 + self.detergent = body[8] & 0x0F + self.washing_data = body[3:15] + self.progress = 0 + for i in range(1, 7): + if (body[16] & (1 << i)) > 0: + self.progress = i + break + if self.power: + self.time_remaining = body[17] + body[18] * 60 + else: + self.time_remaining = None + + +class MessageDAResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.message_type in [MessageType.query, MessageType.set] or ( + self.message_type == MessageType.notify1 and self.body_type == 0x04 + ): + self.set_body(DAGeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/db/device.py b/custom_components/midea_ac_lan/midea/devices/db/device.py index a18e253a..bdd69954 100644 --- a/custom_components/midea_ac_lan/midea/devices/db/device.py +++ b/custom_components/midea_ac_lan/midea/devices/db/device.py @@ -1,97 +1,97 @@ -import logging - -from .message import MessageDBResponse, MessagePower, MessageQuery, MessageStart - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - start = "start" - washing_data = "washing_data" - progress = "progress" - time_remaining = "time_remaining" - - -class MideaDBDevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xDB, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.start: False, - DeviceAttributes.washing_data: bytearray([]), - DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageDBResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - progress = [ - "Idle", - "Spin", - "Rinse", - "Wash", - "Pre-wash", - "Dry", - "Weight", - "Hi-speed Spin", - "Unknown", - ] - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if status == DeviceAttributes.progress: - self._attributes[status] = progress[getattr(message, str(status))] - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - self.build_send(message) - elif attr == DeviceAttributes.start: - message = MessageStart(self._protocol_version) - message.start = value - message.washing_data = self._attributes[DeviceAttributes.washing_data] - self.build_send(message) - - -class MideaAppliance(MideaDBDevice): - pass +import logging + +from .message import MessageDBResponse, MessagePower, MessageQuery, MessageStart + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + start = "start" + washing_data = "washing_data" + progress = "progress" + time_remaining = "time_remaining" + + +class MideaDBDevice(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xDB, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.start: False, + DeviceAttributes.washing_data: bytearray([]), + DeviceAttributes.progress: "Unknown", + DeviceAttributes.time_remaining: None, + }, + ) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageDBResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + progress = [ + "Idle", + "Spin", + "Rinse", + "Wash", + "Pre-wash", + "Dry", + "Weight", + "Hi-speed Spin", + "Unknown", + ] + for status in self._attributes.keys(): + if hasattr(message, str(status)): + if status == DeviceAttributes.progress: + self._attributes[status] = progress[getattr(message, str(status))] + else: + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = self._attributes[status] + return new_status + + def set_attribute(self, attr, value): + if attr == DeviceAttributes.power: + message = MessagePower(self._protocol_version) + message.power = value + self.build_send(message) + elif attr == DeviceAttributes.start: + message = MessageStart(self._protocol_version) + message.start = value + message.washing_data = self._attributes[DeviceAttributes.washing_data] + self.build_send(message) + + +class MideaAppliance(MideaDBDevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/db/message.py b/custom_components/midea_ac_lan/midea/devices/db/message.py index 63e918b3..7a70ca55 100644 --- a/custom_components/midea_ac_lan/midea/devices/db/message.py +++ b/custom_components/midea_ac_lan/midea/devices/db/message.py @@ -1,113 +1,113 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageDBBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xDB, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageDBBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x03, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessagePower(MessageDBBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - return bytearray( - [ - power, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - ] - ) - - -class MessageStart(MessageDBBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.start = False - self.washing_data = bytearray([]) - - @property - def _body(self): - if self.start: # Pause - return bytearray([0xFF, 0x01]) + self.washing_data - else: - # Pause - return bytearray([0xFF, 0x00]) - - -class DBGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = body[1] > 0 - self.start = True if body[2] in [2, 6] else False - self.washing_data = body[3:16] - self.progress = 0 - for i in range(0, 7): - if (body[16] & (1 << i)) > 0: - self.progress = i + 1 - break - if self.power: - self.time_remaining = body[17] + (body[18] << 8) - else: - self.time_remaining = None - - -class MessageDBResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or ( - self.message_type == MessageType.notify1 and self.body_type == 0x04 - ): - self.set_body(DBGeneralMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageDBBase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xDB, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageDBBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x03, + ) + + @property + def _body(self): + return bytearray([]) + + +class MessagePower(MessageDBBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x02, + ) + self.power = False + + @property + def _body(self): + power = 0x01 if self.power else 0x00 + return bytearray( + [ + power, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + ] + ) + + +class MessageStart(MessageDBBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x02, + ) + self.start = False + self.washing_data = bytearray([]) + + @property + def _body(self): + if self.start: # Pause + return bytearray([0xFF, 0x01]) + self.washing_data + else: + # Pause + return bytearray([0xFF, 0x00]) + + +class DBGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = body[1] > 0 + self.start = True if body[2] in [2, 6] else False + self.washing_data = body[3:16] + self.progress = 0 + for i in range(0, 7): + if (body[16] & (1 << i)) > 0: + self.progress = i + 1 + break + if self.power: + self.time_remaining = body[17] + (body[18] << 8) + else: + self.time_remaining = None + + +class MessageDBResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.message_type in [MessageType.query, MessageType.set] or ( + self.message_type == MessageType.notify1 and self.body_type == 0x04 + ): + self.set_body(DBGeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/dc/device.py b/custom_components/midea_ac_lan/midea/devices/dc/device.py index 302f514e..ad18390a 100644 --- a/custom_components/midea_ac_lan/midea/devices/dc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/dc/device.py @@ -1,96 +1,96 @@ -import logging - -from .message import MessageDCResponse, MessagePower, MessageQuery, MessageStart - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - start = "start" - washing_data = "washing_data" - progress = "progress" - time_remaining = "time_remaining" - - -class MideaDADevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xDC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.start: False, - DeviceAttributes.washing_data: bytearray([]), - DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageDCResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - progress = [ - "Prog0", - "Prog1", - "Prog2", - "Prog3", - "Prog4", - "Prog5", - "Prog6", - "Prog7", - ] - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if status == DeviceAttributes.progress: - self._attributes[status] = progress[getattr(message, str(status))] - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - self.build_send(message) - elif attr == DeviceAttributes.start: - message = MessageStart(self._protocol_version) - message.start = value - message.washing_data = self._attributes[DeviceAttributes.washing_data] - self.build_send(message) - - -class MideaAppliance(MideaDADevice): - pass +import logging + +from .message import MessageDCResponse, MessagePower, MessageQuery, MessageStart + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + start = "start" + washing_data = "washing_data" + progress = "progress" + time_remaining = "time_remaining" + + +class MideaDADevice(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xDC, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.start: False, + DeviceAttributes.washing_data: bytearray([]), + DeviceAttributes.progress: "Unknown", + DeviceAttributes.time_remaining: None, + }, + ) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageDCResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + progress = [ + "Prog0", + "Prog1", + "Prog2", + "Prog3", + "Prog4", + "Prog5", + "Prog6", + "Prog7", + ] + for status in self._attributes.keys(): + if hasattr(message, str(status)): + if status == DeviceAttributes.progress: + self._attributes[status] = progress[getattr(message, str(status))] + else: + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = self._attributes[status] + return new_status + + def set_attribute(self, attr, value): + if attr == DeviceAttributes.power: + message = MessagePower(self._protocol_version) + message.power = value + self.build_send(message) + elif attr == DeviceAttributes.start: + message = MessageStart(self._protocol_version) + message.start = value + message.washing_data = self._attributes[DeviceAttributes.washing_data] + self.build_send(message) + + +class MideaAppliance(MideaDADevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/dc/message.py b/custom_components/midea_ac_lan/midea/devices/dc/message.py index 03c45141..3be5d3dd 100644 --- a/custom_components/midea_ac_lan/midea/devices/dc/message.py +++ b/custom_components/midea_ac_lan/midea/devices/dc/message.py @@ -1,89 +1,89 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageDCBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xDC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageDCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x03, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessagePower(MessageDCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - return bytearray([power, 0xFF]) - - -class MessageStart(MessageDCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.start = False - self.washing_data = bytearray([]) - - @property - def _body(self): - if self.start: - return bytearray([0xFF, 0x01]) + self.washing_data - else: - # Stop - return bytearray([0xFF, 0x00]) - - -class DCGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = body[1] > 0 - self.start = True if body[2] in [2, 6] else False - self.washing_data = body[3:15] - self.progress = 0 - for i in range(0, 7): - if (body[16] & (1 << i)) > 0: - self.progress = i + 1 - break - if self.power: - self.time_remaining = body[17] + body[18] * 60 - else: - self.time_remaining = None - - -class MessageDCResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or ( - self.message_type == MessageType.notify1 and self.body_type == 0x04 - ): - self.set_body(DCGeneralMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageDCBase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xDC, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageDCBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x03, + ) + + @property + def _body(self): + return bytearray([]) + + +class MessagePower(MessageDCBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x02, + ) + self.power = False + + @property + def _body(self): + power = 0x01 if self.power else 0x00 + return bytearray([power, 0xFF]) + + +class MessageStart(MessageDCBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x02, + ) + self.start = False + self.washing_data = bytearray([]) + + @property + def _body(self): + if self.start: + return bytearray([0xFF, 0x01]) + self.washing_data + else: + # Stop + return bytearray([0xFF, 0x00]) + + +class DCGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = body[1] > 0 + self.start = True if body[2] in [2, 6] else False + self.washing_data = body[3:15] + self.progress = 0 + for i in range(0, 7): + if (body[16] & (1 << i)) > 0: + self.progress = i + 1 + break + if self.power: + self.time_remaining = body[17] + body[18] * 60 + else: + self.time_remaining = None + + +class MessageDCResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.message_type in [MessageType.query, MessageType.set] or ( + self.message_type == MessageType.notify1 and self.body_type == 0x04 + ): + self.set_body(DCGeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e1/device.py b/custom_components/midea_ac_lan/midea/devices/e1/device.py index 74c6a390..62510255 100644 --- a/custom_components/midea_ac_lan/midea/devices/e1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e1/device.py @@ -1,172 +1,172 @@ -import logging - -from .message import ( - MessageE1Response, - MessageLock, - MessagePower, - MessageQuery, - MessageStorage, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - status = "status" - mode = "mode" - additional = "additional" - door = "door" - rinse_aid = "rinse_aid" - salt = "salt" - child_lock = "child_lock" - uv = "uv" - dry = "dry" - dry_status = "dry_status" - storage = "storage" - storage_status = "storage_status" - time_remaining = "time_remaining" - progress = "progress" - storage_remaining = "storage_remaining" - temperature = "temperature" - humidity = "humidity" - waterswitch = "waterswitch" - water_lack = "water_lack" - error_code = "error_code" - softwater = "softwater" - wrong_operation = "wrong_operation" - bright = "bright" - - -class MideaE1Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xE1, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.status: None, - DeviceAttributes.mode: 0, - DeviceAttributes.additional: 0, - DeviceAttributes.uv: False, - DeviceAttributes.dry: False, - DeviceAttributes.dry_status: False, - DeviceAttributes.door: False, - DeviceAttributes.rinse_aid: False, - DeviceAttributes.salt: False, - DeviceAttributes.child_lock: False, - DeviceAttributes.storage: False, - DeviceAttributes.storage_status: False, - DeviceAttributes.time_remaining: None, - DeviceAttributes.progress: None, - DeviceAttributes.storage_remaining: None, - DeviceAttributes.temperature: None, - DeviceAttributes.humidity: None, - DeviceAttributes.waterswitch: False, - DeviceAttributes.water_lack: False, - DeviceAttributes.error_code: None, - DeviceAttributes.softwater: 0, - DeviceAttributes.wrong_operation: None, - DeviceAttributes.bright: 0, - }, - ) - self._modes = { - 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR - 0x1: "Auto", # BYTE_MODE_AUTO_WASH - 0x2: "Heavy", # BYTE_MODE_STRONG_WASH - 0x3: "Normal", # BYTE_MODE_STANDARD_WASH - 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH - 0x5: "Delicate", # BYTE_MODE_GLASS_WASH - 0x6: "Hour", # BYTE_MODE_HOUR_WASH - 0x7: "Quick", # BYTE_MODE_FAST_WASH - 0x8: "Rinse", # BYTE_MODE_SOAK_WASH - 0x9: "90min", # BYTE_MODE_90MIN_WASH - 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN - 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH - 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE - 0xD: "Germ", # BYTE_MODE_GERM ??? - 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH - 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM - 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH - 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH - 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH - 0x14: "Less Wash", # BYTE_MODE_LESS_WASH - 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH - 0x19: "Cloud Wash", # BYTE_MODE_CLOUD_WASH - } - self._status = ["Off", "Idle", "Delay", "Running", "Error"] - self._progress = ["Idle", "Pre-wash", "Wash", "Rinse", "Dry", "Complete"] - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageE1Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if status == DeviceAttributes.status: - v = getattr(message, str(status)) - if v < len(self._status): - self._attributes[status] = self._status[v] - else: - self._attributes[status] = None - elif status == DeviceAttributes.progress: - v = getattr(message, str(status)) - if v < len(self._progress): - self._attributes[status] = self._progress[v] - else: - self._attributes[status] = None - elif status == DeviceAttributes.mode: - v = getattr(message, str(status)) - self._attributes[status] = self._modes[v] - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - self.build_send(message) - elif attr == DeviceAttributes.child_lock: - message = MessageLock(self._protocol_version) - message.lock = value - self.build_send(message) - elif attr == DeviceAttributes.storage: - message = MessageStorage(self._protocol_version) - message.storage = value - self.build_send(message) - - -class MideaAppliance(MideaE1Device): - pass +import logging + +from .message import ( + MessageE1Response, + MessageLock, + MessagePower, + MessageQuery, + MessageStorage, +) + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + status = "status" + mode = "mode" + additional = "additional" + door = "door" + rinse_aid = "rinse_aid" + salt = "salt" + child_lock = "child_lock" + uv = "uv" + dry = "dry" + dry_status = "dry_status" + storage = "storage" + storage_status = "storage_status" + time_remaining = "time_remaining" + progress = "progress" + storage_remaining = "storage_remaining" + temperature = "temperature" + humidity = "humidity" + waterswitch = "waterswitch" + water_lack = "water_lack" + error_code = "error_code" + softwater = "softwater" + wrong_operation = "wrong_operation" + bright = "bright" + + +class MideaE1Device(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xE1, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.status: None, + DeviceAttributes.mode: 0, + DeviceAttributes.additional: 0, + DeviceAttributes.uv: False, + DeviceAttributes.dry: False, + DeviceAttributes.dry_status: False, + DeviceAttributes.door: False, + DeviceAttributes.rinse_aid: False, + DeviceAttributes.salt: False, + DeviceAttributes.child_lock: False, + DeviceAttributes.storage: False, + DeviceAttributes.storage_status: False, + DeviceAttributes.time_remaining: None, + DeviceAttributes.progress: None, + DeviceAttributes.storage_remaining: None, + DeviceAttributes.temperature: None, + DeviceAttributes.humidity: None, + DeviceAttributes.waterswitch: False, + DeviceAttributes.water_lack: False, + DeviceAttributes.error_code: None, + DeviceAttributes.softwater: 0, + DeviceAttributes.wrong_operation: None, + DeviceAttributes.bright: 0, + }, + ) + self._modes = { + 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR + 0x1: "Auto", # BYTE_MODE_AUTO_WASH + 0x2: "Heavy", # BYTE_MODE_STRONG_WASH + 0x3: "Normal", # BYTE_MODE_STANDARD_WASH + 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH + 0x5: "Delicate", # BYTE_MODE_GLASS_WASH + 0x6: "Hour", # BYTE_MODE_HOUR_WASH + 0x7: "Quick", # BYTE_MODE_FAST_WASH + 0x8: "Rinse", # BYTE_MODE_SOAK_WASH + 0x9: "90min", # BYTE_MODE_90MIN_WASH + 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN + 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH + 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE + 0xD: "Germ", # BYTE_MODE_GERM ??? + 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH + 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM + 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH + 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH + 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH + 0x14: "Less Wash", # BYTE_MODE_LESS_WASH + 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH + 0x19: "Cloud Wash", # BYTE_MODE_CLOUD_WASH + } + self._status = ["Off", "Idle", "Delay", "Running", "Error"] + self._progress = ["Idle", "Pre-wash", "Wash", "Rinse", "Dry", "Complete"] + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageE1Response(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + if status == DeviceAttributes.status: + v = getattr(message, str(status)) + if v < len(self._status): + self._attributes[status] = self._status[v] + else: + self._attributes[status] = None + elif status == DeviceAttributes.progress: + v = getattr(message, str(status)) + if v < len(self._progress): + self._attributes[status] = self._progress[v] + else: + self._attributes[status] = None + elif status == DeviceAttributes.mode: + v = getattr(message, str(status)) + self._attributes[status] = self._modes[v] + else: + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = self._attributes[status] + return new_status + + def set_attribute(self, attr, value): + if attr == DeviceAttributes.power: + message = MessagePower(self._protocol_version) + message.power = value + self.build_send(message) + elif attr == DeviceAttributes.child_lock: + message = MessageLock(self._protocol_version) + message.lock = value + self.build_send(message) + elif attr == DeviceAttributes.storage: + message = MessageStorage(self._protocol_version) + message.storage = value + self.build_send(message) + + +class MideaAppliance(MideaE1Device): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/e1/message.py b/custom_components/midea_ac_lan/midea/devices/e1/message.py index f74ee45d..530572e8 100644 --- a/custom_components/midea_ac_lan/midea/devices/e1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e1/message.py @@ -1,122 +1,122 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageE1Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xE1, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessagePower(MessageE1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x08, - ) - self.power = False - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - return bytearray([power, 0x00, 0x00, 0x00]) - - -class MessageLock(MessageE1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x83, - ) - self.lock = False - - @property - def _body(self): - lock = 0x03 if self.lock else 0x04 - return bytearray([lock]) + bytearray([0x00] * 36) - - -class MessageStorage(MessageE1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x81, - ) - self.storage = False - - @property - def _body(self): - storage = 0x01 if self.storage else 0x00 - return ( - bytearray([0x00, 0x00, 0x00, storage]) - + bytearray([0xFF] * 6) - + bytearray([0x00] * 27) - ) - - -class MessageQuery(MessageE1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x00, - ) - - @property - def _body(self): - return bytearray([]) - - -class E1GeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = body[1] > 0 - self.status = body[1] - self.mode = body[2] - self.additional = body[3] - self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close - self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage - self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage - start_pause = (body[5] & 0x08) > 0 - if start_pause: - self.start = True - elif self.status in [2, 3]: - self.start = False - self.child_lock = (body[5] & 0x10) > 0 - self.uv = (body[4] & 0x2) > 0 - self.dry = (body[4] & 0x10) > 0 - self.dry_status = (body[4] & 0x20) > 0 - self.storage = (body[5] & 0x20) > 0 - self.storage_status = (body[5] & 0x40) > 0 - self.time_remaining = body[6] - self.progress = body[9] - self.storage_remaining = body[18] if len(body) > 18 else False - self.temperature = body[11] - self.humidity = body[33] if len(body) > 33 else None - self.waterswitch = (body[4] & 0x4) > 0 - self.water_lack = (body[5] & 0x80) > 0 - self.error_code = body[10] - self.softwater = body[13] - self.wrong_operation = body[16] - self.bright = body[24] if len(body) > 24 else None - - -class MessageE1Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0 - ): - self.set_body(E1GeneralMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageE1Base(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xE1, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessagePower(MessageE1Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x08, + ) + self.power = False + + @property + def _body(self): + power = 0x01 if self.power else 0x00 + return bytearray([power, 0x00, 0x00, 0x00]) + + +class MessageLock(MessageE1Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x83, + ) + self.lock = False + + @property + def _body(self): + lock = 0x03 if self.lock else 0x04 + return bytearray([lock]) + bytearray([0x00] * 36) + + +class MessageStorage(MessageE1Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x81, + ) + self.storage = False + + @property + def _body(self): + storage = 0x01 if self.storage else 0x00 + return ( + bytearray([0x00, 0x00, 0x00, storage]) + + bytearray([0xFF] * 6) + + bytearray([0x00] * 27) + ) + + +class MessageQuery(MessageE1Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x00, + ) + + @property + def _body(self): + return bytearray([]) + + +class E1GeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = body[1] > 0 + self.status = body[1] + self.mode = body[2] + self.additional = body[3] + self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close + self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage + self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage + start_pause = (body[5] & 0x08) > 0 + if start_pause: + self.start = True + elif self.status in [2, 3]: + self.start = False + self.child_lock = (body[5] & 0x10) > 0 + self.uv = (body[4] & 0x2) > 0 + self.dry = (body[4] & 0x10) > 0 + self.dry_status = (body[4] & 0x20) > 0 + self.storage = (body[5] & 0x20) > 0 + self.storage_status = (body[5] & 0x40) > 0 + self.time_remaining = body[6] + self.progress = body[9] + self.storage_remaining = body[18] if len(body) > 18 else False + self.temperature = body[11] + self.humidity = body[33] if len(body) > 33 else None + self.waterswitch = (body[4] & 0x4) > 0 + self.water_lack = (body[5] & 0x80) > 0 + self.error_code = body[10] + self.softwater = body[13] + self.wrong_operation = body[16] + self.bright = body[24] if len(body) > 24 else None + + +class MessageE1Response(MessageResponse): + def __init__(self, message): + super().__init__(message) + if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or ( + self.message_type in [MessageType.query, MessageType.notify1] + and self.body_type == 0 + ): + self.set_body(E1GeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e2/device.py b/custom_components/midea_ac_lan/midea/devices/e2/device.py index ae0cf739..9b919e7c 100644 --- a/custom_components/midea_ac_lan/midea/devices/e2/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e2/device.py @@ -1,141 +1,141 @@ -import json -import logging - -from .message import ( - MessageE2Response, - MessageNewProtocolSet, - MessagePower, - MessageQuery, - MessageSet, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - heating = "heating" - keep_warm = "keep_warm" - protection = "protection" - current_temperature = "current_temperature" - target_temperature = "target_temperature" - whole_tank_heating = "whole_tank_heating" - variable_heating = "variable_heating" - heating_time_remaining = "heating_time_remaining" - water_consumption = "water_consumption" - heating_power = "heating_power" - - -class MideaE2Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xE2, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.heating: False, - DeviceAttributes.keep_warm: False, - DeviceAttributes.protection: False, - DeviceAttributes.current_temperature: None, - DeviceAttributes.target_temperature: 40, - DeviceAttributes.whole_tank_heating: False, - DeviceAttributes.variable_heating: False, - DeviceAttributes.heating_time_remaining: 0, - DeviceAttributes.water_consumption: None, - DeviceAttributes.heating_power: None, - }, - ) - self._default_old_protocol = "auto" - self._old_protocol = self._default_old_protocol - self.set_customize(customize) - - def old_protocol(self): - return self.subtype <= 82 or self.subtype == 85 or self.subtype == 36353 - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageE2Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.protection = self._attributes[DeviceAttributes.protection] - message.whole_tank_heating = self._attributes[ - DeviceAttributes.whole_tank_heating - ] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - message.variable_heating = self._attributes[DeviceAttributes.variable_heating] - return message - - def set_attribute(self, attr, value): - if attr not in [ - DeviceAttributes.heating, - DeviceAttributes.keep_warm, - DeviceAttributes.current_temperature, - ]: - if self._old_protocol is not None and self._old_protocol != "auto": - old_protocol = self._old_protocol - else: - old_protocol = self.old_protocol() - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - elif old_protocol: - message = self.make_message_set() - setattr(message, str(attr), value) - else: - message = MessageNewProtocolSet(self._protocol_version) - setattr(message, str(attr), value) - self.build_send(message) - - def set_customize(self, customize): - self._old_protocol = self._default_old_protocol - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "old_protocol" in params: - self._old_protocol = params.get("old_protocol") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"old_protocol": self._old_protocol}) - - -class MideaAppliance(MideaE2Device): - pass +import json +import logging + +from .message import ( + MessageE2Response, + MessageNewProtocolSet, + MessagePower, + MessageQuery, + MessageSet, +) + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + heating = "heating" + keep_warm = "keep_warm" + protection = "protection" + current_temperature = "current_temperature" + target_temperature = "target_temperature" + whole_tank_heating = "whole_tank_heating" + variable_heating = "variable_heating" + heating_time_remaining = "heating_time_remaining" + water_consumption = "water_consumption" + heating_power = "heating_power" + + +class MideaE2Device(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xE2, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.heating: False, + DeviceAttributes.keep_warm: False, + DeviceAttributes.protection: False, + DeviceAttributes.current_temperature: None, + DeviceAttributes.target_temperature: 40, + DeviceAttributes.whole_tank_heating: False, + DeviceAttributes.variable_heating: False, + DeviceAttributes.heating_time_remaining: 0, + DeviceAttributes.water_consumption: None, + DeviceAttributes.heating_power: None, + }, + ) + self._default_old_protocol = "auto" + self._old_protocol = self._default_old_protocol + self.set_customize(customize) + + def old_protocol(self): + return self.subtype <= 82 or self.subtype == 85 or self.subtype == 36353 + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageE2Response(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = getattr(message, str(status)) + return new_status + + def make_message_set(self): + message = MessageSet(self._protocol_version) + message.protection = self._attributes[DeviceAttributes.protection] + message.whole_tank_heating = self._attributes[ + DeviceAttributes.whole_tank_heating + ] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] + message.variable_heating = self._attributes[DeviceAttributes.variable_heating] + return message + + def set_attribute(self, attr, value): + if attr not in [ + DeviceAttributes.heating, + DeviceAttributes.keep_warm, + DeviceAttributes.current_temperature, + ]: + if self._old_protocol is not None and self._old_protocol != "auto": + old_protocol = self._old_protocol + else: + old_protocol = self.old_protocol() + if attr == DeviceAttributes.power: + message = MessagePower(self._protocol_version) + message.power = value + elif old_protocol: + message = self.make_message_set() + setattr(message, str(attr), value) + else: + message = MessageNewProtocolSet(self._protocol_version) + setattr(message, str(attr), value) + self.build_send(message) + + def set_customize(self, customize): + self._old_protocol = self._default_old_protocol + if customize and len(customize) > 0: + try: + params = json.loads(customize) + if params and "old_protocol" in params: + self._old_protocol = params.get("old_protocol") + except Exception as e: + _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") + self.update_all({"old_protocol": self._old_protocol}) + + +class MideaAppliance(MideaE2Device): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/e2/message.py b/custom_components/midea_ac_lan/midea/devices/e2/message.py index e07c8669..a70c6cc9 100644 --- a/custom_components/midea_ac_lan/midea/devices/e2/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e2/message.py @@ -1,150 +1,150 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageE2Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xE2, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageE2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessagePower(MessageE2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - if self.power: - self.body_type = 0x01 - else: - self.body_type = 0x02 - return bytearray([0x01]) - - -class MessageNewProtocolSet(MessageE2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x14, - ) - self.target_temperature = None - self.variable_heating = None - self.whole_tank_heating = None - - @property - def _body(self): - byte1 = 0x00 - byte2 = 0x00 - if self.target_temperature is not None: - byte1 = 0x07 - byte2 = int(self.target_temperature) & 0xFF - elif self.whole_tank_heating is not None: - byte1 = 0x04 - byte2 = 0x02 if self.whole_tank_heating else 0x01 - elif self.variable_heating is not None: - byte1 = 0x10 - byte2 = 0x01 if self.variable_heating else 0x00 - return bytearray([byte1, byte2]) - - -class MessageSet(MessageE2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x04, - ) - self.target_temperature = 0 - self.variable_heating = False - self.whole_tank_heating = False - self.protection = False - - @property - def _body(self): - # Byte 4 whole_tank_heating, protection - protection = 0x04 if self.protection else 0x00 - whole_tank_heating = 0x02 if self.whole_tank_heating else 0x01 - # Byte 5 target_temperature - target_temperature = self.target_temperature & 0xFF - # Byte 9 variable_heating - variable_heating = 0x10 if self.variable_heating else 0x00 - return bytearray( - [ - 0x01, - 0x00, - 0x80, - whole_tank_heating | protection, - target_temperature, - 0x00, - 0x00, - 0x00, - variable_heating, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class E2GeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[2] & 0x01) > 0 - self.heating = (body[2] & 0x04) > 0 - self.keep_warm = (body[2] & 0x08) > 0 - self.variable_heating = (body[2] & 0x80) > 0 - self.current_temperature = body[4] - self.whole_tank_heating = (body[7] & 0x08) > 0 - self.heating_time_remaining = body[9] * 60 + body[10] - self.target_temperature = body[11] - self.protection = ((body[22] & 0x02) > 0) if len(body) > 22 else False - if len(body) > 25: - self.water_consumption = body[24] + (body[25] << 8) - if len(body) > 34: - self.heating_power = body[34] * 100 - - -class MessageE2Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0x01 - ) or ( - self.message_type == MessageType.set - and self.body_type in [0x01, 0x02, 0x04, 0x14] - ): - self.set_body(E2GeneralMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageE2Base(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xE2, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageE2Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x01, + ) + + @property + def _body(self): + return bytearray([0x01]) + + +class MessagePower(MessageE2Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x02, + ) + self.power = False + + @property + def _body(self): + if self.power: + self.body_type = 0x01 + else: + self.body_type = 0x02 + return bytearray([0x01]) + + +class MessageNewProtocolSet(MessageE2Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x14, + ) + self.target_temperature = None + self.variable_heating = None + self.whole_tank_heating = None + + @property + def _body(self): + byte1 = 0x00 + byte2 = 0x00 + if self.target_temperature is not None: + byte1 = 0x07 + byte2 = int(self.target_temperature) & 0xFF + elif self.whole_tank_heating is not None: + byte1 = 0x04 + byte2 = 0x02 if self.whole_tank_heating else 0x01 + elif self.variable_heating is not None: + byte1 = 0x10 + byte2 = 0x01 if self.variable_heating else 0x00 + return bytearray([byte1, byte2]) + + +class MessageSet(MessageE2Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x04, + ) + self.target_temperature = 0 + self.variable_heating = False + self.whole_tank_heating = False + self.protection = False + + @property + def _body(self): + # Byte 4 whole_tank_heating, protection + protection = 0x04 if self.protection else 0x00 + whole_tank_heating = 0x02 if self.whole_tank_heating else 0x01 + # Byte 5 target_temperature + target_temperature = self.target_temperature & 0xFF + # Byte 9 variable_heating + variable_heating = 0x10 if self.variable_heating else 0x00 + return bytearray( + [ + 0x01, + 0x00, + 0x80, + whole_tank_heating | protection, + target_temperature, + 0x00, + 0x00, + 0x00, + variable_heating, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class E2GeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[2] & 0x01) > 0 + self.heating = (body[2] & 0x04) > 0 + self.keep_warm = (body[2] & 0x08) > 0 + self.variable_heating = (body[2] & 0x80) > 0 + self.current_temperature = body[4] + self.whole_tank_heating = (body[7] & 0x08) > 0 + self.heating_time_remaining = body[9] * 60 + body[10] + self.target_temperature = body[11] + self.protection = ((body[22] & 0x02) > 0) if len(body) > 22 else False + if len(body) > 25: + self.water_consumption = body[24] + (body[25] << 8) + if len(body) > 34: + self.heating_power = body[34] * 100 + + +class MessageE2Response(MessageResponse): + def __init__(self, message): + super().__init__(message) + if ( + self.message_type in [MessageType.query, MessageType.notify1] + and self.body_type == 0x01 + ) or ( + self.message_type == MessageType.set + and self.body_type in [0x01, 0x02, 0x04, 0x14] + ): + self.set_body(E2GeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e3/device.py b/custom_components/midea_ac_lan/midea/devices/e3/device.py index 27cf42da..999a6276 100644 --- a/custom_components/midea_ac_lan/midea/devices/e3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e3/device.py @@ -1,142 +1,142 @@ -import json -import logging - -from .message import ( - MessageE3Response, - MessageNewProtocolSet, - MessagePower, - MessageQuery, - MessageSet, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - burning_state = "burning_state" - zero_cold_water = "zero_cold_water" - protection = "protection" - zero_cold_pulse = "zero_cold_pulse" - smart_volume = "smart_volume" - current_temperature = "current_temperature" - target_temperature = "target_temperature" - - -class MideaE3Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xE3, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.burning_state: False, - DeviceAttributes.zero_cold_water: False, - DeviceAttributes.protection: False, - DeviceAttributes.zero_cold_pulse: False, - DeviceAttributes.smart_volume: False, - DeviceAttributes.current_temperature: None, - DeviceAttributes.target_temperature: 40, - }, - ) - self._old_subtypes = [32, 33, 34, 35, 36, 37, 40, 43, 48, 49, 80] - self._precision_halves = None - self._default_precision_halves = False - self.set_customize(customize) - - @property - def precision_halves(self): - return self._precision_halves - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageE3Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if self._precision_halves and status in [ - DeviceAttributes.current_temperature, - DeviceAttributes.target_temperature, - ]: - self._attributes[status] = getattr(message, str(status)) / 2 - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.zero_cold_water = self._attributes[DeviceAttributes.zero_cold_water] - message.protection = self._attributes[DeviceAttributes.protection] - message.zero_clod_pulse = self._attributes[DeviceAttributes.zero_cold_pulse] - message.smart_volume = self._attributes[DeviceAttributes.smart_volume] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - return message - - def set_attribute(self, attr, value): - if attr not in [ - DeviceAttributes.burning_state, - DeviceAttributes.current_temperature, - DeviceAttributes.protection, - ]: - if self._precision_halves and attr == DeviceAttributes.target_temperature: - value = int(value * 2) - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - elif self.subtype in self._old_subtypes: - message = self.make_message_set() - setattr(message, str(attr), value) - else: - message = MessageNewProtocolSet(self._protocol_version) - setattr(message, "key", str(attr)) - setattr(message, "value", value) - self.build_send(message) - - def set_customize(self, customize): - self._precision_halves = self._default_precision_halves - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "precision_halves" in params: - self._precision_halves = params.get("precision_halves") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"precision_halves": self._precision_halves}) - - -class MideaAppliance(MideaE3Device): - pass +import json +import logging + +from .message import ( + MessageE3Response, + MessageNewProtocolSet, + MessagePower, + MessageQuery, + MessageSet, +) + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + burning_state = "burning_state" + zero_cold_water = "zero_cold_water" + protection = "protection" + zero_cold_pulse = "zero_cold_pulse" + smart_volume = "smart_volume" + current_temperature = "current_temperature" + target_temperature = "target_temperature" + + +class MideaE3Device(MiedaDevice): + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xE3, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.burning_state: False, + DeviceAttributes.zero_cold_water: False, + DeviceAttributes.protection: False, + DeviceAttributes.zero_cold_pulse: False, + DeviceAttributes.smart_volume: False, + DeviceAttributes.current_temperature: None, + DeviceAttributes.target_temperature: 40, + }, + ) + self._old_subtypes = [32, 33, 34, 35, 36, 37, 40, 43, 48, 49, 80] + self._precision_halves = None + self._default_precision_halves = False + self.set_customize(customize) + + @property + def precision_halves(self): + return self._precision_halves + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageE3Response(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + if self._precision_halves and status in [ + DeviceAttributes.current_temperature, + DeviceAttributes.target_temperature, + ]: + self._attributes[status] = getattr(message, str(status)) / 2 + else: + self._attributes[status] = getattr(message, str(status)) + new_status[str(status)] = self._attributes[status] + + return new_status + + def make_message_set(self): + message = MessageSet(self._protocol_version) + message.zero_cold_water = self._attributes[DeviceAttributes.zero_cold_water] + message.protection = self._attributes[DeviceAttributes.protection] + message.zero_clod_pulse = self._attributes[DeviceAttributes.zero_cold_pulse] + message.smart_volume = self._attributes[DeviceAttributes.smart_volume] + message.target_temperature = self._attributes[ + DeviceAttributes.target_temperature + ] + return message + + def set_attribute(self, attr, value): + if attr not in [ + DeviceAttributes.burning_state, + DeviceAttributes.current_temperature, + DeviceAttributes.protection, + ]: + if self._precision_halves and attr == DeviceAttributes.target_temperature: + value = int(value * 2) + if attr == DeviceAttributes.power: + message = MessagePower(self._protocol_version) + message.power = value + elif self.subtype in self._old_subtypes: + message = self.make_message_set() + setattr(message, str(attr), value) + else: + message = MessageNewProtocolSet(self._protocol_version) + setattr(message, "key", str(attr)) + setattr(message, "value", value) + self.build_send(message) + + def set_customize(self, customize): + self._precision_halves = self._default_precision_halves + if customize and len(customize) > 0: + try: + params = json.loads(customize) + if params and "precision_halves" in params: + self._precision_halves = params.get("precision_halves") + except Exception as e: + _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") + self.update_all({"precision_halves": self._precision_halves}) + + +class MideaAppliance(MideaE3Device): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/e3/message.py b/custom_components/midea_ac_lan/midea/devices/e3/message.py index 53004ed7..4b6952a2 100644 --- a/custom_components/midea_ac_lan/midea/devices/e3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e3/message.py @@ -1,173 +1,173 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - -NEW_PROTOCOL_PARAMS = { - "zero_cold_water": 0x03, - # "zero_cold_master": 0x12, - "zero_cold_pulse": 0x04, - "smart_volume": 0x07, - "target_temperature": 0x08, -} - - -class MessageE3Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xE3, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageE3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessagePower(MessageE3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - if self.power: - self.body_type = 0x01 - else: - self.body_type = 0x02 - return bytearray([0x01]) - - -class MessageSet(MessageE3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x04, - ) - - self.target_temperature = 0 - self.zero_cold_water = False - self.bathtub_volume = 0 - self.protection = False - self.zero_cold_pulse = False - self.smart_volume = False - - @property - def _body(self): - # Byte 2 zero_cold_water mode - zero_cold_water = 0x01 if self.zero_cold_water else 0x00 - # Byte 3 - protection = 0x08 if self.protection else 0x00 - zero_cold_pulse = 0x10 if self.zero_cold_pulse else 0x00 - smart_volume = 0x20 if self.smart_volume else 0x00 - # Byte 5 target_temperature - target_temperature = self.target_temperature & 0xFF - - return bytearray( - [ - 0x01, - zero_cold_water | 0x02, - protection | zero_cold_pulse | smart_volume, - 0x00, - target_temperature, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageNewProtocolSet(MessageE3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x14, - ) - self.key = None - self.value = None - - @property - def _body(self): - key = NEW_PROTOCOL_PARAMS.get(self.key) - if self.key == "target_temperature": - value = self.value - else: - value = 0x01 if self.value else 0x00 - return bytearray( - [ - key, - value, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class E3GeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[2] & 0x01) > 0 - self.burning_state = (body[2] & 0x02) > 0 - self.zero_cold_water = (body[2] & 0x04) > 0 - self.current_temperature = body[5] - self.target_temperature = body[6] - self.protection = (body[8] & 0x08) > 0 - self.zero_cold_pulse = (body[20] & 0x01) > 0 if len(body) > 20 else False - self.smart_volume = (body[20] & 0x02) > 0 if len(body) > 20 else False - - -class MessageE3Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - (self.message_type == MessageType.query and self.body_type == 0x01) - or ( - self.message_type == MessageType.set - and self.body_type in [0x01, 0x02, 0x04, 0x14] - ) - or ( - self.message_type == MessageType.notify1 - and self.body_type in [0x00, 0x01] - ) - ): - self.set_body(E3GeneralMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + +NEW_PROTOCOL_PARAMS = { + "zero_cold_water": 0x03, + # "zero_cold_master": 0x12, + "zero_cold_pulse": 0x04, + "smart_volume": 0x07, + "target_temperature": 0x08, +} + + +class MessageE3Base(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xE3, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageE3Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x01, + ) + + @property + def _body(self): + return bytearray([0x01]) + + +class MessagePower(MessageE3Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x02, + ) + self.power = False + + @property + def _body(self): + if self.power: + self.body_type = 0x01 + else: + self.body_type = 0x02 + return bytearray([0x01]) + + +class MessageSet(MessageE3Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x04, + ) + + self.target_temperature = 0 + self.zero_cold_water = False + self.bathtub_volume = 0 + self.protection = False + self.zero_cold_pulse = False + self.smart_volume = False + + @property + def _body(self): + # Byte 2 zero_cold_water mode + zero_cold_water = 0x01 if self.zero_cold_water else 0x00 + # Byte 3 + protection = 0x08 if self.protection else 0x00 + zero_cold_pulse = 0x10 if self.zero_cold_pulse else 0x00 + smart_volume = 0x20 if self.smart_volume else 0x00 + # Byte 5 target_temperature + target_temperature = self.target_temperature & 0xFF + + return bytearray( + [ + 0x01, + zero_cold_water | 0x02, + protection | zero_cold_pulse | smart_volume, + 0x00, + target_temperature, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class MessageNewProtocolSet(MessageE3Base): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x14, + ) + self.key = None + self.value = None + + @property + def _body(self): + key = NEW_PROTOCOL_PARAMS.get(self.key) + if self.key == "target_temperature": + value = self.value + else: + value = 0x01 if self.value else 0x00 + return bytearray( + [ + key, + value, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class E3GeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[2] & 0x01) > 0 + self.burning_state = (body[2] & 0x02) > 0 + self.zero_cold_water = (body[2] & 0x04) > 0 + self.current_temperature = body[5] + self.target_temperature = body[6] + self.protection = (body[8] & 0x08) > 0 + self.zero_cold_pulse = (body[20] & 0x01) > 0 if len(body) > 20 else False + self.smart_volume = (body[20] & 0x02) > 0 if len(body) > 20 else False + + +class MessageE3Response(MessageResponse): + def __init__(self, message): + super().__init__(message) + if ( + (self.message_type == MessageType.query and self.body_type == 0x01) + or ( + self.message_type == MessageType.set + and self.body_type in [0x01, 0x02, 0x04, 0x14] + ) + or ( + self.message_type == MessageType.notify1 + and self.body_type in [0x00, 0x01] + ) + ): + self.set_body(E3GeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ea/device.py b/custom_components/midea_ac_lan/midea/devices/ea/device.py index c5268d26..7e81a55c 100644 --- a/custom_components/midea_ac_lan/midea/devices/ea/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ea/device.py @@ -1,197 +1,197 @@ -import logging - -from .message import MessageEAResponse, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - cooking = "cooking" - keep_warm = "keep_warm" - mode = "mode" - time_remaining = "time_remaining" - keep_warm_time = "keep_warm_time" - top_temperature = "top_temperature" - bottom_temperature = "bottom_temperature" - progress = "progress" - - -class MideaEADevice(MiedaDevice): - _mode_list = ( - [ - "smart", - "reserve", - "cook_rice", - "fast_cook_rice", - "standard_cook_rice", - "gruel", - "cook_congee", - "stew_soup", - "stewing", - "heat_rice", - "make_cake", - "yoghourt", - "soup_rice", - "coarse_rice", - "five_ceeals_rice", - "eight_treasures_rice", - "crispy_rice", - "shelled_rice", - "eight_treasures_congee", - "infant_congee", - "older_rice", - "rice_soup", - "rice_paste", - "egg_custard", - "warm_milk", - "hot_spring_egg", - "millet_congee", - "firewood_rice", - "few_rice", - "red_potato", - "corn", - "quick_freeze_bun", - "steam_ribs", - "steam_egg", - "coarse_congee", - "steep_rice", - "appetizing_congee", - "corn_congee", - "sprout_rice", - "luscious_rice", - "luscious_boiled", - "fast_rice", - "fast_boil", - "bean_rice_congee", - "fast_congee", - "baby_congee", - "cook_soup", - "congee_coup", - "steam_corn", - "steam_red_potato", - "boil_congee", - "delicious_steam", - "boil_egg", - "rice_wine", - "fruit_vegetable_paste", - "vegetable_porridge", - "pork_porridge", - "fragrant_rice", - "assorte_rice", - "steame_fish", - "baby_rice", - "essence_rice", - "fragrant_dense_congee", - "one_two_cook", - "original_steame", - "hot_fast_rice", - "online_celebrity_rice", - "sushi_rice", - "stone_bowl_rice", - "no_water_treat", - "keep_fresh", - "low_sugar_rice", - "black_buckwheat_rice", - "resveratrol_rice", - "yellow_wheat_rice", - "green_buckwheat_rice", - "roughage_rice", - "millet_mixed_rice", - "iron_pan_rice", - "olla_pan_rice", - "vegetable_rice", - "baby_side", - "regimen_congee", - "earthen_pot_congee", - "regimen_soup", - "pottery_jar_soup", - "canton_soup", - "nutrition_stew", - "northeast_stew", - "uncap_boil", - "trichromatic_coarse_grain", - "four_color_vegetables", - "egg", - "chop", - ] - + ["unknown"] * 98 - + ["clean"] - + ["unknown"] * 5 - + ["keep_warm"] - ) - _progress = ["Idle", "Delay", "Cooking", "Keep-warm"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xEA, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.cooking: False, - DeviceAttributes.keep_warm: False, - DeviceAttributes.mode: 0, - DeviceAttributes.time_remaining: None, - DeviceAttributes.top_temperature: None, - DeviceAttributes.bottom_temperature: None, - DeviceAttributes.keep_warm_time: None, - DeviceAttributes.progress: "Unknown", - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageEAResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.progress: - if value < len(MideaEADevice._progress): - self._attributes[status] = MideaEADevice._progress[value] - else: - self._attributes[status] = "Unknown" - elif status == DeviceAttributes.mode: - if value < len(MideaEADevice._mode_list): - self._attributes[status] = MideaEADevice._mode_list[value] - else: - self._attributes[status] = "Cloud" - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaEADevice): - pass +import logging + +from .message import MessageEAResponse, MessageQuery + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + cooking = "cooking" + keep_warm = "keep_warm" + mode = "mode" + time_remaining = "time_remaining" + keep_warm_time = "keep_warm_time" + top_temperature = "top_temperature" + bottom_temperature = "bottom_temperature" + progress = "progress" + + +class MideaEADevice(MiedaDevice): + _mode_list = ( + [ + "smart", + "reserve", + "cook_rice", + "fast_cook_rice", + "standard_cook_rice", + "gruel", + "cook_congee", + "stew_soup", + "stewing", + "heat_rice", + "make_cake", + "yoghourt", + "soup_rice", + "coarse_rice", + "five_ceeals_rice", + "eight_treasures_rice", + "crispy_rice", + "shelled_rice", + "eight_treasures_congee", + "infant_congee", + "older_rice", + "rice_soup", + "rice_paste", + "egg_custard", + "warm_milk", + "hot_spring_egg", + "millet_congee", + "firewood_rice", + "few_rice", + "red_potato", + "corn", + "quick_freeze_bun", + "steam_ribs", + "steam_egg", + "coarse_congee", + "steep_rice", + "appetizing_congee", + "corn_congee", + "sprout_rice", + "luscious_rice", + "luscious_boiled", + "fast_rice", + "fast_boil", + "bean_rice_congee", + "fast_congee", + "baby_congee", + "cook_soup", + "congee_coup", + "steam_corn", + "steam_red_potato", + "boil_congee", + "delicious_steam", + "boil_egg", + "rice_wine", + "fruit_vegetable_paste", + "vegetable_porridge", + "pork_porridge", + "fragrant_rice", + "assorte_rice", + "steame_fish", + "baby_rice", + "essence_rice", + "fragrant_dense_congee", + "one_two_cook", + "original_steame", + "hot_fast_rice", + "online_celebrity_rice", + "sushi_rice", + "stone_bowl_rice", + "no_water_treat", + "keep_fresh", + "low_sugar_rice", + "black_buckwheat_rice", + "resveratrol_rice", + "yellow_wheat_rice", + "green_buckwheat_rice", + "roughage_rice", + "millet_mixed_rice", + "iron_pan_rice", + "olla_pan_rice", + "vegetable_rice", + "baby_side", + "regimen_congee", + "earthen_pot_congee", + "regimen_soup", + "pottery_jar_soup", + "canton_soup", + "nutrition_stew", + "northeast_stew", + "uncap_boil", + "trichromatic_coarse_grain", + "four_color_vegetables", + "egg", + "chop", + ] + + ["unknown"] * 98 + + ["clean"] + + ["unknown"] * 5 + + ["keep_warm"] + ) + _progress = ["Idle", "Delay", "Cooking", "Keep-warm"] + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xEA, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.cooking: False, + DeviceAttributes.keep_warm: False, + DeviceAttributes.mode: 0, + DeviceAttributes.time_remaining: None, + DeviceAttributes.top_temperature: None, + DeviceAttributes.bottom_temperature: None, + DeviceAttributes.keep_warm_time: None, + DeviceAttributes.progress: "Unknown", + }, + ) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageEAResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.progress: + if value < len(MideaEADevice._progress): + self._attributes[status] = MideaEADevice._progress[value] + else: + self._attributes[status] = "Unknown" + elif status == DeviceAttributes.mode: + if value < len(MideaEADevice._mode_list): + self._attributes[status] = MideaEADevice._mode_list[value] + else: + self._attributes[status] = "Cloud" + else: + self._attributes[status] = value + new_status[str(status)] = self._attributes[status] + return new_status + + def set_attribute(self, attr, value): + pass + + +class MideaAppliance(MideaEADevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/ea/message.py b/custom_components/midea_ac_lan/midea/devices/ea/message.py index 636ed1e2..49347767 100644 --- a/custom_components/midea_ac_lan/midea/devices/ea/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ea/message.py @@ -1,116 +1,116 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageEABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xEA, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageEABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=None, - ) - - @property - def body(self): - return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00]) - - @property - def _body(self): - return bytearray([]) - - -class EABody1(MessageBody): - def __init__(self, body): - super().__init__(body) - self.mode = body[6] + (body[7] << 8) - self.progress = body[14] - self.cooking = self.progress == 2 - self.keep_warm = self.progress == 3 - self.top_temperature = body[18] - self.bottom_temperature = body[19] - self.time_remaining = body[22] * 60 + body[23] - self.keep_warm_time = body[26] * 60 + body[27] - - -class EABody2(MessageBody): - def __init__(self, body): - super().__init__(body) - self.progress = body[9] - self.cooking = self.progress == 2 - self.keep_warm = self.progress == 3 - self.mode = body[58] + (body[59] << 8) - self.time_remaining = body[50] * 60 + body[51] - self.keep_warm_time = body[54] * 60 + body[55] - self.top_temperature = body[21] - self.bottom_temperature = body[20] - - -class EABody3(MessageBody): - def __init__(self, body): - super().__init__(body) - self.mode = body[4] + (body[5] << 8) - self.progress = body[8] - self.cooking = self.progress == 2 - self.keep_warm = self.progress == 3 - self.time_remaining = body[12] * 60 + body[13] - self.top_temperature = body[20] - self.bottom_temperature = body[21] - self.keep_warm_time = body[22] * 60 + body[23] - - -class EABodyNew(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[6] in [2, 4, 6, 8, 10, 0x62]: - self.mode = body[7] + (body[8] << 8) - self.progress = body[11] - self.cooking = self.progress == 2 - self.keep_warm = self.progress == 3 - self.time_remaining = body[16] * 60 + body[17] - self.top_temperature = body[60] - self.bottom_temperature = body[61] - self.keep_warm_time = body[19] * 60 + body[20] - - -class MessageEAResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type == MessageType.notify1 and super().body[3] == 0x01: - self.set_body(EABodyNew(super().body)) - elif self.protocol_version == 0: - if self.message_type == MessageType.set and super().body[5] == 0x16: # 381 - self.set_body(EABody1(super().body)) - elif self.message_type == MessageType.query: - if super().body[6] == 0x52 and super().body[7] == 0xC3: # 404 - self.set_body(EABody2(super().body)) - elif super().body[5] == 0x3D: # 420 - self.set_body(EABody1(super().body)) - elif ( - self.message_type == MessageType.notify1 and super().body[5] == 0x3D - ): # 463 - self.set_body(EABody1(super().body)) - else: - if ( - (self.message_type == MessageType.set and super().body[3] == 0x02) - or (self.message_type == MessageType.query and super().body[3] == 0x03) - or ( - self.message_type == MessageType.notify1 and super().body[3] == 0x04 - ) - ): # 351 - self.set_body(EABody3(super().body)) - elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: - self.mode = super().body[4] + (super().body[5] << 8) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageEABase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xEA, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageEABase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=None, + ) + + @property + def body(self): + return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00]) + + @property + def _body(self): + return bytearray([]) + + +class EABody1(MessageBody): + def __init__(self, body): + super().__init__(body) + self.mode = body[6] + (body[7] << 8) + self.progress = body[14] + self.cooking = self.progress == 2 + self.keep_warm = self.progress == 3 + self.top_temperature = body[18] + self.bottom_temperature = body[19] + self.time_remaining = body[22] * 60 + body[23] + self.keep_warm_time = body[26] * 60 + body[27] + + +class EABody2(MessageBody): + def __init__(self, body): + super().__init__(body) + self.progress = body[9] + self.cooking = self.progress == 2 + self.keep_warm = self.progress == 3 + self.mode = body[58] + (body[59] << 8) + self.time_remaining = body[50] * 60 + body[51] + self.keep_warm_time = body[54] * 60 + body[55] + self.top_temperature = body[21] + self.bottom_temperature = body[20] + + +class EABody3(MessageBody): + def __init__(self, body): + super().__init__(body) + self.mode = body[4] + (body[5] << 8) + self.progress = body[8] + self.cooking = self.progress == 2 + self.keep_warm = self.progress == 3 + self.time_remaining = body[12] * 60 + body[13] + self.top_temperature = body[20] + self.bottom_temperature = body[21] + self.keep_warm_time = body[22] * 60 + body[23] + + +class EABodyNew(MessageBody): + def __init__(self, body): + super().__init__(body) + if body[6] in [2, 4, 6, 8, 10, 0x62]: + self.mode = body[7] + (body[8] << 8) + self.progress = body[11] + self.cooking = self.progress == 2 + self.keep_warm = self.progress == 3 + self.time_remaining = body[16] * 60 + body[17] + self.top_temperature = body[60] + self.bottom_temperature = body[61] + self.keep_warm_time = body[19] * 60 + body[20] + + +class MessageEAResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.message_type == MessageType.notify1 and super().body[3] == 0x01: + self.set_body(EABodyNew(super().body)) + elif self.protocol_version == 0: + if self.message_type == MessageType.set and super().body[5] == 0x16: # 381 + self.set_body(EABody1(super().body)) + elif self.message_type == MessageType.query: + if super().body[6] == 0x52 and super().body[7] == 0xC3: # 404 + self.set_body(EABody2(super().body)) + elif super().body[5] == 0x3D: # 420 + self.set_body(EABody1(super().body)) + elif ( + self.message_type == MessageType.notify1 and super().body[5] == 0x3D + ): # 463 + self.set_body(EABody1(super().body)) + else: + if ( + (self.message_type == MessageType.set and super().body[3] == 0x02) + or (self.message_type == MessageType.query and super().body[3] == 0x03) + or ( + self.message_type == MessageType.notify1 and super().body[3] == 0x04 + ) + ): # 351 + self.set_body(EABody3(super().body)) + elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: + self.mode = super().body[4] + (super().body[5] << 8) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ec/device.py b/custom_components/midea_ac_lan/midea/devices/ec/device.py index 0a66ab66..554d8b88 100644 --- a/custom_components/midea_ac_lan/midea/devices/ec/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ec/device.py @@ -1,211 +1,211 @@ -import logging - -from .message import MessageECResponse, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - cooking = "cooking" - mode = "mode" - time_remaining = "time_remaining" - keep_warm_time = "keep_warm_time" - top_temperature = "top_temperature" - bottom_temperature = "bottom_temperature" - progress = "progress" - with_pressure = "with_pressure" - - -class MideaECDevice(MiedaDevice): - _mode_list = ( - [ - "smart", - "reserve", - "cook_rice", - "fast_cook_rice", - "standard_cook_rice", - "gruel", - "cook_congee", - "stew_soup", - "stewing", - "heat_rice", - "make_cake", - "yoghourt", - "soup_rice", - "coarse_rice", - "five_ceeals_rice", - "eight_treasures_rice", - "crispy_rice", - "shelled_rice", - "eight_treasures_congee", - "infant_congee", - "older_rice", - "rice_soup", - "rice_paste", - "egg_custard", - "warm_milk", - "hot_spring_egg", - "millet_congee", - "firewood_rice", - "few_rice", - "red_potato", - "corn", - "quick_freeze_bun", - "steam_ribs", - "steam_egg", - "coarse_congee", - "steep_rice", - "appetizing_congee", - "corn_congee", - "sprout_rice", - "luscious_rice", - "luscious_boiled", - "fast_rice", - "fast_boil", - "bean_rice_congee", - "fast_congee", - "baby_congee", - "cook_soup", - "congee_coup", - "steam_corn", - "steam_red_potato", - "boil_congee", - "delicious_steam", - "boil_egg", - "rice_wine", - "fruit_vegetable_paste", - "vegetable_porridge", - "pork_porridge", - "fragrant_rice", - "assorte_rice", - "steame_fish", - "baby_rice", - "essence_rice", - "fragrant_dense_congee", - "one_two_cook", - "original_steame", - "hot_fast_rice", - "online_celebrity_rice", - "sushi_rice", - "stone_bowl_rice", - "no_water_treat", - "keep_fresh", - "low_sugar_rice", - "black_buckwheat_rice", - "resveratrol_rice", - "yellow_wheat_rice", - "green_buckwheat_rice", - "roughage_rice", - "millet_mixed_rice", - "iron_pan_rice", - "olla_pan_rice", - "vegetable_rice", - "baby_side", - "regimen_congee", - "earthen_pot_congee", - "regimen_soup", - "pottery_jar_soup", - "canton_soup", - "nutrition_stew", - "northeast_stew", - "uncap_boil", - "trichromatic_coarse_grain", - "four_color_vegetables", - "egg", - "chop", - ] - + ["unknown"] * 98 - + ["clean"] - + ["unknown"] * 5 - + ["keep_warm", "diy"] - ) - _progress = [ - "Idle", - "Cooking", - "Delay", - "Keep-warm", - "Lid-open", - "Relieving", - "Keep-pressure", - "Relieving", - "Cooking", - "Relieving", - "Lid-open", - ] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xEC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.cooking: False, - DeviceAttributes.mode: 0, - DeviceAttributes.time_remaining: None, - DeviceAttributes.top_temperature: None, - DeviceAttributes.bottom_temperature: None, - DeviceAttributes.keep_warm_time: None, - DeviceAttributes.progress: "Unknown", - DeviceAttributes.with_pressure: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageECResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.progress: - if value < len(MideaECDevice._progress): - self._attributes[status] = MideaECDevice._progress[ - getattr(message, str(status)) - ] - else: - self._attributes[status] = "Unknown" - elif status == DeviceAttributes.mode: - if value < len(MideaECDevice._mode_list): - self._attributes[status] = MideaECDevice._mode_list[value] - else: - self._attributes[status] = "Cloud" - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaECDevice): - pass +import logging + +from .message import MessageECResponse, MessageQuery + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + cooking = "cooking" + mode = "mode" + time_remaining = "time_remaining" + keep_warm_time = "keep_warm_time" + top_temperature = "top_temperature" + bottom_temperature = "bottom_temperature" + progress = "progress" + with_pressure = "with_pressure" + + +class MideaECDevice(MiedaDevice): + _mode_list = ( + [ + "smart", + "reserve", + "cook_rice", + "fast_cook_rice", + "standard_cook_rice", + "gruel", + "cook_congee", + "stew_soup", + "stewing", + "heat_rice", + "make_cake", + "yoghourt", + "soup_rice", + "coarse_rice", + "five_ceeals_rice", + "eight_treasures_rice", + "crispy_rice", + "shelled_rice", + "eight_treasures_congee", + "infant_congee", + "older_rice", + "rice_soup", + "rice_paste", + "egg_custard", + "warm_milk", + "hot_spring_egg", + "millet_congee", + "firewood_rice", + "few_rice", + "red_potato", + "corn", + "quick_freeze_bun", + "steam_ribs", + "steam_egg", + "coarse_congee", + "steep_rice", + "appetizing_congee", + "corn_congee", + "sprout_rice", + "luscious_rice", + "luscious_boiled", + "fast_rice", + "fast_boil", + "bean_rice_congee", + "fast_congee", + "baby_congee", + "cook_soup", + "congee_coup", + "steam_corn", + "steam_red_potato", + "boil_congee", + "delicious_steam", + "boil_egg", + "rice_wine", + "fruit_vegetable_paste", + "vegetable_porridge", + "pork_porridge", + "fragrant_rice", + "assorte_rice", + "steame_fish", + "baby_rice", + "essence_rice", + "fragrant_dense_congee", + "one_two_cook", + "original_steame", + "hot_fast_rice", + "online_celebrity_rice", + "sushi_rice", + "stone_bowl_rice", + "no_water_treat", + "keep_fresh", + "low_sugar_rice", + "black_buckwheat_rice", + "resveratrol_rice", + "yellow_wheat_rice", + "green_buckwheat_rice", + "roughage_rice", + "millet_mixed_rice", + "iron_pan_rice", + "olla_pan_rice", + "vegetable_rice", + "baby_side", + "regimen_congee", + "earthen_pot_congee", + "regimen_soup", + "pottery_jar_soup", + "canton_soup", + "nutrition_stew", + "northeast_stew", + "uncap_boil", + "trichromatic_coarse_grain", + "four_color_vegetables", + "egg", + "chop", + ] + + ["unknown"] * 98 + + ["clean"] + + ["unknown"] * 5 + + ["keep_warm", "diy"] + ) + _progress = [ + "Idle", + "Cooking", + "Delay", + "Keep-warm", + "Lid-open", + "Relieving", + "Keep-pressure", + "Relieving", + "Cooking", + "Relieving", + "Lid-open", + ] + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xEC, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.cooking: False, + DeviceAttributes.mode: 0, + DeviceAttributes.time_remaining: None, + DeviceAttributes.top_temperature: None, + DeviceAttributes.bottom_temperature: None, + DeviceAttributes.keep_warm_time: None, + DeviceAttributes.progress: "Unknown", + DeviceAttributes.with_pressure: None, + }, + ) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageECResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.progress: + if value < len(MideaECDevice._progress): + self._attributes[status] = MideaECDevice._progress[ + getattr(message, str(status)) + ] + else: + self._attributes[status] = "Unknown" + elif status == DeviceAttributes.mode: + if value < len(MideaECDevice._mode_list): + self._attributes[status] = MideaECDevice._mode_list[value] + else: + self._attributes[status] = "Cloud" + else: + self._attributes[status] = value + new_status[str(status)] = self._attributes[status] + return new_status + + def set_attribute(self, attr, value): + pass + + +class MideaAppliance(MideaECDevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/ec/message.py b/custom_components/midea_ac_lan/midea/devices/ec/message.py index e4d65645..00c4aefe 100644 --- a/custom_components/midea_ac_lan/midea/devices/ec/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ec/message.py @@ -1,74 +1,74 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageECBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xEC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageECBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=None, - ) - - @property - def body(self): - return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) - - @property - def _body(self): - return bytearray([]) - - -class ECGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.mode = body[4] + (body[5] << 8) - self.progress = body[8] - self.cooking = self.progress == 1 - self.time_remaining = body[12] * 60 + body[13] - self.keep_warm_time = body[16] * 60 + body[17] - self.top_temperature = body[21] - self.bottom_temperature = body[22] - self.with_pressure = (body[23] & 0x04) > 0 - - -class ECBodyNew(MessageBody): - def __init__(self, body): - super().__init__(body) - self.progress = body[11] - self.cooking = self.progress == 1 - self.time_remaining = body[16] * 60 + body[17] - self.keep_warm_time = body[19] * 60 + body[20] - self.top_temperature = body[48] - self.bottom_temperature = body[49] - self.with_pressure = body[33] > 0 - - -class MessageECResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type == MessageType.notify1 and super().body[3] == 0x01: - self.set_body(ECBodyNew(super().body)) - elif ( - (self.message_type == MessageType.set and super().body[3] == 0x02) - or (self.message_type == MessageType.query and super().body[3] == 0x03) - or (self.message_type == MessageType.notify1 and super().body[3] == 0x04) - or (self.message_type == MessageType.notify1 and super().body[3] == 0x3D) - ): - self.set_body(ECGeneralMessageBody(super().body)) - elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: - self.mode = super().body[4] + (super().body[5] << 8) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageECBase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xEC, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageECBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=None, + ) + + @property + def body(self): + return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + + @property + def _body(self): + return bytearray([]) + + +class ECGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.mode = body[4] + (body[5] << 8) + self.progress = body[8] + self.cooking = self.progress == 1 + self.time_remaining = body[12] * 60 + body[13] + self.keep_warm_time = body[16] * 60 + body[17] + self.top_temperature = body[21] + self.bottom_temperature = body[22] + self.with_pressure = (body[23] & 0x04) > 0 + + +class ECBodyNew(MessageBody): + def __init__(self, body): + super().__init__(body) + self.progress = body[11] + self.cooking = self.progress == 1 + self.time_remaining = body[16] * 60 + body[17] + self.keep_warm_time = body[19] * 60 + body[20] + self.top_temperature = body[48] + self.bottom_temperature = body[49] + self.with_pressure = body[33] > 0 + + +class MessageECResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.message_type == MessageType.notify1 and super().body[3] == 0x01: + self.set_body(ECBodyNew(super().body)) + elif ( + (self.message_type == MessageType.set and super().body[3] == 0x02) + or (self.message_type == MessageType.query and super().body[3] == 0x03) + or (self.message_type == MessageType.notify1 and super().body[3] == 0x04) + or (self.message_type == MessageType.notify1 and super().body[3] == 0x3D) + ): + self.set_body(ECGeneralMessageBody(super().body)) + elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: + self.mode = super().body[4] + (super().body[5] << 8) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ed/device.py b/custom_components/midea_ac_lan/midea/devices/ed/device.py index 414be310..c758ddfb 100644 --- a/custom_components/midea_ac_lan/midea/devices/ed/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ed/device.py @@ -1,103 +1,103 @@ -import logging - -from .message import MessageEDResponse, MessageNewSet, MessageOldSet, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - water_consumption = "water_consumption" - in_tds = "in_tds" - out_tds = "out_tds" - filter1 = "filter1" - filter2 = "filter2" - filter3 = "filter3" - life1 = "life1" - life2 = "life2" - life3 = "life3" - child_lock = "child_lock" - - -class MideaEDDevice(MiedaDevice): - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xED, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.water_consumption: None, - DeviceAttributes.in_tds: None, - DeviceAttributes.out_tds: None, - DeviceAttributes.filter1: None, - DeviceAttributes.filter2: None, - DeviceAttributes.filter3: None, - DeviceAttributes.life1: None, - DeviceAttributes.life2: None, - DeviceAttributes.life3: None, - DeviceAttributes.child_lock: False, - }, - ) - self._device_class = 0 - - def _use_new_set(self): - return True # if (self.sub_type > 342 or self.sub_type == 340) else False - - def build_query(self): - return [MessageQuery(self._protocol_version, self._device_class)] - - def process_message(self, msg): - message = MessageEDResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - if hasattr(message, "device_class"): - self._device_class = message.device_class - for status in self._attributes.keys(): - if hasattr(message, str(status)): - new_status[str(status)] = getattr(message, str(status)) - self._attributes[status] = getattr(message, str(status)) - return new_status - - def set_attribute(self, attr, value): - message = None - if self._use_new_set(): - if attr in [DeviceAttributes.power, DeviceAttributes.child_lock]: - message = MessageNewSet(self._protocol_version) - else: - if attr in []: - message = MessageOldSet(self._protocol_version) - if message is not None: - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(MideaEDDevice): - pass +import logging + +from .message import MessageEDResponse, MessageNewSet, MessageOldSet, MessageQuery + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + water_consumption = "water_consumption" + in_tds = "in_tds" + out_tds = "out_tds" + filter1 = "filter1" + filter2 = "filter2" + filter3 = "filter3" + life1 = "life1" + life2 = "life2" + life3 = "life3" + child_lock = "child_lock" + + +class MideaEDDevice(MiedaDevice): + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xED, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.water_consumption: None, + DeviceAttributes.in_tds: None, + DeviceAttributes.out_tds: None, + DeviceAttributes.filter1: None, + DeviceAttributes.filter2: None, + DeviceAttributes.filter3: None, + DeviceAttributes.life1: None, + DeviceAttributes.life2: None, + DeviceAttributes.life3: None, + DeviceAttributes.child_lock: False, + }, + ) + self._device_class = 0 + + def _use_new_set(self): + return True # if (self.sub_type > 342 or self.sub_type == 340) else False + + def build_query(self): + return [MessageQuery(self._protocol_version, self._device_class)] + + def process_message(self, msg): + message = MessageEDResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + if hasattr(message, "device_class"): + self._device_class = message.device_class + for status in self._attributes.keys(): + if hasattr(message, str(status)): + new_status[str(status)] = getattr(message, str(status)) + self._attributes[status] = getattr(message, str(status)) + return new_status + + def set_attribute(self, attr, value): + message = None + if self._use_new_set(): + if attr in [DeviceAttributes.power, DeviceAttributes.child_lock]: + message = MessageNewSet(self._protocol_version) + else: + if attr in []: + message = MessageOldSet(self._protocol_version) + if message is not None: + setattr(message, str(attr), value) + self.build_send(message) + + +class MideaAppliance(MideaEDDevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/ed/message.py b/custom_components/midea_ac_lan/midea/devices/ed/message.py index 3425414d..e843d8af 100644 --- a/custom_components/midea_ac_lan/midea/devices/ed/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ed/message.py @@ -1,200 +1,200 @@ -from enum import IntEnum - -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class NewSetTags(IntEnum): - power = 0x0100 - lock = 0x0201 - - -class EDNewSetParamPack: - @staticmethod - def pack(param, value, addition=0): - return bytearray( - [param & 0xFF, param >> 8, value, addition & 0xFF, addition >> 8] - ) - - -class MessageEDBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xED, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageEDBase): - def __init__(self, protocol_version, device_class): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=device_class, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessageNewSet(MessageEDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x15, - ) - self.power = None - self.lock = None - - @property - def _body(self): - pack_count = 0 - payload = bytearray([0x01, 0x00]) - if self.power is not None: - pack_count += 1 - payload.extend( - EDNewSetParamPack.pack( - param=NewSetTags.power, value=0x01 if self.power else 0x00 # power - ) - ) - if self.lock is not None: - pack_count += 1 - payload.extend( - EDNewSetParamPack.pack( - param=NewSetTags.lock, value=0x01 if self.lock else 0x00 # lock - ) - ) - payload[1] = pack_count - return payload - - -class MessageOldSet(MessageEDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=None, - ) - - @property - def body(self): - return bytearray([]) - - @property - def _body(self): - return bytearray([]) - - -class EDMessageBody01(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[2] & 0x01) > 0 - self.water_consumption = body[7] + (body[8] << 8) - self.in_tds = body[36] + (body[37] << 8) - self.out_tds = body[38] + (body[39] << 8) - self.child_lock = body[15] > 0 - self.filter1 = round((body[25] + (body[26] << 8)) / 24) - self.filter2 = round((body[27] + (body[28] << 8)) / 24) - self.filter3 = round((body[29] + (body[30] << 8)) / 24) - self.life1 = body[16] - self.life2 = body[17] - self.life3 = body[18] - - -class EDMessageBody03(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[51] & 0x01) > 0 - self.child_lock = (body[51] & 0x08) > 0 - self.water_consumption = body[20] + (body[21] << 8) - self.life1 = body[22] - self.life2 = body[23] - self.life3 = body[24] - self.in_tds = body[27] + (body[28] << 8) - self.out_tds = body[29] + (body[30] << 8) - - -class EDMessageBody05(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[51] & 0x01) > 0 - self.child_lock = (body[51] & 0x08) > 0 - self.water_consumption = body[20] + (body[21] << 8) - - -class EDMessageBody06(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[51] & 0x01) > 0 - self.child_lock = (body[51] & 0x08) > 0 - self.water_consumption = body[25] + (body[26] << 8) - - -class EDMessageBody07(MessageBody): - def __init__(self, body): - super().__init__(body) - self.water_consumption = (body[21] << 8) + body[20] - self.power = (body[51] & 0x01) > 0 - self.child_lock = (body[51] & 0x08) > 0 - - -class EDMessageBodyFF(MessageBody): - def __init__(self, body): - super().__init__(body) - data_offset = 2 - while True: - length = (body[data_offset + 2] >> 4) + 2 - attr = ((body[data_offset + 2] % 16) << 8) + body[data_offset + 1] - if attr == 0x000: - self.child_lock = (body[data_offset + 5] & 0x01) > 0 - self.power = (body[data_offset + 6] & 0x01) > 0 - elif attr == 0x011: - self.water_consumption = ( - float( - ( - body[data_offset + 3] - + (body[data_offset + 4] << 8) - + (body[data_offset + 5] << 16) - + (body[data_offset + 6] << 24) - ) - ) - / 1000 - ) - elif attr == 0x013: - self.in_tds = body[data_offset + 3] + (body[data_offset + 4] << 8) - self.out_tds = body[data_offset + 5] + (body[data_offset + 6] << 8) - elif attr == 0x10: - self.life1 = body[data_offset + 3] - self.life2 = body[data_offset + 4] - self.life3 = body[data_offset + 5] - # fix index out of range error - if data_offset + length + 6 > len(body): - break - data_offset += length - - -class MessageEDResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self._message_type in [MessageType.query, MessageType.notify1]: - self.device_class = self._body_type - if self._body_type in [0x00, 0xFF]: - self.set_body(EDMessageBodyFF(super().body)) - if self.body_type == 0x01: - self.set_body(EDMessageBody01(super().body)) - elif self.body_type in [0x03, 0x04]: - self.set_body(EDMessageBody03(super().body)) - elif self.body_type == 0x05: - self.set_body(EDMessageBody05(super().body)) - elif self.body_type == 0x06: - self.set_body(EDMessageBody06(super().body)) - elif self.body_type == 0x07: - self.set_body(EDMessageBody07(super().body)) - self.set_attr() +from enum import IntEnum + +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class NewSetTags(IntEnum): + power = 0x0100 + lock = 0x0201 + + +class EDNewSetParamPack: + @staticmethod + def pack(param, value, addition=0): + return bytearray( + [param & 0xFF, param >> 8, value, addition & 0xFF, addition >> 8] + ) + + +class MessageEDBase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xED, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageEDBase): + def __init__(self, protocol_version, device_class): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=device_class, + ) + + @property + def _body(self): + return bytearray([0x01]) + + +class MessageNewSet(MessageEDBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x15, + ) + self.power = None + self.lock = None + + @property + def _body(self): + pack_count = 0 + payload = bytearray([0x01, 0x00]) + if self.power is not None: + pack_count += 1 + payload.extend( + EDNewSetParamPack.pack( + param=NewSetTags.power, value=0x01 if self.power else 0x00 # power + ) + ) + if self.lock is not None: + pack_count += 1 + payload.extend( + EDNewSetParamPack.pack( + param=NewSetTags.lock, value=0x01 if self.lock else 0x00 # lock + ) + ) + payload[1] = pack_count + return payload + + +class MessageOldSet(MessageEDBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=None, + ) + + @property + def body(self): + return bytearray([]) + + @property + def _body(self): + return bytearray([]) + + +class EDMessageBody01(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[2] & 0x01) > 0 + self.water_consumption = body[7] + (body[8] << 8) + self.in_tds = body[36] + (body[37] << 8) + self.out_tds = body[38] + (body[39] << 8) + self.child_lock = body[15] > 0 + self.filter1 = round((body[25] + (body[26] << 8)) / 24) + self.filter2 = round((body[27] + (body[28] << 8)) / 24) + self.filter3 = round((body[29] + (body[30] << 8)) / 24) + self.life1 = body[16] + self.life2 = body[17] + self.life3 = body[18] + + +class EDMessageBody03(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[51] & 0x01) > 0 + self.child_lock = (body[51] & 0x08) > 0 + self.water_consumption = body[20] + (body[21] << 8) + self.life1 = body[22] + self.life2 = body[23] + self.life3 = body[24] + self.in_tds = body[27] + (body[28] << 8) + self.out_tds = body[29] + (body[30] << 8) + + +class EDMessageBody05(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[51] & 0x01) > 0 + self.child_lock = (body[51] & 0x08) > 0 + self.water_consumption = body[20] + (body[21] << 8) + + +class EDMessageBody06(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[51] & 0x01) > 0 + self.child_lock = (body[51] & 0x08) > 0 + self.water_consumption = body[25] + (body[26] << 8) + + +class EDMessageBody07(MessageBody): + def __init__(self, body): + super().__init__(body) + self.water_consumption = (body[21] << 8) + body[20] + self.power = (body[51] & 0x01) > 0 + self.child_lock = (body[51] & 0x08) > 0 + + +class EDMessageBodyFF(MessageBody): + def __init__(self, body): + super().__init__(body) + data_offset = 2 + while True: + length = (body[data_offset + 2] >> 4) + 2 + attr = ((body[data_offset + 2] % 16) << 8) + body[data_offset + 1] + if attr == 0x000: + self.child_lock = (body[data_offset + 5] & 0x01) > 0 + self.power = (body[data_offset + 6] & 0x01) > 0 + elif attr == 0x011: + self.water_consumption = ( + float( + ( + body[data_offset + 3] + + (body[data_offset + 4] << 8) + + (body[data_offset + 5] << 16) + + (body[data_offset + 6] << 24) + ) + ) + / 1000 + ) + elif attr == 0x013: + self.in_tds = body[data_offset + 3] + (body[data_offset + 4] << 8) + self.out_tds = body[data_offset + 5] + (body[data_offset + 6] << 8) + elif attr == 0x10: + self.life1 = body[data_offset + 3] + self.life2 = body[data_offset + 4] + self.life3 = body[data_offset + 5] + # fix index out of range error + if data_offset + length + 6 > len(body): + break + data_offset += length + + +class MessageEDResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self._message_type in [MessageType.query, MessageType.notify1]: + self.device_class = self._body_type + if self._body_type in [0x00, 0xFF]: + self.set_body(EDMessageBodyFF(super().body)) + if self.body_type == 0x01: + self.set_body(EDMessageBody01(super().body)) + elif self.body_type in [0x03, 0x04]: + self.set_body(EDMessageBody03(super().body)) + elif self.body_type == 0x05: + self.set_body(EDMessageBody05(super().body)) + elif self.body_type == 0x06: + self.set_body(EDMessageBody06(super().body)) + elif self.body_type == 0x07: + self.set_body(EDMessageBody07(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fa/device.py b/custom_components/midea_ac_lan/midea/devices/fa/device.py index ee2e3f74..9c9864de 100644 --- a/custom_components/midea_ac_lan/midea/devices/fa/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fa/device.py @@ -1,327 +1,327 @@ -import json -import logging - -from .message import MessageFAResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - child_lock = "child_lock" - mode = "mode" - fan_speed = "fan_speed" - oscillate = "oscillate" - oscillation_angle = "oscillation_angle" - tilting_angle = "tilting_angle" - oscillation_mode = "oscillation_mode" - - -class MideaFADevice(MiedaDevice): - _oscillation_angles = ["Off", "30", "60", "90", "120", "180", "360"] - _tilting_angles = ["Off", "30", "60", "90", "120", "180", "360", "+60", "-60", "40"] - _oscillation_modes = [ - "Off", - "Oscillation", - "Tilting", - "Curve-W", - "Curve-8", - "Reserved", - "Both", - ] - _modes = [ - "Normal", - "Natural", - "Sleep", - "Comfort", - "Silent", - "Baby", - "Induction", - "Circulation", - "Strong", - "Soft", - "Customize", - "Warm", - "Smart", - ] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xFA, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.child_lock: False, - DeviceAttributes.mode: 0, - DeviceAttributes.fan_speed: 0, - DeviceAttributes.oscillate: False, - DeviceAttributes.oscillation_angle: None, - DeviceAttributes.tilting_angle: None, - DeviceAttributes.oscillation_mode: None, - }, - ) - self._default_speed_count = 3 - self._speed_count = self._default_speed_count - self.set_customize(customize) - - @property - def speed_count(self): - return self._speed_count - - @property - def oscillation_angles(self): - return MideaFADevice._oscillation_angles - - @property - def tilting_angles(self): - return MideaFADevice._tilting_angles - - @property - def oscillation_modes(self): - return MideaFADevice._oscillation_modes - - @property - def preset_modes(self): - return self._modes - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageFAResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.oscillation_angle: - if value < len(MideaFADevice._oscillation_angles): - self._attributes[status] = MideaFADevice._oscillation_angles[ - value - ] - else: - self._attributes[status] = None - elif status == DeviceAttributes.tilting_angle: - if value < len(MideaFADevice._tilting_angles): - self._attributes[status] = MideaFADevice._tilting_angles[value] - else: - self._attributes[status] = None - elif status == DeviceAttributes.oscillation_mode: - if value < len(MideaFADevice._oscillation_modes): - self._attributes[status] = MideaFADevice._oscillation_modes[ - value - ] - else: - self._attributes[status] = None - elif status == DeviceAttributes.mode: - if value < len(MideaFADevice._modes): - self._attributes[status] = MideaFADevice._modes[value] - else: - self._attributes[status] = None - elif status == DeviceAttributes.power: - self._attributes[status] = value - if not value: - self._attributes[DeviceAttributes.fan_speed] = 0 - elif ( - status == DeviceAttributes.fan_speed - and not self._attributes[DeviceAttributes.power] - ): - self._attributes[status] = 0 - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_oscillation(self, attr, value): - message = None - if self._attributes[attr] != value: - if attr == DeviceAttributes.oscillate: - message = MessageSet(self._protocol_version, self.subtype) - message.oscillate = value - if value: - message.oscillation_angle = 3 # 90 - message.oscillation_mode = 1 # Oscillation - elif attr == DeviceAttributes.oscillation_mode and ( - value in MideaFADevice._oscillation_modes or not value - ): - message = MessageSet(self._protocol_version, self.subtype) - if value == "Off" or not value: - message.oscillate = False - else: - message.oscillate = True - message.oscillation_mode = MideaFADevice._oscillation_modes.index( - value - ) - if value == "Oscillation": - if ( - self._attributes[DeviceAttributes.oscillation_angle] - == "Off" - ): - message.oscillation_angle = 3 # 90 - else: - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) - ) - elif value == "Tilting": - if self._attributes[DeviceAttributes.tilting_angle] == "Off": - message.tilting_angle = 3 # 90 - else: - message.tilting_angle = MideaFADevice._tilting_angles.index( - self._attributes[DeviceAttributes.tilting_angle] - ) - else: - if ( - self._attributes[DeviceAttributes.oscillation_angle] - == "Off" - ): - message.oscillation_angle = 3 # 90 - else: - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) - ) - if self._attributes[DeviceAttributes.tilting_angle] == "Off": - message.tilting_angle = 3 # 90 - else: - message.tilting_angle = MideaFADevice._tilting_angles.index( - self._attributes[DeviceAttributes.tilting_angle] - ) - elif attr == DeviceAttributes.oscillation_angle and ( - value in MideaFADevice._oscillation_angles or not value - ): - message = MessageSet(self._protocol_version, self.subtype) - if value == "Off" or not value: - if self._attributes[DeviceAttributes.tilting_angle] == "Off": - message.oscillate = False - else: - message.oscillate = True - message.oscillation_mode = 2 - message.tilting_angle = MideaFADevice._tilting_angles.index( - self._attributes[DeviceAttributes.tilting_angle] - ) - else: - message.oscillation_angle = MideaFADevice._oscillation_angles.index( - value - ) - message.oscillate = True - if self._attributes[DeviceAttributes.tilting_angle] == "Off": - message.oscillation_mode = 1 - elif ( - self._attributes[DeviceAttributes.oscillation_mode] == "Tilting" - ): - message.oscillation_mode = 6 - message.tilting_angle = MideaFADevice._tilting_angles.index( - self._attributes[DeviceAttributes.tilting_angle] - ) - elif attr == DeviceAttributes.tilting_angle and ( - value in MideaFADevice._tilting_angles or not value - ): - message = MessageSet(self._protocol_version, self.subtype) - if value == "Off" or not value: - if self._attributes[DeviceAttributes.oscillation_angle] == "Off": - message.oscillate = False - else: - message.oscillate = True - message.oscillation_mode = 1 - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) - ) - else: - message.tilting_angle = MideaFADevice._tilting_angles.index(value) - message.oscillate = True - if self._attributes[DeviceAttributes.oscillation_angle] == "Off": - message.oscillation_mode = 2 - elif ( - self._attributes[DeviceAttributes.oscillation_mode] - == "Oscillation" - ): - message.oscillation_mode = 6 - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) - ) - return message - - def set_attribute(self, attr, value): - message = None - if attr in [ - DeviceAttributes.oscillate, - DeviceAttributes.oscillation_mode, - DeviceAttributes.oscillation_angle, - DeviceAttributes.tilting_angle, - ]: - message = self.set_oscillation(attr, value) - elif ( - attr == DeviceAttributes.fan_speed - and value > 0 - and not self._attributes[DeviceAttributes.power] - ): - message = MessageSet(self._protocol_version, self.subtype) - message.fan_speed = value - message.power = True - elif attr == DeviceAttributes.mode: - if value in MideaFADevice._modes: - message = MessageSet(self._protocol_version, self.subtype) - message.mode = MideaFADevice._modes.index(value) - elif not (attr == DeviceAttributes.fan_speed and value == 0): - message = MessageSet(self._protocol_version, self.subtype) - setattr(message, str(attr), value) - if message is not None: - self.build_send(message) - - def turn_on(self, fan_speed=None, mode=None): - message = MessageSet(self._protocol_version, self.subtype) - message.power = True - if fan_speed is not None: - message.fan_speed = fan_speed - if mode is None: - message.mode = mode - self.build_send(message) - - def set_customize(self, customize): - self._speed_count = self._default_speed_count - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "speed_count" in params: - self._speed_count = params.get("speed_count") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"speed_count": self._speed_count}) - - -class MideaAppliance(MideaFADevice): - pass +import json +import logging + +from .message import MessageFAResponse, MessageQuery, MessageSet + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + child_lock = "child_lock" + mode = "mode" + fan_speed = "fan_speed" + oscillate = "oscillate" + oscillation_angle = "oscillation_angle" + tilting_angle = "tilting_angle" + oscillation_mode = "oscillation_mode" + + +class MideaFADevice(MiedaDevice): + _oscillation_angles = ["Off", "30", "60", "90", "120", "180", "360"] + _tilting_angles = ["Off", "30", "60", "90", "120", "180", "360", "+60", "-60", "40"] + _oscillation_modes = [ + "Off", + "Oscillation", + "Tilting", + "Curve-W", + "Curve-8", + "Reserved", + "Both", + ] + _modes = [ + "Normal", + "Natural", + "Sleep", + "Comfort", + "Silent", + "Baby", + "Induction", + "Circulation", + "Strong", + "Soft", + "Customize", + "Warm", + "Smart", + ] + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xFA, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.child_lock: False, + DeviceAttributes.mode: 0, + DeviceAttributes.fan_speed: 0, + DeviceAttributes.oscillate: False, + DeviceAttributes.oscillation_angle: None, + DeviceAttributes.tilting_angle: None, + DeviceAttributes.oscillation_mode: None, + }, + ) + self._default_speed_count = 3 + self._speed_count = self._default_speed_count + self.set_customize(customize) + + @property + def speed_count(self): + return self._speed_count + + @property + def oscillation_angles(self): + return MideaFADevice._oscillation_angles + + @property + def tilting_angles(self): + return MideaFADevice._tilting_angles + + @property + def oscillation_modes(self): + return MideaFADevice._oscillation_modes + + @property + def preset_modes(self): + return self._modes + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageFAResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.oscillation_angle: + if value < len(MideaFADevice._oscillation_angles): + self._attributes[status] = MideaFADevice._oscillation_angles[ + value + ] + else: + self._attributes[status] = None + elif status == DeviceAttributes.tilting_angle: + if value < len(MideaFADevice._tilting_angles): + self._attributes[status] = MideaFADevice._tilting_angles[value] + else: + self._attributes[status] = None + elif status == DeviceAttributes.oscillation_mode: + if value < len(MideaFADevice._oscillation_modes): + self._attributes[status] = MideaFADevice._oscillation_modes[ + value + ] + else: + self._attributes[status] = None + elif status == DeviceAttributes.mode: + if value < len(MideaFADevice._modes): + self._attributes[status] = MideaFADevice._modes[value] + else: + self._attributes[status] = None + elif status == DeviceAttributes.power: + self._attributes[status] = value + if not value: + self._attributes[DeviceAttributes.fan_speed] = 0 + elif ( + status == DeviceAttributes.fan_speed + and not self._attributes[DeviceAttributes.power] + ): + self._attributes[status] = 0 + else: + self._attributes[status] = value + new_status[str(status)] = self._attributes[status] + return new_status + + def set_oscillation(self, attr, value): + message = None + if self._attributes[attr] != value: + if attr == DeviceAttributes.oscillate: + message = MessageSet(self._protocol_version, self.subtype) + message.oscillate = value + if value: + message.oscillation_angle = 3 # 90 + message.oscillation_mode = 1 # Oscillation + elif attr == DeviceAttributes.oscillation_mode and ( + value in MideaFADevice._oscillation_modes or not value + ): + message = MessageSet(self._protocol_version, self.subtype) + if value == "Off" or not value: + message.oscillate = False + else: + message.oscillate = True + message.oscillation_mode = MideaFADevice._oscillation_modes.index( + value + ) + if value == "Oscillation": + if ( + self._attributes[DeviceAttributes.oscillation_angle] + == "Off" + ): + message.oscillation_angle = 3 # 90 + else: + message.oscillation_angle = ( + MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] + ) + ) + elif value == "Tilting": + if self._attributes[DeviceAttributes.tilting_angle] == "Off": + message.tilting_angle = 3 # 90 + else: + message.tilting_angle = MideaFADevice._tilting_angles.index( + self._attributes[DeviceAttributes.tilting_angle] + ) + else: + if ( + self._attributes[DeviceAttributes.oscillation_angle] + == "Off" + ): + message.oscillation_angle = 3 # 90 + else: + message.oscillation_angle = ( + MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] + ) + ) + if self._attributes[DeviceAttributes.tilting_angle] == "Off": + message.tilting_angle = 3 # 90 + else: + message.tilting_angle = MideaFADevice._tilting_angles.index( + self._attributes[DeviceAttributes.tilting_angle] + ) + elif attr == DeviceAttributes.oscillation_angle and ( + value in MideaFADevice._oscillation_angles or not value + ): + message = MessageSet(self._protocol_version, self.subtype) + if value == "Off" or not value: + if self._attributes[DeviceAttributes.tilting_angle] == "Off": + message.oscillate = False + else: + message.oscillate = True + message.oscillation_mode = 2 + message.tilting_angle = MideaFADevice._tilting_angles.index( + self._attributes[DeviceAttributes.tilting_angle] + ) + else: + message.oscillation_angle = MideaFADevice._oscillation_angles.index( + value + ) + message.oscillate = True + if self._attributes[DeviceAttributes.tilting_angle] == "Off": + message.oscillation_mode = 1 + elif ( + self._attributes[DeviceAttributes.oscillation_mode] == "Tilting" + ): + message.oscillation_mode = 6 + message.tilting_angle = MideaFADevice._tilting_angles.index( + self._attributes[DeviceAttributes.tilting_angle] + ) + elif attr == DeviceAttributes.tilting_angle and ( + value in MideaFADevice._tilting_angles or not value + ): + message = MessageSet(self._protocol_version, self.subtype) + if value == "Off" or not value: + if self._attributes[DeviceAttributes.oscillation_angle] == "Off": + message.oscillate = False + else: + message.oscillate = True + message.oscillation_mode = 1 + message.oscillation_angle = ( + MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] + ) + ) + else: + message.tilting_angle = MideaFADevice._tilting_angles.index(value) + message.oscillate = True + if self._attributes[DeviceAttributes.oscillation_angle] == "Off": + message.oscillation_mode = 2 + elif ( + self._attributes[DeviceAttributes.oscillation_mode] + == "Oscillation" + ): + message.oscillation_mode = 6 + message.oscillation_angle = ( + MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] + ) + ) + return message + + def set_attribute(self, attr, value): + message = None + if attr in [ + DeviceAttributes.oscillate, + DeviceAttributes.oscillation_mode, + DeviceAttributes.oscillation_angle, + DeviceAttributes.tilting_angle, + ]: + message = self.set_oscillation(attr, value) + elif ( + attr == DeviceAttributes.fan_speed + and value > 0 + and not self._attributes[DeviceAttributes.power] + ): + message = MessageSet(self._protocol_version, self.subtype) + message.fan_speed = value + message.power = True + elif attr == DeviceAttributes.mode: + if value in MideaFADevice._modes: + message = MessageSet(self._protocol_version, self.subtype) + message.mode = MideaFADevice._modes.index(value) + elif not (attr == DeviceAttributes.fan_speed and value == 0): + message = MessageSet(self._protocol_version, self.subtype) + setattr(message, str(attr), value) + if message is not None: + self.build_send(message) + + def turn_on(self, fan_speed=None, mode=None): + message = MessageSet(self._protocol_version, self.subtype) + message.power = True + if fan_speed is not None: + message.fan_speed = fan_speed + if mode is None: + message.mode = mode + self.build_send(message) + + def set_customize(self, customize): + self._speed_count = self._default_speed_count + if customize and len(customize) > 0: + try: + params = json.loads(customize) + if params and "speed_count" in params: + self._speed_count = params.get("speed_count") + except Exception as e: + _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") + self.update_all({"speed_count": self._speed_count}) + + +class MideaAppliance(MideaFADevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/fa/message.py b/custom_components/midea_ac_lan/midea/devices/fa/message.py index 69381737..6e11a307 100644 --- a/custom_components/midea_ac_lan/midea/devices/fa/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fa/message.py @@ -1,197 +1,197 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xFA, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageFABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=None, - ) - - @property - def body(self): - return bytearray([]) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageFABase): - def __init__(self, protocol_version, subtype): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x00, - ) - self._subtype = subtype - self.power = None - self.lock = None - self.mode = None - self.fan_speed = None - self.oscillate = None - self.oscillation_angle = None - self.oscillation_mode = None - self.tilting_angle = None - - @property - def _body(self): - if 1 <= self._subtype <= 10 or self._subtype == 161: - _body_return = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - if self._subtype != 10: - _body_return[13] = 0xFF - else: - _body_return = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - if self.power is not None: - if self.power: - _body_return[3] = 1 - else: - _body_return[3] = 0 - if self.lock is not None: - if self.lock: - _body_return[2] = 1 - else: - _body_return[2] = 2 - if self.mode is not None: - _body_return[3] = 1 | (((self.mode + 1) << 1) & 0x1E) - if self.fan_speed is not None and 1 <= self.fan_speed <= 26: - _body_return[4] = self.fan_speed - if self.oscillate is not None: - if self.oscillate: - _body_return[7] = 1 - else: - _body_return[7] = 0 - if self.oscillation_angle is not None: - _body_return[7] = ( - 1 | _body_return[7] | ((self.oscillation_angle << 4) & 0x70) - ) - if self.oscillation_mode is not None: - _body_return[7] = ( - 1 | _body_return[7] | ((self.oscillation_mode << 1) & 0x0E) - ) - if self.tilting_angle is not None and len(_body_return) > 24: - _body_return[24] = self.tilting_angle - return _body_return - - -class FAGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - lock = body[3] & 0x03 - if lock == 1: - self.child_lock = True - else: - self.child_lock = False - self.power = (body[4] & 0x01) > 0 - mode = (body[4] & 0x1E) >> 1 - if mode > 0: - self.mode = mode - 1 - fan_speed = body[5] - if 1 <= fan_speed <= 26: - self.fan_speed = fan_speed - else: - self.fan_speed = 0 - self.oscillate = (body[8] & 0x01) > 0 - self.oscillation_angle = (body[8] & 0x70) >> 4 - self.oscillation_mode = (body[8] & 0x0E) >> 1 - self.tilting_angle = body[25] if len(body) > 25 else 0 - - -class MessageFAResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: - self.set_body(FAGeneralMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageFABase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xFA, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageFABase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=None, + ) + + @property + def body(self): + return bytearray([]) + + @property + def _body(self): + return bytearray([]) + + +class MessageSet(MessageFABase): + def __init__(self, protocol_version, subtype): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x00, + ) + self._subtype = subtype + self.power = None + self.lock = None + self.mode = None + self.fan_speed = None + self.oscillate = None + self.oscillation_angle = None + self.oscillation_mode = None + self.tilting_angle = None + + @property + def _body(self): + if 1 <= self._subtype <= 10 or self._subtype == 161: + _body_return = bytearray( + [ + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + if self._subtype != 10: + _body_return[13] = 0xFF + else: + _body_return = bytearray( + [ + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + if self.power is not None: + if self.power: + _body_return[3] = 1 + else: + _body_return[3] = 0 + if self.lock is not None: + if self.lock: + _body_return[2] = 1 + else: + _body_return[2] = 2 + if self.mode is not None: + _body_return[3] = 1 | (((self.mode + 1) << 1) & 0x1E) + if self.fan_speed is not None and 1 <= self.fan_speed <= 26: + _body_return[4] = self.fan_speed + if self.oscillate is not None: + if self.oscillate: + _body_return[7] = 1 + else: + _body_return[7] = 0 + if self.oscillation_angle is not None: + _body_return[7] = ( + 1 | _body_return[7] | ((self.oscillation_angle << 4) & 0x70) + ) + if self.oscillation_mode is not None: + _body_return[7] = ( + 1 | _body_return[7] | ((self.oscillation_mode << 1) & 0x0E) + ) + if self.tilting_angle is not None and len(_body_return) > 24: + _body_return[24] = self.tilting_angle + return _body_return + + +class FAGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + lock = body[3] & 0x03 + if lock == 1: + self.child_lock = True + else: + self.child_lock = False + self.power = (body[4] & 0x01) > 0 + mode = (body[4] & 0x1E) >> 1 + if mode > 0: + self.mode = mode - 1 + fan_speed = body[5] + if 1 <= fan_speed <= 26: + self.fan_speed = fan_speed + else: + self.fan_speed = 0 + self.oscillate = (body[8] & 0x01) > 0 + self.oscillation_angle = (body[8] & 0x70) >> 4 + self.oscillation_mode = (body[8] & 0x0E) >> 1 + self.tilting_angle = body[25] if len(body) > 25 else 0 + + +class MessageFAResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify1, + ]: + self.set_body(FAGeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fb/device.py b/custom_components/midea_ac_lan/midea/devices/fb/device.py index 01e84497..b54565d6 100644 --- a/custom_components/midea_ac_lan/midea/devices/fb/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fb/device.py @@ -1,109 +1,109 @@ -import logging - -from .message import MessageFBResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - heating_level = "heating_level" - target_temperature = "target_temperature" - current_temperature = "current_temperature" - child_lock = "child_lock" - - -class MideaFBDevice(MiedaDevice): - _modes = { - 0x01: "Auto", - 0x02: "ECO", - 0x03: "Sleep", - 0x04: "Anti-freezing", - 0x05: "Comfort", - 0x06: "Constant-temperature", - 0x07: "Normal", - 0x08: "Fast-heating", - 0x10: "Standby", - } - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xFB, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: None, - DeviceAttributes.heating_level: 0, - DeviceAttributes.target_temperature: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.child_lock: False, - }, - ) - - @property - def modes(self): - return list(MideaFBDevice._modes.values()) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageFBResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - if value in MideaFBDevice._modes.keys(): - self._attributes[status] = MideaFBDevice._modes.get(value) - else: - self._attributes[status] = None - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.mode: - message = MessageSet(self._protocol_version, self.subtype) - if value in MideaFBDevice._modes.values(): - message.mode = list(MideaFBDevice._modes.keys())[ - list(MideaFBDevice._modes.values()).index(value) - ] - else: - message = MessageSet(self._protocol_version, self.subtype) - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(MideaFBDevice): - pass +import logging + +from .message import MessageFBResponse, MessageQuery, MessageSet + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + mode = "mode" + heating_level = "heating_level" + target_temperature = "target_temperature" + current_temperature = "current_temperature" + child_lock = "child_lock" + + +class MideaFBDevice(MiedaDevice): + _modes = { + 0x01: "Auto", + 0x02: "ECO", + 0x03: "Sleep", + 0x04: "Anti-freezing", + 0x05: "Comfort", + 0x06: "Constant-temperature", + 0x07: "Normal", + 0x08: "Fast-heating", + 0x10: "Standby", + } + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xFB, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.mode: None, + DeviceAttributes.heating_level: 0, + DeviceAttributes.target_temperature: None, + DeviceAttributes.current_temperature: None, + DeviceAttributes.child_lock: False, + }, + ) + + @property + def modes(self): + return list(MideaFBDevice._modes.values()) + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageFBResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.mode: + if value in MideaFBDevice._modes.keys(): + self._attributes[status] = MideaFBDevice._modes.get(value) + else: + self._attributes[status] = None + else: + self._attributes[status] = value + new_status[str(status)] = self._attributes[status] + return new_status + + def set_attribute(self, attr, value): + if attr == DeviceAttributes.mode: + message = MessageSet(self._protocol_version, self.subtype) + if value in MideaFBDevice._modes.values(): + message.mode = list(MideaFBDevice._modes.keys())[ + list(MideaFBDevice._modes.values()).index(value) + ] + else: + message = MessageSet(self._protocol_version, self.subtype) + setattr(message, str(attr), value) + self.build_send(message) + + +class MideaAppliance(MideaFBDevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/fb/message.py b/custom_components/midea_ac_lan/midea/devices/fb/message.py index 9aea4bdd..57e613d9 100644 --- a/custom_components/midea_ac_lan/midea/devices/fb/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fb/message.py @@ -1,134 +1,134 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFBBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xFB, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageFBBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=None, - ) - - @property - def body(self): - return bytearray([]) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageFBBase): - def __init__(self, protocol_version, subtype): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x00, - ) - self._subtype = subtype - self.power = None - self.mode = None - self.heating_level = None - self.target_temperature = None - self.child_lock = None - - @property - def body(self): - power = 0 if self.power is None else (0x01 if self.power else 0x02) - mode = 0 if self.mode is None else self.mode - heating_level = ( - 0 - if self.heating_level is None - else ( - int(self.heating_level if 1 <= self.heating_level <= 10 else 0) & 0xFF - ) - ) - target_temperature = ( - 0 - if self.target_temperature is None - else ( - int( - (self.target_temperature + 41) - if -40 <= self.target_temperature <= 50 - else (0x80 if self.target_temperature in [0x80, 87] else 0) - ) - & 0xFF - ) - ) - child_lock = ( - 0xFF if self.child_lock is None else (0x01 if self.child_lock else 0x00) - ) - _return_body = bytearray( - [ - power, - 0x00, - 0x00, - 0x00, - mode, - heating_level, - target_temperature, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - child_lock, - 0x00, - ] - ) - if self._subtype > 5: - _return_body += bytearray([0x00, 0x00, 0x00]) - return _return_body - - @property - def _body(self): - return bytearray([]) - - -class FBGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[0] & 0x01) not in [0, 2] - self.mode = body[4] - self.heating_level = body[5] - self.target_temperature = body[6] - 41 - if 1 <= body[7] <= 100: - self.target_humidity = body[7] - self.current_humidity = body[12] - self.current_temperature = body[13] - 20 - if len(body) > 18: - self.child_lock = (body[18] & 0x01) > 0 - if len(body) > 21: - self.energy_consumption = (body[21] << 8) + body[20] - - -class MessageFBResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: - self.set_body(FBGeneralMessageBody(super().body)) - self.set_attr() +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageFBBase(MessageRequest): + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xFB, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + + @property + def _body(self): + raise NotImplementedError + + +class MessageQuery(MessageFBBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=None, + ) + + @property + def body(self): + return bytearray([]) + + @property + def _body(self): + return bytearray([]) + + +class MessageSet(MessageFBBase): + def __init__(self, protocol_version, subtype): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x00, + ) + self._subtype = subtype + self.power = None + self.mode = None + self.heating_level = None + self.target_temperature = None + self.child_lock = None + + @property + def body(self): + power = 0 if self.power is None else (0x01 if self.power else 0x02) + mode = 0 if self.mode is None else self.mode + heating_level = ( + 0 + if self.heating_level is None + else ( + int(self.heating_level if 1 <= self.heating_level <= 10 else 0) & 0xFF + ) + ) + target_temperature = ( + 0 + if self.target_temperature is None + else ( + int( + (self.target_temperature + 41) + if -40 <= self.target_temperature <= 50 + else (0x80 if self.target_temperature in [0x80, 87] else 0) + ) + & 0xFF + ) + ) + child_lock = ( + 0xFF if self.child_lock is None else (0x01 if self.child_lock else 0x00) + ) + _return_body = bytearray( + [ + power, + 0x00, + 0x00, + 0x00, + mode, + heating_level, + target_temperature, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + child_lock, + 0x00, + ] + ) + if self._subtype > 5: + _return_body += bytearray([0x00, 0x00, 0x00]) + return _return_body + + @property + def _body(self): + return bytearray([]) + + +class FBGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[0] & 0x01) not in [0, 2] + self.mode = body[4] + self.heating_level = body[5] + self.target_temperature = body[6] - 41 + if 1 <= body[7] <= 100: + self.target_humidity = body[7] + self.current_humidity = body[12] + self.current_temperature = body[13] - 20 + if len(body) > 18: + self.child_lock = (body[18] & 0x01) > 0 + if len(body) > 21: + self.energy_consumption = (body[21] << 8) + body[20] + + +class MessageFBResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify1, + ]: + self.set_body(FBGeneralMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fc/device.py b/custom_components/midea_ac_lan/midea/devices/fc/device.py index 94e19f76..f536e0d5 100644 --- a/custom_components/midea_ac_lan/midea/devices/fc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fc/device.py @@ -1,238 +1,238 @@ -import json -import logging - -from .message import MessageFCResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - fan_speed = "fan_speed" - anion = "anion" - screen_display = "screen_display" - detect_mode = "detect_mode" - pm25 = "pm25" - tvoc = "tvoc" - hcho = "hcho" - child_lock = "child_lock" - prompt_tone = "prompt_tone" - filter1_life = "filter1_life" - filter2_life = "filter2_life" - standby = "standby" - - -class MideaFCDevice(MiedaDevice): - _modes = { - 0x00: "Standby", - 0x10: "Auto", - 0x20: "Manual", - 0x30: "Sleep", - 0x40: "Fast", - 0x50: "Smoke", - } - _speeds = {1: "Auto", 4: "Standby", 39: "Low", 59: "Medium", 80: "High"} - _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} - _detect_modes = ["Off", "PM 2.5", "Methanal"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xFC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: None, - DeviceAttributes.fan_speed: None, - DeviceAttributes.anion: False, - DeviceAttributes.standby: False, - DeviceAttributes.screen_display: None, - DeviceAttributes.detect_mode: None, - DeviceAttributes.pm25: None, - DeviceAttributes.tvoc: None, - DeviceAttributes.hcho: None, - DeviceAttributes.child_lock: False, - DeviceAttributes.prompt_tone: True, - DeviceAttributes.filter1_life: None, - DeviceAttributes.filter2_life: None, - }, - ) - - self._standby_detect_default = [40, 20] - self._standby_detect = self._standby_detect_default - self.set_customize(customize) - - @property - def modes(self): - return list(MideaFCDevice._modes.values()) - - @property - def fan_speeds(self): - return list(MideaFCDevice._speeds.values()) - - @property - def screen_displays(self): - return list(MideaFCDevice._screen_displays.values()) - - @property - def detect_modes(self): - return self._detect_modes - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageFCResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - if value in MideaFCDevice._modes.keys(): - self._attributes[status] = MideaFCDevice._modes.get(value) - else: - self._attributes[status] = None - elif status == DeviceAttributes.fan_speed: - if value in MideaFCDevice._speeds.keys(): - self._attributes[status] = MideaFCDevice._speeds.get(value) - else: - self._attributes[status] = None - elif status == DeviceAttributes.screen_display: - if value in MideaFCDevice._screen_displays.keys(): - self._attributes[status] = MideaFCDevice._screen_displays.get( - value - ) - else: - self._attributes[status] = None - elif status == DeviceAttributes.detect_mode: - if value < len(MideaFCDevice._detect_modes): - self._attributes[status] = MideaFCDevice._detect_modes[value] - else: - self._attributes[status] = None - else: - - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.child_lock = self._attributes[DeviceAttributes.child_lock] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.anion = self._attributes[DeviceAttributes.anion] - message.standby = self._attributes[DeviceAttributes.standby] - message.screen_display = self._attributes[DeviceAttributes.screen_display] - message.detect_mode = ( - 0 - if self._attributes[DeviceAttributes.detect_mode] is None - else MideaFCDevice._detect_modes.index( - self._attributes[DeviceAttributes.detect_mode] - ) - ) - message.mode = ( - 0x10 - if self._attributes[DeviceAttributes.mode] is None - else list(MideaFCDevice._modes.keys())[ - list(MideaFCDevice._modes.values()).index( - self._attributes[DeviceAttributes.mode] - ) - ] - ) - message.fan_speed = ( - 39 - if self._attributes[DeviceAttributes.fan_speed] is None - else list(MideaFCDevice._speeds.keys())[ - list(MideaFCDevice._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - ) - message.screen_display = ( - 0 - if self._attributes[DeviceAttributes.screen_display] is None - else list(MideaFCDevice._screen_displays.keys())[ - list(MideaFCDevice._screen_displays.values()).index( - self._attributes[DeviceAttributes.screen_display] - ) - ] - ) - message.standby_detect = self._standby_detect - return message - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.prompt_tone: - self._attributes[DeviceAttributes.prompt_tone] = value - self.update_all({DeviceAttributes.prompt_tone.value: value}) - else: - message = self.make_message_set() - if attr == DeviceAttributes.mode: - if value in MideaFCDevice._modes.values(): - message.mode = list(MideaFCDevice._modes.keys())[ - list(MideaFCDevice._modes.values()).index(value) - ] - elif attr == DeviceAttributes.fan_speed: - if value in MideaFCDevice._speeds.values(): - message.fan_speed = list(MideaFCDevice._speeds.keys())[ - list(MideaFCDevice._speeds.values()).index(value) - ] - elif attr == DeviceAttributes.screen_display: - if value in MideaFCDevice._screen_displays.values(): - message.screen_display = list( - MideaFCDevice._screen_displays.keys() - )[list(MideaFCDevice._screen_displays.values()).index(value)] - elif not value: - message.screen_display = 7 - elif attr == DeviceAttributes.detect_mode: - if value in MideaFCDevice._detect_modes: - message.detect_mode = MideaFCDevice._detect_modes.index(value) - elif not value: - message.detect_mode = 0 - else: - setattr(message, str(attr), value) - self.build_send(message) - - def set_customize(self, customize): - self._standby_detect = self._standby_detect_default - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "standby_detect" in params: - settings = params.get("standby_detect") - if len(settings) == 2 and settings[0] > settings[1]: - self._standby_detect = settings - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"standby_detect": self._standby_detect}) - - -class MideaAppliance(MideaFCDevice): - pass +import json +import logging + +from .message import MessageFCResponse, MessageQuery, MessageSet + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + mode = "mode" + fan_speed = "fan_speed" + anion = "anion" + screen_display = "screen_display" + detect_mode = "detect_mode" + pm25 = "pm25" + tvoc = "tvoc" + hcho = "hcho" + child_lock = "child_lock" + prompt_tone = "prompt_tone" + filter1_life = "filter1_life" + filter2_life = "filter2_life" + standby = "standby" + + +class MideaFCDevice(MiedaDevice): + _modes = { + 0x00: "Standby", + 0x10: "Auto", + 0x20: "Manual", + 0x30: "Sleep", + 0x40: "Fast", + 0x50: "Smoke", + } + _speeds = {1: "Auto", 4: "Standby", 39: "Low", 59: "Medium", 80: "High"} + _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} + _detect_modes = ["Off", "PM 2.5", "Methanal"] + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xFC, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.mode: None, + DeviceAttributes.fan_speed: None, + DeviceAttributes.anion: False, + DeviceAttributes.standby: False, + DeviceAttributes.screen_display: None, + DeviceAttributes.detect_mode: None, + DeviceAttributes.pm25: None, + DeviceAttributes.tvoc: None, + DeviceAttributes.hcho: None, + DeviceAttributes.child_lock: False, + DeviceAttributes.prompt_tone: True, + DeviceAttributes.filter1_life: None, + DeviceAttributes.filter2_life: None, + }, + ) + + self._standby_detect_default = [40, 20] + self._standby_detect = self._standby_detect_default + self.set_customize(customize) + + @property + def modes(self): + return list(MideaFCDevice._modes.values()) + + @property + def fan_speeds(self): + return list(MideaFCDevice._speeds.values()) + + @property + def screen_displays(self): + return list(MideaFCDevice._screen_displays.values()) + + @property + def detect_modes(self): + return self._detect_modes + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + message = MessageFCResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.mode: + if value in MideaFCDevice._modes.keys(): + self._attributes[status] = MideaFCDevice._modes.get(value) + else: + self._attributes[status] = None + elif status == DeviceAttributes.fan_speed: + if value in MideaFCDevice._speeds.keys(): + self._attributes[status] = MideaFCDevice._speeds.get(value) + else: + self._attributes[status] = None + elif status == DeviceAttributes.screen_display: + if value in MideaFCDevice._screen_displays.keys(): + self._attributes[status] = MideaFCDevice._screen_displays.get( + value + ) + else: + self._attributes[status] = None + elif status == DeviceAttributes.detect_mode: + if value < len(MideaFCDevice._detect_modes): + self._attributes[status] = MideaFCDevice._detect_modes[value] + else: + self._attributes[status] = None + else: + + self._attributes[status] = value + new_status[str(status)] = self._attributes[status] + return new_status + + def make_message_set(self): + message = MessageSet(self._protocol_version) + message.power = self._attributes[DeviceAttributes.power] + message.child_lock = self._attributes[DeviceAttributes.child_lock] + message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] + message.anion = self._attributes[DeviceAttributes.anion] + message.standby = self._attributes[DeviceAttributes.standby] + message.screen_display = self._attributes[DeviceAttributes.screen_display] + message.detect_mode = ( + 0 + if self._attributes[DeviceAttributes.detect_mode] is None + else MideaFCDevice._detect_modes.index( + self._attributes[DeviceAttributes.detect_mode] + ) + ) + message.mode = ( + 0x10 + if self._attributes[DeviceAttributes.mode] is None + else list(MideaFCDevice._modes.keys())[ + list(MideaFCDevice._modes.values()).index( + self._attributes[DeviceAttributes.mode] + ) + ] + ) + message.fan_speed = ( + 39 + if self._attributes[DeviceAttributes.fan_speed] is None + else list(MideaFCDevice._speeds.keys())[ + list(MideaFCDevice._speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + ) + ] + ) + message.screen_display = ( + 0 + if self._attributes[DeviceAttributes.screen_display] is None + else list(MideaFCDevice._screen_displays.keys())[ + list(MideaFCDevice._screen_displays.values()).index( + self._attributes[DeviceAttributes.screen_display] + ) + ] + ) + message.standby_detect = self._standby_detect + return message + + def set_attribute(self, attr, value): + if attr == DeviceAttributes.prompt_tone: + self._attributes[DeviceAttributes.prompt_tone] = value + self.update_all({DeviceAttributes.prompt_tone.value: value}) + else: + message = self.make_message_set() + if attr == DeviceAttributes.mode: + if value in MideaFCDevice._modes.values(): + message.mode = list(MideaFCDevice._modes.keys())[ + list(MideaFCDevice._modes.values()).index(value) + ] + elif attr == DeviceAttributes.fan_speed: + if value in MideaFCDevice._speeds.values(): + message.fan_speed = list(MideaFCDevice._speeds.keys())[ + list(MideaFCDevice._speeds.values()).index(value) + ] + elif attr == DeviceAttributes.screen_display: + if value in MideaFCDevice._screen_displays.values(): + message.screen_display = list( + MideaFCDevice._screen_displays.keys() + )[list(MideaFCDevice._screen_displays.values()).index(value)] + elif not value: + message.screen_display = 7 + elif attr == DeviceAttributes.detect_mode: + if value in MideaFCDevice._detect_modes: + message.detect_mode = MideaFCDevice._detect_modes.index(value) + elif not value: + message.detect_mode = 0 + else: + setattr(message, str(attr), value) + self.build_send(message) + + def set_customize(self, customize): + self._standby_detect = self._standby_detect_default + if customize and len(customize) > 0: + try: + params = json.loads(customize) + if params and "standby_detect" in params: + settings = params.get("standby_detect") + if len(settings) == 2 and settings[0] > settings[1]: + self._standby_detect = settings + except Exception as e: + _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") + self.update_all({"standby_detect": self._standby_detect}) + + +class MideaAppliance(MideaFCDevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/fc/message.py b/custom_components/midea_ac_lan/midea/devices/fc/message.py index 00d0bce0..db391f8e 100644 --- a/custom_components/midea_ac_lan/midea/devices/fc/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fc/message.py @@ -1,209 +1,209 @@ -from ...core.crc8 import calculate -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFCBase(MessageRequest): - _message_serial = 0 - - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xFC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - MessageFCBase._message_serial += 1 - if MessageFCBase._message_serial >= 254: - MessageFCBase._message_serial = 1 - self._message_id = MessageFCBase._message_serial - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) - body.append(calculate(body)) - return body - - -class MessageQuery(MessageFCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray( - [ - 0x00, - 0x00, - 0xFF, - 0x03, - 0x00, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageSet(MessageFCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x48, - ) - self.power = False - self.mode = 0 - self.fan_speed = 0 - self.child_lock = False - self.prompt_tone = False - self.anion = False - self.standby = False - self.screen_display = 0 - self.detect_mode = 0 - self.standby_detect = [40, 20] - - @property - def _body(self): - # byte1 power - power = 0x01 if self.power else 0x00 - detect = 0x08 if self.detect_mode > 0 else 0x00 - detect_mode = (self.detect_mode - 1) if self.detect_mode > 0 else 0 - # byte2 mode - # byte3 fan_speed - # byte 8 child_lock - child_lock = 0x80 if self.child_lock else 0x00 - # byte 9 anion - anion = 0x20 if self.anion else 0x00 - # byte 10 prompt_tone - prompt_tone = 0x40 if self.prompt_tone else 0x00 - # byte 15/16/17 standby - if self.standby: - standby = 0x04 - standby_detect_high = self.standby_detect[0] - standby_detect_low = self.standby_detect[1] - else: - standby = 0x08 - standby_detect_high = 0 - standby_detect_low = 0 - return bytearray( - [ - power | prompt_tone | detect | 0x02, - self.mode, - self.fan_speed, - 0x00, - 0x00, - 0x00, - 0x00, - child_lock, - self.screen_display, - anion, - 0x00, - 0x00, - 0x00, - detect_mode, - standby, - standby_detect_high, - standby_detect_low, - 0x00, - 0x00, - 0x00, - ] - ) - - -class FCGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.mode = body[2] & 0xF0 - self.fan_speed = body[3] & 0x7F - self.screen_display = body[9] & 0x07 - if len(body) > 14 and body[14] != 0xFF: - self.pm25 = body[13] + (body[14] << 8) - else: - self.pm25 = None - if len(body) > 15 and body[15] != 0xFF: - self.tvoc = body[15] - else: - self.tvoc = None - self.anion = (body[19] & 0x40 > 0) if len(body) > 19 else False - self.standby = ((body[34] & 0xFF) == 0x14) if len(body) > 34 else False - self.child_lock = (body[8] & 0x80 > 0) if len(body) > 8 else False - if len(body) > 23: - self.filter1_life = body[23] - if len(body) > 24: - self.filter2_life = body[24] - if len(body) > 29: - if (body[1] & 0x08) > 0: - self.detect_mode = body[29] + 1 - else: - self.detect_mode = 0 - if len(body) > 38 and body[38] != 0xFF: - self.hcho = body[37] + (body[38] << 8) - else: - self.hcho = None - - -class FCNotifyMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.mode = body[2] & 0xF0 - self.fan_speed = body[3] & 0x7F - self.screen_display = body[9] & 0x07 - if len(body) > 14 and body[14] != 0xFF: - self.pm25 = body[13] + (body[14] << 8) - else: - self.pm25 = None - if len(body) > 15 and body[15] != 0xFF: - self.tvoc = body[15] - else: - self.tvoc = None - self.anion = (body[10] & 0x20 > 0) if len(body) > 10 else False - self.standby = (body[27] & 0x14 == 0xFF) if len(body) > 27 else False - self.child_lock = (body[10] & 0x10 > 0) if len(body) > 10 else False - if len(body) > 22: - if (body[1] & 0x08) > 0: - self.detect_mode = body[22] + 1 - else: - self.detect_mode = 0 - if len(body) > 31 and body[31] != 0xFF: - self.hcho = body[30] + (body[31] << 8) - else: - self.hcho = None - - -class MessageFCResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.body_type in [0xB0, 0xB1]: - pass - else: - if ( - self.message_type - in [MessageType.query, MessageType.set, MessageType.notify1] - and self.body_type == 0xC8 - ): - self.set_body(FCGeneralMessageBody(super().body)) - elif self.message_type == MessageType.notify1 and self.body_type == 0xA0: - self.set_body(FCNotifyMessageBody(super().body)) - self.set_attr() +from ...core.crc8 import calculate +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageFCBase(MessageRequest): + _message_serial = 0 + + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xFC, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + MessageFCBase._message_serial += 1 + if MessageFCBase._message_serial >= 254: + MessageFCBase._message_serial = 1 + self._message_id = MessageFCBase._message_serial + + @property + def _body(self): + raise NotImplementedError + + @property + def body(self): + body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) + body.append(calculate(body)) + return body + + +class MessageQuery(MessageFCBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x41, + ) + + @property + def _body(self): + return bytearray( + [ + 0x00, + 0x00, + 0xFF, + 0x03, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class MessageSet(MessageFCBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x48, + ) + self.power = False + self.mode = 0 + self.fan_speed = 0 + self.child_lock = False + self.prompt_tone = False + self.anion = False + self.standby = False + self.screen_display = 0 + self.detect_mode = 0 + self.standby_detect = [40, 20] + + @property + def _body(self): + # byte1 power + power = 0x01 if self.power else 0x00 + detect = 0x08 if self.detect_mode > 0 else 0x00 + detect_mode = (self.detect_mode - 1) if self.detect_mode > 0 else 0 + # byte2 mode + # byte3 fan_speed + # byte 8 child_lock + child_lock = 0x80 if self.child_lock else 0x00 + # byte 9 anion + anion = 0x20 if self.anion else 0x00 + # byte 10 prompt_tone + prompt_tone = 0x40 if self.prompt_tone else 0x00 + # byte 15/16/17 standby + if self.standby: + standby = 0x04 + standby_detect_high = self.standby_detect[0] + standby_detect_low = self.standby_detect[1] + else: + standby = 0x08 + standby_detect_high = 0 + standby_detect_low = 0 + return bytearray( + [ + power | prompt_tone | detect | 0x02, + self.mode, + self.fan_speed, + 0x00, + 0x00, + 0x00, + 0x00, + child_lock, + self.screen_display, + anion, + 0x00, + 0x00, + 0x00, + detect_mode, + standby, + standby_detect_high, + standby_detect_low, + 0x00, + 0x00, + 0x00, + ] + ) + + +class FCGeneralMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[1] & 0x01) > 0 + self.mode = body[2] & 0xF0 + self.fan_speed = body[3] & 0x7F + self.screen_display = body[9] & 0x07 + if len(body) > 14 and body[14] != 0xFF: + self.pm25 = body[13] + (body[14] << 8) + else: + self.pm25 = None + if len(body) > 15 and body[15] != 0xFF: + self.tvoc = body[15] + else: + self.tvoc = None + self.anion = (body[19] & 0x40 > 0) if len(body) > 19 else False + self.standby = ((body[34] & 0xFF) == 0x14) if len(body) > 34 else False + self.child_lock = (body[8] & 0x80 > 0) if len(body) > 8 else False + if len(body) > 23: + self.filter1_life = body[23] + if len(body) > 24: + self.filter2_life = body[24] + if len(body) > 29: + if (body[1] & 0x08) > 0: + self.detect_mode = body[29] + 1 + else: + self.detect_mode = 0 + if len(body) > 38 and body[38] != 0xFF: + self.hcho = body[37] + (body[38] << 8) + else: + self.hcho = None + + +class FCNotifyMessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[1] & 0x01) > 0 + self.mode = body[2] & 0xF0 + self.fan_speed = body[3] & 0x7F + self.screen_display = body[9] & 0x07 + if len(body) > 14 and body[14] != 0xFF: + self.pm25 = body[13] + (body[14] << 8) + else: + self.pm25 = None + if len(body) > 15 and body[15] != 0xFF: + self.tvoc = body[15] + else: + self.tvoc = None + self.anion = (body[10] & 0x20 > 0) if len(body) > 10 else False + self.standby = (body[27] & 0x14 == 0xFF) if len(body) > 27 else False + self.child_lock = (body[10] & 0x10 > 0) if len(body) > 10 else False + if len(body) > 22: + if (body[1] & 0x08) > 0: + self.detect_mode = body[22] + 1 + else: + self.detect_mode = 0 + if len(body) > 31 and body[31] != 0xFF: + self.hcho = body[30] + (body[31] << 8) + else: + self.hcho = None + + +class MessageFCResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.body_type in [0xB0, 0xB1]: + pass + else: + if ( + self.message_type + in [MessageType.query, MessageType.set, MessageType.notify1] + and self.body_type == 0xC8 + ): + self.set_body(FCGeneralMessageBody(super().body)) + elif self.message_type == MessageType.notify1 and self.body_type == 0xA0: + self.set_body(FCNotifyMessageBody(super().body)) + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fd/device.py b/custom_components/midea_ac_lan/midea/devices/fd/device.py index 323b45b2..f310119a 100644 --- a/custom_components/midea_ac_lan/midea/devices/fd/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fd/device.py @@ -1,207 +1,207 @@ -import logging - -from .message import MessageFDResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.enum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - fan_speed = "fan_speed" - prompt_tone = "prompt_tone" - target_humidity = "target_humidity" - current_humidity = "current_humidity" - current_temperature = "current_temperature" - tank = "tank" - mode = "mode" - screen_display = "screen_display" - disinfect = "disinfect" - - -class MideaFDDevice(MiedaDevice): - _modes = [ - "Manual", - "Auto", - "Continuous", - "Living-Room", - "Bed-Room", - "Kitchen", - "Sleep", - ] - _speeds_old = { - 1: "Lowest", - 40: "Low", - 60: "Medium", - 80: "High", - 102: "Auto", - 127: "Off", - } - _speeds_new = { - 1: "Lowest", - 39: "Low", - 59: "Medium", - 80: "High", - 101: "Auto", - 127: "Off", - } - _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} - _detect_modes = ["Off", "PM 2.5", "Methanal"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xFD, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.fan_speed: None, - DeviceAttributes.prompt_tone: True, - DeviceAttributes.target_humidity: 60, - DeviceAttributes.current_humidity: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.tank: 0, - DeviceAttributes.mode: None, - DeviceAttributes.screen_display: None, - DeviceAttributes.disinfect: None, - }, - ) - if self.subtype > 5: - self._speeds = MideaFDDevice._speeds_new - else: - self._speeds = MideaFDDevice._speeds_old - - @property - def modes(self): - return list(MideaFDDevice._modes) - - @property - def fan_speeds(self): - return list(self._speeds.values()) - - @property - def screen_displays(self): - return list(MideaFDDevice._screen_displays.values()) - - @property - def detect_modes(self): - return self._detect_modes - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - - message = MessageFDResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - if value <= len(MideaFDDevice._modes): - self._attributes[status] = MideaFDDevice._modes[value - 1] - else: - self._attributes[status] = None - elif status == DeviceAttributes.fan_speed: - if value in self._speeds.keys(): - self._attributes[status] = self._speeds.get(value) - else: - self._attributes[status] = None - elif status == DeviceAttributes.screen_display: - if value in MideaFDDevice._screen_displays.keys(): - self._attributes[status] = MideaFDDevice._screen_displays.get( - value - ) - else: - self._attributes[status] = None - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.screen_display = self._attributes[DeviceAttributes.screen_display] - message.disinfect = self._attributes[DeviceAttributes.disinfect] - if self._attributes[DeviceAttributes.mode] in MideaFDDevice._modes: - message.mode = ( - MideaFDDevice._modes.index(self._attributes[DeviceAttributes.mode]) + 1 - ) - else: - message.mode = 1 - message.fan_speed = ( - 40 - if self._attributes[DeviceAttributes.fan_speed] is None - else list(self._speeds.keys())[ - list(self._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - ) - message.screen_display = ( - 0 - if self._attributes[DeviceAttributes.screen_display] is None - else list(MideaFDDevice._screen_displays.keys())[ - list(MideaFDDevice._screen_displays.values()).index( - self._attributes[DeviceAttributes.screen_display] - ) - ] - ) - return message - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.prompt_tone: - self._attributes[DeviceAttributes.prompt_tone] = value - self.update_all({DeviceAttributes.prompt_tone.value: value}) - else: - message = self.make_message_set() - if attr == DeviceAttributes.mode: - if value in MideaFDDevice._modes: - message.mode = MideaFDDevice._modes.index(value) + 1 - elif attr == DeviceAttributes.fan_speed: - if value in self._speeds.values(): - message.fan_speed = list(self._speeds.keys())[ - list(self._speeds.values()).index(value) - ] - elif attr == DeviceAttributes.screen_display: - if value in MideaFDDevice._screen_displays.values(): - message.screen_display = list( - MideaFDDevice._screen_displays.keys() - )[list(MideaFDDevice._screen_displays.values()).index(value)] - elif not value: - message.screen_display = 7 - else: - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(MideaFDDevice): - pass +import logging + +from .message import MessageFDResponse, MessageQuery, MessageSet + +try: + from enum import StrEnum +except ImportError: + from ...backports.enum import StrEnum + +from ...core.device import MiedaDevice + +_LOGGER = logging.getLogger(__name__) + + +class DeviceAttributes(StrEnum): + power = "power" + fan_speed = "fan_speed" + prompt_tone = "prompt_tone" + target_humidity = "target_humidity" + current_humidity = "current_humidity" + current_temperature = "current_temperature" + tank = "tank" + mode = "mode" + screen_display = "screen_display" + disinfect = "disinfect" + + +class MideaFDDevice(MiedaDevice): + _modes = [ + "Manual", + "Auto", + "Continuous", + "Living-Room", + "Bed-Room", + "Kitchen", + "Sleep", + ] + _speeds_old = { + 1: "Lowest", + 40: "Low", + 60: "Medium", + 80: "High", + 102: "Auto", + 127: "Off", + } + _speeds_new = { + 1: "Lowest", + 39: "Low", + 59: "Medium", + 80: "High", + 101: "Auto", + 127: "Off", + } + _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} + _detect_modes = ["Off", "PM 2.5", "Methanal"] + + def __init__( + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str, + ): + super().__init__( + name=name, + device_id=device_id, + device_type=0xFD, + ip_address=ip_address, + port=port, + token=token, + key=key, + protocol=protocol, + model=model, + subtype=subtype, + attributes={ + DeviceAttributes.power: False, + DeviceAttributes.fan_speed: None, + DeviceAttributes.prompt_tone: True, + DeviceAttributes.target_humidity: 60, + DeviceAttributes.current_humidity: None, + DeviceAttributes.current_temperature: None, + DeviceAttributes.tank: 0, + DeviceAttributes.mode: None, + DeviceAttributes.screen_display: None, + DeviceAttributes.disinfect: None, + }, + ) + if self.subtype > 5: + self._speeds = MideaFDDevice._speeds_new + else: + self._speeds = MideaFDDevice._speeds_old + + @property + def modes(self): + return list(MideaFDDevice._modes) + + @property + def fan_speeds(self): + return list(self._speeds.values()) + + @property + def screen_displays(self): + return list(MideaFDDevice._screen_displays.values()) + + @property + def detect_modes(self): + return self._detect_modes + + def build_query(self): + return [MessageQuery(self._protocol_version)] + + def process_message(self, msg): + + message = MessageFDResponse(msg) + _LOGGER.debug(f"[{self.device_id}] Received: {message}") + new_status = {} + for status in self._attributes.keys(): + if hasattr(message, str(status)): + value = getattr(message, str(status)) + if status == DeviceAttributes.mode: + if value <= len(MideaFDDevice._modes): + self._attributes[status] = MideaFDDevice._modes[value - 1] + else: + self._attributes[status] = None + elif status == DeviceAttributes.fan_speed: + if value in self._speeds.keys(): + self._attributes[status] = self._speeds.get(value) + else: + self._attributes[status] = None + elif status == DeviceAttributes.screen_display: + if value in MideaFDDevice._screen_displays.keys(): + self._attributes[status] = MideaFDDevice._screen_displays.get( + value + ) + else: + self._attributes[status] = None + else: + self._attributes[status] = value + new_status[str(status)] = self._attributes[status] + return new_status + + def make_message_set(self): + message = MessageSet(self._protocol_version) + message.power = self._attributes[DeviceAttributes.power] + message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] + message.screen_display = self._attributes[DeviceAttributes.screen_display] + message.disinfect = self._attributes[DeviceAttributes.disinfect] + if self._attributes[DeviceAttributes.mode] in MideaFDDevice._modes: + message.mode = ( + MideaFDDevice._modes.index(self._attributes[DeviceAttributes.mode]) + 1 + ) + else: + message.mode = 1 + message.fan_speed = ( + 40 + if self._attributes[DeviceAttributes.fan_speed] is None + else list(self._speeds.keys())[ + list(self._speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + ) + ] + ) + message.screen_display = ( + 0 + if self._attributes[DeviceAttributes.screen_display] is None + else list(MideaFDDevice._screen_displays.keys())[ + list(MideaFDDevice._screen_displays.values()).index( + self._attributes[DeviceAttributes.screen_display] + ) + ] + ) + return message + + def set_attribute(self, attr, value): + if attr == DeviceAttributes.prompt_tone: + self._attributes[DeviceAttributes.prompt_tone] = value + self.update_all({DeviceAttributes.prompt_tone.value: value}) + else: + message = self.make_message_set() + if attr == DeviceAttributes.mode: + if value in MideaFDDevice._modes: + message.mode = MideaFDDevice._modes.index(value) + 1 + elif attr == DeviceAttributes.fan_speed: + if value in self._speeds.values(): + message.fan_speed = list(self._speeds.keys())[ + list(self._speeds.values()).index(value) + ] + elif attr == DeviceAttributes.screen_display: + if value in MideaFDDevice._screen_displays.values(): + message.screen_display = list( + MideaFDDevice._screen_displays.keys() + )[list(MideaFDDevice._screen_displays.values()).index(value)] + elif not value: + message.screen_display = 7 + else: + setattr(message, str(attr), value) + self.build_send(message) + + +class MideaAppliance(MideaFDDevice): + pass diff --git a/custom_components/midea_ac_lan/midea/devices/fd/message.py b/custom_components/midea_ac_lan/midea/devices/fd/message.py index 68310684..39aa6ec1 100644 --- a/custom_components/midea_ac_lan/midea/devices/fd/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fd/message.py @@ -1,171 +1,171 @@ -from ...core.crc8 import calculate -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFDBase(MessageRequest): - _message_serial = 0 - - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xFD, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - MessageFDBase._message_serial += 1 - if MessageFDBase._message_serial >= 254: - MessageFDBase._message_serial = 1 - self._message_id = MessageFDBase._message_serial - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) - body.append(calculate(body)) - return body - - -class MessageQuery(MessageFDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray( - [ - 0x81, - 0x00, - 0xFF, - 0x03, - 0x00, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageSet(MessageFDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x48, - ) - self.power = False - self.fan_speed = 0 - self.target_humidity = 50 - self.prompt_tone = False - self.screen_display = 0x07 - self.mode = 0x01 - self.disinfect = None - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - prompt_tone = 0x40 if self.prompt_tone else 0x00 - disinfect = 0 if self.disinfect is None else (1 if self.disinfect else 2) - return bytearray( - [ - power | prompt_tone | 0x02, - 0x00, - self.fan_speed, - 0x00, - 0x00, - 0x00, - self.target_humidity, - 0x00, - self.screen_display, - self.mode, - 0x00, - 0x00, - 0x00, - 0x00, - disinfect, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class FDC8MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.fan_speed = body[3] & 0x7F - self.target_humidity = body[7] - self.current_humidity = body[16] - self.current_temperature = (body[17] - 50) / 2 - self.tank = body[10] - self.mode = (body[8] & 0x70) >> 4 - self.screen_display = body[9] & 0x07 - if len(body) > 36: - disinfect = body[34] & 0x03 - if disinfect == 1: - self.disinfect = True - elif disinfect == 2: - self.disinfect = False - - -class FDA0MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.fan_speed = body[3] & 0x7F - self.target_humidity = body[7] - self.current_humidity = body[16] - self.current_temperature = (body[17] - 50) / 2 - self.tank = body[10] - self.mode = body[10] & 0x07 - self.screen_display = body[9] & 0x07 - if len(body) > 29: - disinfect = body[27] & 0x03 - if disinfect == 1: - self.disinfect = True - elif disinfect == 2: - self.disinfect = False - - -class MessageFDResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: - if self.body_type in [0xB0, 0xB1]: - pass - elif self.body_type == 0xA0: - self.set_body(FDA0MessageBody(super().body)) - elif self.body_type == 0xC8: - self.set_body(FDC8MessageBody(super().body)) - self.set_attr() - if ( - hasattr(self, "fan_speed") - and self.fan_speed is not None - and self.fan_speed < 5 - ): - self.fan_speed = 1 +from ...core.crc8 import calculate +from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType + + +class MessageFDBase(MessageRequest): + _message_serial = 0 + + def __init__(self, protocol_version, message_type, body_type): + super().__init__( + device_type=0xFD, + protocol_version=protocol_version, + message_type=message_type, + body_type=body_type, + ) + MessageFDBase._message_serial += 1 + if MessageFDBase._message_serial >= 254: + MessageFDBase._message_serial = 1 + self._message_id = MessageFDBase._message_serial + + @property + def _body(self): + raise NotImplementedError + + @property + def body(self): + body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) + body.append(calculate(body)) + return body + + +class MessageQuery(MessageFDBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.query, + body_type=0x41, + ) + + @property + def _body(self): + return bytearray( + [ + 0x81, + 0x00, + 0xFF, + 0x03, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class MessageSet(MessageFDBase): + def __init__(self, protocol_version): + super().__init__( + protocol_version=protocol_version, + message_type=MessageType.set, + body_type=0x48, + ) + self.power = False + self.fan_speed = 0 + self.target_humidity = 50 + self.prompt_tone = False + self.screen_display = 0x07 + self.mode = 0x01 + self.disinfect = None + + @property + def _body(self): + power = 0x01 if self.power else 0x00 + prompt_tone = 0x40 if self.prompt_tone else 0x00 + disinfect = 0 if self.disinfect is None else (1 if self.disinfect else 2) + return bytearray( + [ + power | prompt_tone | 0x02, + 0x00, + self.fan_speed, + 0x00, + 0x00, + 0x00, + self.target_humidity, + 0x00, + self.screen_display, + self.mode, + 0x00, + 0x00, + 0x00, + 0x00, + disinfect, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + ) + + +class FDC8MessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[1] & 0x01) > 0 + self.fan_speed = body[3] & 0x7F + self.target_humidity = body[7] + self.current_humidity = body[16] + self.current_temperature = (body[17] - 50) / 2 + self.tank = body[10] + self.mode = (body[8] & 0x70) >> 4 + self.screen_display = body[9] & 0x07 + if len(body) > 36: + disinfect = body[34] & 0x03 + if disinfect == 1: + self.disinfect = True + elif disinfect == 2: + self.disinfect = False + + +class FDA0MessageBody(MessageBody): + def __init__(self, body): + super().__init__(body) + self.power = (body[1] & 0x01) > 0 + self.fan_speed = body[3] & 0x7F + self.target_humidity = body[7] + self.current_humidity = body[16] + self.current_temperature = (body[17] - 50) / 2 + self.tank = body[10] + self.mode = body[10] & 0x07 + self.screen_display = body[9] & 0x07 + if len(body) > 29: + disinfect = body[27] & 0x03 + if disinfect == 1: + self.disinfect = True + elif disinfect == 2: + self.disinfect = False + + +class MessageFDResponse(MessageResponse): + def __init__(self, message): + super().__init__(message) + if self.message_type in [ + MessageType.query, + MessageType.set, + MessageType.notify1, + ]: + if self.body_type in [0xB0, 0xB1]: + pass + elif self.body_type == 0xA0: + self.set_body(FDA0MessageBody(super().body)) + elif self.body_type == 0xC8: + self.set_body(FDC8MessageBody(super().body)) + self.set_attr() + if ( + hasattr(self, "fan_speed") + and self.fan_speed is not None + and self.fan_speed < 5 + ): + self.fan_speed = 1 diff --git a/custom_components/midea_ac_lan/midea_devices.py b/custom_components/midea_ac_lan/midea_devices.py index 499f412a..5ad32bd2 100644 --- a/custom_components/midea_ac_lan/midea_devices.py +++ b/custom_components/midea_ac_lan/midea_devices.py @@ -1,2191 +1,2191 @@ -from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass -from homeassistant.const import ( - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - CONCENTRATION_PARTS_PER_MILLION, - PERCENTAGE, - Platform, - UnitOfEnergy, - UnitOfPower, - UnitOfTemperature, - UnitOfTime, - UnitOfVolume, -) - -from .midea.devices.a1.device import DeviceAttributes as A1Attributes -from .midea.devices.ac.device import DeviceAttributes as ACAttributes -from .midea.devices.b0.device import DeviceAttributes as B0Attributes -from .midea.devices.b1.device import DeviceAttributes as B1Attributes -from .midea.devices.b3.device import DeviceAttributes as B3Attributes -from .midea.devices.b4.device import DeviceAttributes as B4Attributes -from .midea.devices.b6.device import DeviceAttributes as B6Attributes -from .midea.devices.bf.device import DeviceAttributes as BFAttributes -from .midea.devices.c2.device import DeviceAttributes as C2Attributes -from .midea.devices.c3.device import DeviceAttributes as C3Attributes -from .midea.devices.ca.device import DeviceAttributes as CAAttributes -from .midea.devices.cc.device import DeviceAttributes as CCAttributes -from .midea.devices.cd.device import DeviceAttributes as CDAttributes -from .midea.devices.ce.device import DeviceAttributes as CEAttributes -from .midea.devices.cf.device import DeviceAttributes as CFAttributes -from .midea.devices.da.device import DeviceAttributes as DAAttributes -from .midea.devices.db.device import DeviceAttributes as DBAttributes -from .midea.devices.dc.device import DeviceAttributes as DCAttributes -from .midea.devices.e1.device import DeviceAttributes as E1Attributes -from .midea.devices.e2.device import DeviceAttributes as E2Attributes -from .midea.devices.e3.device import DeviceAttributes as E3Attributes -from .midea.devices.e6.device import DeviceAttributes as E6Attributes -from .midea.devices.e8.device import DeviceAttributes as E8Attributes -from .midea.devices.ea.device import DeviceAttributes as EAAttributes -from .midea.devices.ec.device import DeviceAttributes as ECAttributes -from .midea.devices.ed.device import DeviceAttributes as EDAttributes -from .midea.devices.fa.device import DeviceAttributes as FAAttributes -from .midea.devices.fb.device import DeviceAttributes as FBAttributes -from .midea.devices.fc.device import DeviceAttributes as FCAttributes -from .midea.devices.fd.device import DeviceAttributes as FDAttributes -from .midea.devices.x26.device import DeviceAttributes as X26Attributes -from .midea.devices.x34.device import DeviceAttributes as X34Attributes -from .midea.devices.x40.device import DeviceAttributes as X40Attributes - -MIDEA_DEVICES = { - 0x13: { - "name": "Light", - "entities": { - "light": {"type": Platform.LIGHT, "icon": "mdi:lightbulb", "default": True} - }, - }, - 0x26: { - "name": "Bathroom Master", - "entities": { - X26Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - X26Attributes.current_humidity: { - "type": Platform.SENSOR, - "name": "Current Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - X26Attributes.current_radar: { - "type": Platform.BINARY_SENSOR, - "name": "Occupancy Status", - "device_class": BinarySensorDeviceClass.MOVING, - }, - X26Attributes.main_light: { - "type": Platform.SWITCH, - "name": "Main Light", - "icon": "mdi:lightbulb", - }, - X26Attributes.night_light: { - "type": Platform.SWITCH, - "name": "Night Light", - "icon": "mdi:lightbulb", - }, - X26Attributes.mode: { - "type": Platform.SELECT, - "name": "Mode", - "options": "preset_modes", - "icon": "mdi:fan", - }, - X26Attributes.direction: { - "type": Platform.SELECT, - "name": "Direction", - "options": "directions", - "icon": "mdi:arrow-split-vertical", - }, - }, - }, - 0x34: { - "name": "Sink Dishwasher", - "entities": { - X34Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - X34Attributes.rinse_aid: { - "type": Platform.BINARY_SENSOR, - "name": "Rinse Aid Shortage", - "icon": "mdi:bottle-tonic", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - X34Attributes.salt: { - "type": Platform.BINARY_SENSOR, - "name": "Salt Shortage", - "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - X34Attributes.humidity: { - "type": Platform.SENSOR, - "name": "Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - X34Attributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - X34Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - X34Attributes.storage_remaining: { - "type": Platform.SENSOR, - "name": "Storage Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.HOURS, - "state_class": SensorStateClass.MEASUREMENT, - }, - X34Attributes.temperature: { - "type": Platform.SENSOR, - "name": "Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - X34Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - X34Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - X34Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - X34Attributes.storage: { - "type": Platform.SWITCH, - "name": "Storage", - "icon": "mdi:repeat-variant", - }, - X34Attributes.mode: { - "type": Platform.SENSOR, - "name": "Working Mode", - "icon": "mdi:dishwasher", - }, - X34Attributes.error_code: { - "type": Platform.SENSOR, - "name": "Error Code", - "icon": "mdi:alert-box", - }, - X34Attributes.softwater: { - "type": Platform.SENSOR, - "name": "Softwater Level", - "icon": "mdi:shaker-outline", - }, - X34Attributes.bright: { - "type": Platform.SENSOR, - "name": "Bright Level", - "icon": "mdi:star-four-points", - }, - }, - }, - 0x40: { - "name": "Integrated Ceiling Fan", - "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, - X40Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - X40Attributes.light: { - "type": Platform.SWITCH, - "name": "Light", - "icon": "mdi:lightbulb", - }, - X40Attributes.ventilation: { - "type": Platform.SWITCH, - "name": "Ventilation", - "icon": "mdi:air-filter", - }, - X40Attributes.smelly_sensor: { - "type": Platform.SWITCH, - "name": "Smelly Sensor", - "icon": "mdi:scent", - }, - X40Attributes.direction: { - "type": Platform.SELECT, - "name": "Direction", - "options": "directions", - "icon": "mdi:arrow-split-vertical", - }, - }, - }, - 0xA1: { - "name": "Dehumidifier", - "entities": { - "humidifier": { - "type": Platform.HUMIDIFIER, - "icon": "mdi:air-humidifier", - "default": True, - }, - A1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - A1Attributes.anion: { - "type": Platform.SWITCH, - "name": "Anion", - "icon": "mdi:vanish", - }, - A1Attributes.prompt_tone: { - "type": Platform.SWITCH, - "name": "Prompt Tone", - "icon": "mdi:bell", - }, - A1Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - A1Attributes.swing: { - "type": Platform.SWITCH, - "name": "swing", - "icon": "mdi:pan-horizontal", - }, - A1Attributes.fan_speed: { - "type": Platform.SELECT, - "name": "Fan Speed", - "options": "fan_speeds", - "icon": "mdi:fan", - }, - A1Attributes.water_level_set: { - "type": Platform.SELECT, - "name": "Water Level Setting", - "options": "water_level_sets", - "icon": "mdi:cup-water", - }, - A1Attributes.current_humidity: { - "type": Platform.SENSOR, - "name": "Current Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - A1Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - A1Attributes.tank: { - "type": Platform.SENSOR, - "name": "Tank", - "icon": "mdi:cup-water", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - A1Attributes.tank_full: { - "type": Platform.BINARY_SENSOR, - "name": "Tank status", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - }, - }, - 0xAC: { - "name": "Air Conditioner", - "entities": { - "climate": { - "type": Platform.CLIMATE, - "icon": "mdi:air-conditioner", - "default": True, - }, - "fresh_air": {"type": Platform.FAN, "icon": "mdi:fan", "name": "Fresh Air"}, - ACAttributes.aux_heating: { - "type": Platform.SWITCH, - "name": "Aux Heating", - "icon": "mdi:heat-wave", - }, - ACAttributes.boost_mode: { - "type": Platform.SWITCH, - "name": "Boost Mode", - "icon": "mdi:turbine", - }, - ACAttributes.breezeless: { - "type": Platform.SWITCH, - "name": "Breezeless", - "icon": "mdi:tailwind", - }, - ACAttributes.comfort_mode: { - "type": Platform.SWITCH, - "name": "Comfort Mode", - "icon": "mdi:alpha-c-circle", - }, - ACAttributes.dry: { - "type": Platform.SWITCH, - "name": "Dry", - "icon": "mdi:air-filter", - }, - ACAttributes.eco_mode: { - "type": Platform.SWITCH, - "name": "ECO Mode", - "icon": "mdi:leaf-circle", - }, - ACAttributes.frost_protect: { - "type": Platform.SWITCH, - "name": "Frost Protect", - "icon": "mdi:snowflake-alert", - }, - ACAttributes.indirect_wind: { - "type": Platform.SWITCH, - "name": "Indirect Wind", - "icon": "mdi:tailwind", - }, - ACAttributes.natural_wind: { - "type": Platform.SWITCH, - "name": "Natural Wind", - "icon": "mdi:tailwind", - }, - ACAttributes.prompt_tone: { - "type": Platform.SWITCH, - "name": "Prompt Tone", - "icon": "mdi:bell", - }, - ACAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - ACAttributes.screen_display: { - "type": Platform.SWITCH, - "name": "Screen Display", - "icon": "mdi:television-ambient-light", - }, - ACAttributes.screen_display_alternate: { - "type": Platform.SWITCH, - "name": "Screen Display Alternate", - "icon": "mdi:television-ambient-light", - }, - ACAttributes.sleep_mode: { - "type": Platform.SWITCH, - "name": "Sleep Mode", - "icon": "mdi:power-sleep", - }, - ACAttributes.smart_eye: { - "type": Platform.SWITCH, - "name": "Smart Eye", - "icon": "mdi:eye", - }, - ACAttributes.swing_horizontal: { - "type": Platform.SWITCH, - "name": "Swing Horizontal", - "icon": "mdi:arrow-split-vertical", - }, - ACAttributes.swing_vertical: { - "type": Platform.SWITCH, - "name": "Swing Vertical", - "icon": "mdi:arrow-split-horizontal", - }, - ACAttributes.full_dust: { - "type": Platform.BINARY_SENSOR, - "name": "Full of Dust", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - ACAttributes.indoor_humidity: { - "type": Platform.SENSOR, - "name": "Indoor Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - ACAttributes.indoor_temperature: { - "type": Platform.SENSOR, - "name": "Indoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - ACAttributes.outdoor_temperature: { - "type": Platform.SENSOR, - "name": "Outdoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - ACAttributes.total_energy_consumption: { - "type": Platform.SENSOR, - "name": "Total Energy Consumption", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - ACAttributes.current_energy_consumption: { - "type": Platform.SENSOR, - "name": "Current Energy Consumption", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - ACAttributes.realtime_power: { - "type": Platform.SENSOR, - "name": "Realtime Power", - "device_class": SensorDeviceClass.POWER, - "unit": UnitOfPower.WATT, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB0: { - "name": "Microwave Oven", - "entities": { - B0Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B0Attributes.tank_ejected: { - "type": Platform.BINARY_SENSOR, - "name": "Tank Ejected", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B0Attributes.water_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Water Change Reminder", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B0Attributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B0Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B0Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - B0Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB1: { - "name": "Electric Oven", - "entities": { - B1Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B1Attributes.tank_ejected: { - "type": Platform.BINARY_SENSOR, - "name": "Tank ejected", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B1Attributes.water_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Water Change Reminder", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B1Attributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B1Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B1Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - B1Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB3: { - "name": "Dish Sterilizer", - "entities": { - B3Attributes.top_compartment_door: { - "type": Platform.BINARY_SENSOR, - "name": "Top Compartment Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B3Attributes.top_compartment_preheating: { - "type": Platform.BINARY_SENSOR, - "name": "Top Compartment Preheating", - "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.top_compartment_cooling: { - "type": Platform.BINARY_SENSOR, - "name": "Top Compartment Cooling", - "icon": "snowflake-variant", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.middle_compartment_door: { - "type": Platform.BINARY_SENSOR, - "name": "Middle Compartment Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B3Attributes.middle_compartment_preheating: { - "type": Platform.BINARY_SENSOR, - "name": "Middle Compartment Preheating", - "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.middle_compartment_cooling: { - "type": Platform.BINARY_SENSOR, - "name": "Middle Compartment Cooling", - "icon": "snowflake-variant", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.bottom_compartment_door: { - "type": Platform.BINARY_SENSOR, - "name": "Bottom Compartment Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B3Attributes.bottom_compartment_preheating: { - "type": Platform.BINARY_SENSOR, - "name": "Bottom Compartment Preheating", - "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.bottom_compartment_cooling: { - "type": Platform.BINARY_SENSOR, - "name": "Bottom Compartment Cooling", - "icon": "snowflake-variant", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.top_compartment_status: { - "type": Platform.SENSOR, - "name": "Top Compartment Status", - "icon": "mdi:information", - }, - B3Attributes.top_compartment_temperature: { - "type": Platform.SENSOR, - "name": "Top Compartment Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.top_compartment_remaining: { - "type": Platform.SENSOR, - "name": "Top Compartment Remaining", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.middle_compartment_status: { - "type": Platform.SENSOR, - "name": "Middle Compartment Status", - "icon": "mdi:information", - }, - B3Attributes.middle_compartment_temperature: { - "type": Platform.SENSOR, - "name": "Middle Compartment Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.middle_compartment_remaining: { - "type": Platform.SENSOR, - "name": "Middle Compartment Remaining", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.bottom_compartment_status: { - "type": Platform.SENSOR, - "name": "Bottom Compartment Status", - "icon": "mdi:information", - }, - B3Attributes.bottom_compartment_temperature: { - "type": Platform.SENSOR, - "name": "Bottom Compartment Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.bottom_compartment_remaining: { - "type": Platform.SENSOR, - "name": "Bottom Compartment Remaining", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB4: { - "name": "Toaster", - "entities": { - B4Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B4Attributes.tank_ejected: { - "type": Platform.BINARY_SENSOR, - "name": "Tank ejected", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B4Attributes.water_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Water Change Reminder", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B4Attributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B4Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B4Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - B4Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB6: { - "name": "Range Hood", - "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, - B6Attributes.light: { - "type": Platform.SWITCH, - "name": "Light", - "icon": "mdi:lightbulb", - }, - B6Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - B6Attributes.cleaning_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Cleaning Reminder", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B6Attributes.oilcup_full: { - "type": Platform.BINARY_SENSOR, - "name": "Oil-cup Full", - "icon": "mdi:cup", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B6Attributes.fan_level: { - "type": Platform.SENSOR, - "name": "Fan level", - "icon": "mdi:fan", - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xBF: { - "name": "Microwave Steam Oven", - "entities": { - BFAttributes.tank_ejected: { - "type": Platform.BINARY_SENSOR, - "name": "Tank ejected", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - BFAttributes.water_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Water Change Reminder", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - BFAttributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - BFAttributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - BFAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - BFAttributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - BFAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xC2: { - "name": "Toilet", - "entities": { - C2Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - C2Attributes.sensor_light: { - "type": Platform.SWITCH, - "name": "Sensor Light", - "icon": "mdi:lightbulb", - }, - C2Attributes.foam_shield: { - "type": Platform.SWITCH, - "name": "Foam Shield", - "icon": "mdi:chart-bubble", - }, - C2Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - C2Attributes.seat_status: { - "type": Platform.BINARY_SENSOR, - "name": "Seat Status", - "icon": "mdi:seat-legroom-normal", - }, - C2Attributes.lid_status: { - "type": Platform.BINARY_SENSOR, - "name": "Lid Status", - "icon": "mdi:toilet", - }, - C2Attributes.light_status: { - "type": Platform.BINARY_SENSOR, - "name": "Light Status", - "icon": "mdi:lightbulb", - "device_class": BinarySensorDeviceClass.LIGHT, - }, - C2Attributes.water_temperature: { - "type": Platform.SENSOR, - "name": "Water Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - C2Attributes.seat_temperature: { - "type": Platform.SENSOR, - "name": "Seat Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - C2Attributes.filter_life: { - "type": Platform.SENSOR, - "name": "Filter Life", - "icon": "mdi:toilet", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - C2Attributes.dry_level: { - "type": Platform.NUMBER, - "name": "Dry Level", - "icon": "mdi:fire", - "max": "max_dry_level", - "min": 0, - "step": 1, - }, - C2Attributes.water_temp_level: { - "type": Platform.NUMBER, - "name": "Water Temperature Level", - "icon": "mdi:fire", - "max": "max_water_temp_level", - "min": 0, - "step": 1, - }, - C2Attributes.seat_temp_level: { - "type": Platform.NUMBER, - "name": "Seat Temperature Level", - "icon": "mdi:fire", - "max": "max_seat_temp_level", - "min": 0, - "step": 1, - }, - }, - }, - 0xC3: { - "name": "Heat Pump Wi-Fi Controller", - "entities": { - "climate_zone1": { - "type": Platform.CLIMATE, - "icon": "mdi:air-conditioner", - "name": "Zone1 Thermostat", - "zone": 0, - "default": True, - }, - "climate_zone2": { - "type": Platform.CLIMATE, - "icon": "mdi:air-conditioner", - "name": "Zone2 Thermostat", - "zone": 1, - "default": True, - }, - "water_heater": { - "type": Platform.WATER_HEATER, - "icon": "mdi:heat-pump", - "name": "Domestic hot water", - "default": True, - }, - C3Attributes.disinfect: { - "type": Platform.SWITCH, - "name": "Disinfect", - "icon": "mdi:water-plus-outline", - }, - C3Attributes.dhw_power: { - "type": Platform.SWITCH, - "name": "DHW Power", - "icon": "mdi:power", - }, - C3Attributes.eco_mode: { - "type": Platform.SWITCH, - "name": "ECO Mode", - "icon": "mdi:leaf-circle", - }, - C3Attributes.fast_dhw: { - "type": Platform.SWITCH, - "name": "Fast DHW", - "icon": "mdi:rotate-orbit", - }, - C3Attributes.silent_mode: { - "type": Platform.SWITCH, - "name": "Silent Mode", - "icon": "mdi:fan-remove", - }, - C3Attributes.tbh: { - "type": Platform.SWITCH, - "name": "TBH", - "icon": "mdi:water-boiler", - }, - C3Attributes.zone1_curve: { - "type": Platform.SWITCH, - "name": "Zone1 Curve", - "icon": "mdi:chart-bell-curve-cumulative", - }, - C3Attributes.zone2_curve: { - "type": Platform.SWITCH, - "name": "Zone2 Curve", - "icon": "mdi:chart-bell-curve-cumulative", - }, - C3Attributes.zone1_power: { - "type": Platform.SWITCH, - "name": "Zone1 Power", - "icon": "mdi:power", - }, - C3Attributes.zone2_power: { - "type": Platform.SWITCH, - "name": "Zone2 Power", - "icon": "mdi:power", - }, - C3Attributes.zone1_water_temp_mode: { - "type": Platform.BINARY_SENSOR, - "name": "Zone1 Water-temperature Mode", - "icon": "mdi:coolant-temperature", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.zone2_water_temp_mode: { - "type": Platform.BINARY_SENSOR, - "name": "Zone2 Water-temperature Mode", - "icon": "mdi:coolant-temperature", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.zone1_room_temp_mode: { - "type": Platform.BINARY_SENSOR, - "name": "Zone1 Room-temperature Mode", - "icon": "mdi:home-thermometer-outline", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.zone2_room_temp_mode: { - "type": Platform.BINARY_SENSOR, - "name": "Zone2 Room-temperature Mode", - "icon": "mdi:home-thermometer-outline", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.error_code: { - "type": Platform.SENSOR, - "name": "Error Code", - "icon": "mdi:alpha-e-circle", - }, - C3Attributes.tank_actual_temperature: { - "type": Platform.SENSOR, - "name": "Tank Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - C3Attributes.status_dhw: { - "type": Platform.BINARY_SENSOR, - "name": "DHW status", - "icon": "mdi:heat-pump", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.status_tbh: { - "type": Platform.BINARY_SENSOR, - "name": "TBH status", - "icon": "mdi:water-boiler", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.status_ibh: { - "type": Platform.BINARY_SENSOR, - "name": "IBH status", - "icon": "mdi:coolant-temperature", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.status_heating: { - "type": Platform.BINARY_SENSOR, - "name": "Heating status", - "icon": "mdi:heat-pump", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.total_energy_consumption: { - "type": Platform.SENSOR, - "name": "Total energy consumption", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - C3Attributes.total_produced_energy: { - "type": Platform.SENSOR, - "name": "Total produced energy", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - C3Attributes.outdoor_temperature: { - "type": Platform.SENSOR, - "name": "Outdoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xCA: { - "name": "Refrigerator", - "entities": { - CAAttributes.bar_door: { - "type": Platform.BINARY_SENSOR, - "name": "Bar Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - CAAttributes.bar_door_overtime: { - "type": Platform.BINARY_SENSOR, - "name": "Bar Door Overtime", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.flex_zone_door: { - "type": Platform.BINARY_SENSOR, - "name": "Flex Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - CAAttributes.flex_zone_door_overtime: { - "type": Platform.BINARY_SENSOR, - "name": "Flex Zone Door", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.freezer_door: { - "type": Platform.BINARY_SENSOR, - "name": "Freezer Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - CAAttributes.freezer_door_overtime: { - "type": Platform.BINARY_SENSOR, - "name": "Freezer Door Overtime", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.refrigerator_door: { - "type": Platform.BINARY_SENSOR, - "name": "Refrigerator Door", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.refrigerator_door_overtime: { - "type": Platform.BINARY_SENSOR, - "name": "Refrigerator Door Overtime", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.flex_zone_actual_temp: { - "type": Platform.SENSOR, - "name": "Flex Zone Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.flex_zone_setting_temp: { - "type": Platform.SENSOR, - "name": "Flex Zone Setting Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.freezer_actual_temp: { - "type": Platform.SENSOR, - "name": "Freezer Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.freezer_setting_temp: { - "type": Platform.SENSOR, - "name": "Freezer Setting Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.energy_consumption: { - "type": Platform.SENSOR, - "name": "Energy Consumption", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - CAAttributes.refrigerator_actual_temp: { - "type": Platform.SENSOR, - "name": "Refrigerator Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.refrigerator_setting_temp: { - "type": Platform.SENSOR, - "name": "Refrigerator Setting Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.right_flex_zone_actual_temp: { - "type": Platform.SENSOR, - "name": "Right Flex Zone Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.right_flex_zone_setting_temp: { - "type": Platform.SENSOR, - "name": "Right Flex Zone Setting Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xCC: { - "name": "MDV Wi-Fi Controller", - "entities": { - "climate": { - "type": Platform.CLIMATE, - "icon": "hass:air-conditioner", - "default": True, - }, - CCAttributes.aux_heating: { - "type": Platform.SWITCH, - "name": "Aux Heating", - "icon": "mdi:heat-wave", - }, - CCAttributes.eco_mode: { - "type": Platform.SWITCH, - "name": "ECO Mode", - "icon": "mdi:leaf-circle", - }, - CCAttributes.night_light: { - "type": Platform.SWITCH, - "name": "Night Light", - "icon": "mdi:lightbulb", - }, - CCAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - CCAttributes.sleep_mode: { - "type": Platform.SWITCH, - "name": "Sleep Mode", - "icon": "mdi:power-sleep", - }, - CCAttributes.swing: { - "type": Platform.SWITCH, - "name": "Swing", - "icon": "mdi:arrow-split-horizontal", - }, - CCAttributes.indoor_temperature: { - "type": Platform.SENSOR, - "name": "Indoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xCD: { - "name": "Heat Pump Water Heater", - "entities": { - "water_heater": { - "type": Platform.WATER_HEATER, - "icon": "mdi:heat-pump", - "default": True, - }, - CDAttributes.compressor_status: { - "type": Platform.BINARY_SENSOR, - "name": "Compressor Status", - "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - CDAttributes.compressor_temperature: { - "type": Platform.SENSOR, - "name": "Compressor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CDAttributes.condenser_temperature: { - "type": Platform.SENSOR, - "name": "Condenser Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CDAttributes.outdoor_temperature: { - "type": Platform.SENSOR, - "name": "Outdoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CDAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - }, - }, - 0xCE: { - "name": "Fresh Air Appliance", - "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, - CEAttributes.filter_cleaning_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Filter Cleaning Reminder", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CEAttributes.filter_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Filter Change Reminder", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CEAttributes.current_humidity: { - "type": Platform.SENSOR, - "name": "Current Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.co2: { - "type": Platform.SENSOR, - "name": "Carbon Dioxide", - "device_class": SensorDeviceClass.CO2, - "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.hcho: { - "type": Platform.SENSOR, - "name": "Methanal", - "icon": "mdi:molecule", - "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.pm25: { - "type": Platform.SENSOR, - "name": "PM 2.5", - "device_class": SensorDeviceClass.PM25, - "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - CEAttributes.aux_heating: { - "type": Platform.SWITCH, - "name": "Aux Heating", - "icon": "mdi:heat-wave", - }, - CEAttributes.eco_mode: { - "type": Platform.SWITCH, - "name": "ECO Mode", - "icon": "mdi:leaf-circle", - }, - CEAttributes.link_to_ac: { - "type": Platform.SWITCH, - "name": "Link to AC", - "icon": "mdi:link", - }, - CEAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - CEAttributes.powerful_purify: { - "type": Platform.SWITCH, - "name": "Powerful Purification", - "icon": "mdi:turbine", - }, - CEAttributes.sleep_mode: { - "type": Platform.SWITCH, - "name": "Sleep Mode", - "icon": "mdi:power-sleep", - }, - }, - }, - 0xCF: { - "name": "Heat Pump", - "entities": { - "climate": { - "type": Platform.CLIMATE, - "icon": "hass:air-conditioner", - "default": True, - }, - CFAttributes.aux_heating: { - "type": Platform.SWITCH, - "name": "Aux Heating", - "icon": "mdi:heat-wave", - }, - CFAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - CFAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xDA: { - "name": "Top Load Washer", - "entities": { - DAAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DAAttributes.wash_time: { - "type": Platform.SENSOR, - "name": "wash time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DAAttributes.soak_time: { - "type": Platform.SENSOR, - "name": "soak time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DAAttributes.dehydration_time: { - "type": Platform.SENSOR, - "name": "dehydration time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DAAttributes.dehydration_speed: { - "type": Platform.SENSOR, - "name": "dehydration speed", - "icon": "mdi:speedometer", - }, - DAAttributes.error_code: { - "type": Platform.SENSOR, - "name": "error code", - "icon": "mdi:washing-machine-alert", - }, - DAAttributes.rinse_count: { - "type": Platform.SENSOR, - "name": "rinse count", - "icon": "mdi:water-sync", - }, - DAAttributes.rinse_level: { - "type": Platform.SENSOR, - "name": "rinse level", - "icon": "mdi:hydraulic-oil-level", - }, - DAAttributes.wash_level: { - "type": Platform.SENSOR, - "name": "rinse count", - "icon": "mdi:hydraulic-oil-level", - }, - DAAttributes.wash_strength: { - "type": Platform.SENSOR, - "name": "wash strength", - "icon": "mdi:network-strength-4-cog", - }, - DAAttributes.softener: { - "type": Platform.SENSOR, - "name": "softener", - "icon": "mdi:tshirt-crew", - }, - DAAttributes.detergent: { - "type": Platform.SENSOR, - "name": "detergent", - "icon": "mdi:spray-bottle", - }, - DAAttributes.program: { - "type": Platform.SENSOR, - "name": "Program", - "icon": "mdi:progress-wrench", - }, - DAAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - DAAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - DAAttributes.start: { - "type": Platform.SWITCH, - "name": "Start", - "icon": "mdi:motion-play-outline", - }, - }, - }, - 0xDB: { - "name": "Front Load Washer", - "entities": { - DBAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DBAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - DBAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - DBAttributes.start: { - "type": Platform.SWITCH, - "name": "Start", - "icon": "mdi:motion-play-outline", - }, - }, - }, - 0xDC: { - "name": "Clothes Dryer", - "entities": { - DCAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DCAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - DCAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - DCAttributes.start: { - "type": Platform.SWITCH, - "name": "Start", - "icon": "mdi:motion-play-outline", - }, - }, - }, - 0xE1: { - "name": "Dishwasher", - "entities": { - E1Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - E1Attributes.rinse_aid: { - "type": Platform.BINARY_SENSOR, - "name": "Rinse Aid Shortage", - "icon": "mdi:bottle-tonic", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - E1Attributes.salt: { - "type": Platform.BINARY_SENSOR, - "name": "Salt Shortage", - "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - E1Attributes.humidity: { - "type": Platform.SENSOR, - "name": "Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - E1Attributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - E1Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - E1Attributes.storage_remaining: { - "type": Platform.SENSOR, - "name": "Storage Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.HOURS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E1Attributes.temperature: { - "type": Platform.SENSOR, - "name": "Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E1Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - E1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - E1Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - E1Attributes.storage: { - "type": Platform.SWITCH, - "name": "Storage", - "icon": "mdi:repeat-variant", - }, - E1Attributes.mode: { - "type": Platform.SENSOR, - "name": "Working Mode", - "icon": "mdi:dishwasher", - }, - E1Attributes.error_code: { - "type": Platform.SENSOR, - "name": "Error Code", - "icon": "mdi:alert-box", - }, - E1Attributes.softwater: { - "type": Platform.SENSOR, - "name": "Softwater Level", - "icon": "mdi:shaker-outline", - }, - E1Attributes.bright: { - "type": Platform.SENSOR, - "name": "Bright Level", - "icon": "mdi:star-four-points", - }, - }, - }, - 0xE2: { - "name": "Electric Water Heater", - "entities": { - "water_heater": { - "type": Platform.WATER_HEATER, - "icon": "mdi:meter-electric-outline", - "default": True, - }, - E2Attributes.heating: { - "type": Platform.BINARY_SENSOR, - "name": "Heating", - "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E2Attributes.keep_warm: { - "type": Platform.BINARY_SENSOR, - "name": "Keep Warm", - "icon": "mdi:menu", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E2Attributes.protection: { - "type": Platform.BINARY_SENSOR, - "name": "Protection", - "icon": "mdi:shield-check", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E2Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E2Attributes.heating_time_remaining: { - "type": Platform.SENSOR, - "name": "Heating Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - E2Attributes.heating_power: { - "type": Platform.SENSOR, - "name": "Heating Power", - "device_class": SensorDeviceClass.POWER, - "unit": UnitOfPower.WATT, - "state_class": SensorStateClass.MEASUREMENT, - }, - E2Attributes.water_consumption: { - "type": Platform.SENSOR, - "name": "Water Consumption", - "icon": "mdi:water", - "unit": UnitOfVolume.LITERS, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - E2Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - E2Attributes.variable_heating: { - "type": Platform.SWITCH, - "name": "Variable Heating", - "icon": "mdi:waves", - }, - E2Attributes.whole_tank_heating: { - "type": Platform.SWITCH, - "name": "Whole Tank Heating", - "icon": "mdi:restore", - }, - }, - }, - 0xE3: { - "name": "Gas Water Heater", - "entities": { - "water_heater": { - "type": Platform.WATER_HEATER, - "icon": "mdi:meter-gas", - "default": True, - }, - E3Attributes.burning_state: { - "type": Platform.BINARY_SENSOR, - "name": "Burning State", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E3Attributes.protection: { - "type": Platform.BINARY_SENSOR, - "name": "Protection", - "icon": "mdi:shield-check", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E3Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E3Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - E3Attributes.smart_volume: { - "type": Platform.SWITCH, - "name": "Smart Volume", - "icon": "mdi:recycle", - }, - E3Attributes.zero_cold_water: { - "type": Platform.SWITCH, - "name": "Zero Cold Water", - "icon": "mdi:restore", - }, - E3Attributes.zero_cold_pulse: { - "type": Platform.SWITCH, - "name": "Zero Cold Water (Pulse)", - "icon": "mdi:restore-alert", - }, - }, - }, - 0xE6: { - "name": "Gas Boilers", - "entities": { - "water_heater_heating": { - "type": Platform.WATER_HEATER, - "icon": "mdi:meter-gas", - "name": "Heating", - "use": 0, - "default": True, - }, - "water_heater_bathing": { - "type": Platform.WATER_HEATER, - "icon": "mdi:meter-gas", - "name": "Bathing", - "use": 1, - "default": True, - }, - E6Attributes.heating_working: { - "type": Platform.BINARY_SENSOR, - "name": "Heating Working Status", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E6Attributes.bathing_working: { - "type": Platform.BINARY_SENSOR, - "name": "Bathing Working Status", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E6Attributes.heating_leaving_temperature: { - "type": Platform.SENSOR, - "name": "Heating Leaving Water Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E6Attributes.bathing_leaving_temperature: { - "type": Platform.SENSOR, - "name": "Bathing Leaving Water Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E6Attributes.main_power: { - "type": Platform.SWITCH, - "name": "Main Power", - "icon": "mdi:power", - }, - E6Attributes.heating_power: { - "type": Platform.SWITCH, - "name": "Heating Power", - "icon": "mdi:heating-coil", - }, - }, - }, - 0xE8: { - "name": "Electric Slow Cooker", - "entities": { - E8Attributes.finished: { - "type": Platform.BINARY_SENSOR, - "name": "Finished", - "icon": "", - }, - E8Attributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - E8Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - E8Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E8Attributes.keep_warm_remaining: { - "type": Platform.SENSOR, - "name": "Keep Warm Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E8Attributes.working_time: { - "type": Platform.SENSOR, - "name": "Working Time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E8Attributes.target_temperature: { - "type": Platform.SENSOR, - "name": "Target Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E8Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xEA: { - "name": "Electric Rice Cooker", - "entities": { - EAAttributes.cooking: { - "type": Platform.BINARY_SENSOR, - "name": "Cooking", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - EAAttributes.keep_warm: { - "type": Platform.BINARY_SENSOR, - "name": "Keep Warm", - "icon": "mdi:menu", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - EAAttributes.bottom_temperature: { - "type": Platform.SENSOR, - "name": "Bottom Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - EAAttributes.keep_warm_time: { - "type": Platform.SENSOR, - "name": "Keep Warm Time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - EAAttributes.mode: { - "type": Platform.SENSOR, - "name": "Mode", - "icon": "mdi:orbit", - }, - EAAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - EAAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - EAAttributes.top_temperature: { - "type": Platform.SENSOR, - "name": "Top Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xEC: { - "name": "Electric Pressure Cooker", - "entities": { - ECAttributes.cooking: { - "type": Platform.BINARY_SENSOR, - "name": "Cooking", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - ECAttributes.with_pressure: { - "type": Platform.BINARY_SENSOR, - "name": "With Pressure", - "icon": "mdi:information", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - ECAttributes.bottom_temperature: { - "type": Platform.SENSOR, - "name": "Bottom Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - ECAttributes.keep_warm_time: { - "type": Platform.SENSOR, - "name": "Keep Warm Time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - ECAttributes.mode: { - "type": Platform.SENSOR, - "name": "Mode", - "icon": "mdi:orbit", - }, - ECAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - ECAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - ECAttributes.top_temperature: { - "type": Platform.SENSOR, - "name": "Top Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xED: { - "name": "Water Drinking Appliance", - "entities": { - EDAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - EDAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - EDAttributes.filter1: { - "type": Platform.SENSOR, - "name": "Filter1 Available Days", - "icon": "mdi:air-filter", - "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.filter2: { - "type": Platform.SENSOR, - "name": "Filter2 Available Days", - "icon": "mdi:air-filter", - "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.filter3: { - "type": Platform.SENSOR, - "name": "Filter3 Available Days", - "icon": "mdi:air-filter", - "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.life1: { - "type": Platform.SENSOR, - "name": "Filter1 Life Level", - "icon": "mdi:percent", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.life2: { - "type": Platform.SENSOR, - "name": "Filter2 Life Level", - "icon": "mdi:percent", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.life3: { - "type": Platform.SENSOR, - "name": "Filter3 Life Level", - "icon": "mdi:percent", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.in_tds: { - "type": Platform.SENSOR, - "name": "In TDS", - "icon": "mdi:water", - "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.out_tds: { - "type": Platform.SENSOR, - "name": "Out TDS", - "icon": "mdi:water-plus", - "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.water_consumption: { - "type": Platform.SENSOR, - "name": "Water Consumption", - "icon": "mdi:water-pump", - "unit": UnitOfVolume.LITERS, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - }, - }, - 0xFA: { - "name": "Fan", - "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, - FAAttributes.oscillation_mode: { - "type": Platform.SELECT, - "name": "Oscillation Mode", - "options": "oscillation_modes", - "icon": "mdi:swap-horizontal-variant", - }, - FAAttributes.oscillation_angle: { - "type": Platform.SELECT, - "name": "Oscillation Angle", - "options": "oscillation_angles", - "icon": "mdi:pan-horizontal", - }, - FAAttributes.tilting_angle: { - "type": Platform.SELECT, - "name": "Tilting Angle", - "options": "tilting_angles", - "icon": "mdi:pan-vertical", - }, - FAAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - FAAttributes.oscillate: { - "type": Platform.SWITCH, - "name": "Oscillate", - "icon": "mdi:swap-horizontal-bold", - }, - FAAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - }, - }, - 0xFB: { - "name": "Electric Heater", - "entities": { - "climate": { - "type": Platform.CLIMATE, - "icon": "mdi:air-conditioner", - "default": True, - }, - FBAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - FBAttributes.heating_level: { - "type": Platform.NUMBER, - "name": "Heating Level", - "icon": "mdi:fire", - "max": 10, - "min": 1, - "step": 1, - }, - FBAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - FBAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xFC: { - "name": "Air Purifier", - "entities": { - FCAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - FCAttributes.anion: { - "type": Platform.SWITCH, - "name": "Anion", - "icon": "mdi:vanish", - }, - FCAttributes.prompt_tone: { - "type": Platform.SWITCH, - "name": "Prompt Tone", - "icon": "mdi:bell", - }, - FCAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - FCAttributes.standby: { - "type": Platform.SWITCH, - "name": "Standby", - "icon": "mdi:smoke-detector-variant", - }, - FCAttributes.detect_mode: { - "type": Platform.SELECT, - "name": "Detect Mode", - "options": "detect_modes", - "icon": "mdi:smoke-detector-variant", - }, - FCAttributes.mode: { - "type": Platform.SELECT, - "name": "Mode", - "options": "modes", - "icon": "mdi:rotate-360", - }, - FCAttributes.fan_speed: { - "type": Platform.SELECT, - "name": "Fan Speed", - "options": "fan_speeds", - "icon": "mdi:fan", - }, - FCAttributes.screen_display: { - "type": Platform.SELECT, - "name": "Screen Display", - "options": "screen_displays", - "icon": "mdi:television-ambient-light", - }, - FCAttributes.pm25: { - "type": Platform.SENSOR, - "name": "PM 2.5", - "device_class": SensorDeviceClass.PM25, - "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, - }, - FCAttributes.tvoc: { - "type": Platform.SENSOR, - "name": "TVOC", - "icon": "mdi:heat-wave", - "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, - }, - FCAttributes.hcho: { - "type": Platform.SENSOR, - "name": "Methanal", - "icon": "mdi:molecule", - "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, - }, - FCAttributes.filter1_life: { - "type": Platform.SENSOR, - "name": "Filter1 Life Level", - "icon": "mdi:air-filter", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - FCAttributes.filter2_life: { - "type": Platform.SENSOR, - "name": "Filter2 Life Level", - "icon": "mdi:air-filter", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xFD: { - "name": "Humidifier", - "entities": { - Platform.HUMIDIFIER: { - "type": Platform.HUMIDIFIER, - "icon": "mdi:air-humidifier", - "default": True, - }, - FDAttributes.disinfect: { - "type": Platform.SWITCH, - "name": "Disinfect", - "icon": "mdi:water-plus-outline", - }, - FDAttributes.prompt_tone: { - "type": Platform.SWITCH, - "name": "Prompt Tone", - "icon": "mdi:bell", - }, - FDAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - FDAttributes.fan_speed: { - "type": Platform.SELECT, - "name": "Fan Speed", - "options": "fan_speeds", - "icon": "mdi:fan", - }, - FDAttributes.screen_display: { - "type": Platform.SELECT, - "name": "Screen Display", - "options": "screen_displays", - "icon": "mdi:television-ambient-light", - }, - FDAttributes.current_humidity: { - "type": Platform.SENSOR, - "name": "Current Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - FDAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, -} +from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass +from homeassistant.const import ( + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_MILLION, + PERCENTAGE, + Platform, + UnitOfEnergy, + UnitOfPower, + UnitOfTemperature, + UnitOfTime, + UnitOfVolume, +) + +from .midea.devices.a1.device import DeviceAttributes as A1Attributes +from .midea.devices.ac.device import DeviceAttributes as ACAttributes +from .midea.devices.b0.device import DeviceAttributes as B0Attributes +from .midea.devices.b1.device import DeviceAttributes as B1Attributes +from .midea.devices.b3.device import DeviceAttributes as B3Attributes +from .midea.devices.b4.device import DeviceAttributes as B4Attributes +from .midea.devices.b6.device import DeviceAttributes as B6Attributes +from .midea.devices.bf.device import DeviceAttributes as BFAttributes +from .midea.devices.c2.device import DeviceAttributes as C2Attributes +from .midea.devices.c3.device import DeviceAttributes as C3Attributes +from .midea.devices.ca.device import DeviceAttributes as CAAttributes +from .midea.devices.cc.device import DeviceAttributes as CCAttributes +from .midea.devices.cd.device import DeviceAttributes as CDAttributes +from .midea.devices.ce.device import DeviceAttributes as CEAttributes +from .midea.devices.cf.device import DeviceAttributes as CFAttributes +from .midea.devices.da.device import DeviceAttributes as DAAttributes +from .midea.devices.db.device import DeviceAttributes as DBAttributes +from .midea.devices.dc.device import DeviceAttributes as DCAttributes +from .midea.devices.e1.device import DeviceAttributes as E1Attributes +from .midea.devices.e2.device import DeviceAttributes as E2Attributes +from .midea.devices.e3.device import DeviceAttributes as E3Attributes +from .midea.devices.e6.device import DeviceAttributes as E6Attributes +from .midea.devices.e8.device import DeviceAttributes as E8Attributes +from .midea.devices.ea.device import DeviceAttributes as EAAttributes +from .midea.devices.ec.device import DeviceAttributes as ECAttributes +from .midea.devices.ed.device import DeviceAttributes as EDAttributes +from .midea.devices.fa.device import DeviceAttributes as FAAttributes +from .midea.devices.fb.device import DeviceAttributes as FBAttributes +from .midea.devices.fc.device import DeviceAttributes as FCAttributes +from .midea.devices.fd.device import DeviceAttributes as FDAttributes +from .midea.devices.x26.device import DeviceAttributes as X26Attributes +from .midea.devices.x34.device import DeviceAttributes as X34Attributes +from .midea.devices.x40.device import DeviceAttributes as X40Attributes + +MIDEA_DEVICES = { + 0x13: { + "name": "Light", + "entities": { + "light": {"type": Platform.LIGHT, "icon": "mdi:lightbulb", "default": True} + }, + }, + 0x26: { + "name": "Bathroom Master", + "entities": { + X26Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X26Attributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + X26Attributes.current_radar: { + "type": Platform.BINARY_SENSOR, + "name": "Occupancy Status", + "device_class": BinarySensorDeviceClass.MOVING, + }, + X26Attributes.main_light: { + "type": Platform.SWITCH, + "name": "Main Light", + "icon": "mdi:lightbulb", + }, + X26Attributes.night_light: { + "type": Platform.SWITCH, + "name": "Night Light", + "icon": "mdi:lightbulb", + }, + X26Attributes.mode: { + "type": Platform.SELECT, + "name": "Mode", + "options": "preset_modes", + "icon": "mdi:fan", + }, + X26Attributes.direction: { + "type": Platform.SELECT, + "name": "Direction", + "options": "directions", + "icon": "mdi:arrow-split-vertical", + }, + }, + }, + 0x34: { + "name": "Sink Dishwasher", + "entities": { + X34Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + X34Attributes.rinse_aid: { + "type": Platform.BINARY_SENSOR, + "name": "Rinse Aid Shortage", + "icon": "mdi:bottle-tonic", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + X34Attributes.salt: { + "type": Platform.BINARY_SENSOR, + "name": "Salt Shortage", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + X34Attributes.humidity: { + "type": Platform.SENSOR, + "name": "Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + X34Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + X34Attributes.storage_remaining: { + "type": Platform.SENSOR, + "name": "Storage Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.HOURS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.temperature: { + "type": Platform.SENSOR, + "name": "Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + X34Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + X34Attributes.storage: { + "type": Platform.SWITCH, + "name": "Storage", + "icon": "mdi:repeat-variant", + }, + X34Attributes.mode: { + "type": Platform.SENSOR, + "name": "Working Mode", + "icon": "mdi:dishwasher", + }, + X34Attributes.error_code: { + "type": Platform.SENSOR, + "name": "Error Code", + "icon": "mdi:alert-box", + }, + X34Attributes.softwater: { + "type": Platform.SENSOR, + "name": "Softwater Level", + "icon": "mdi:shaker-outline", + }, + X34Attributes.bright: { + "type": Platform.SENSOR, + "name": "Bright Level", + "icon": "mdi:star-four-points", + }, + }, + }, + 0x40: { + "name": "Integrated Ceiling Fan", + "entities": { + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + X40Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X40Attributes.light: { + "type": Platform.SWITCH, + "name": "Light", + "icon": "mdi:lightbulb", + }, + X40Attributes.ventilation: { + "type": Platform.SWITCH, + "name": "Ventilation", + "icon": "mdi:air-filter", + }, + X40Attributes.smelly_sensor: { + "type": Platform.SWITCH, + "name": "Smelly Sensor", + "icon": "mdi:scent", + }, + X40Attributes.direction: { + "type": Platform.SELECT, + "name": "Direction", + "options": "directions", + "icon": "mdi:arrow-split-vertical", + }, + }, + }, + 0xA1: { + "name": "Dehumidifier", + "entities": { + "humidifier": { + "type": Platform.HUMIDIFIER, + "icon": "mdi:air-humidifier", + "default": True, + }, + A1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + A1Attributes.anion: { + "type": Platform.SWITCH, + "name": "Anion", + "icon": "mdi:vanish", + }, + A1Attributes.prompt_tone: { + "type": Platform.SWITCH, + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + A1Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + A1Attributes.swing: { + "type": Platform.SWITCH, + "name": "swing", + "icon": "mdi:pan-horizontal", + }, + A1Attributes.fan_speed: { + "type": Platform.SELECT, + "name": "Fan Speed", + "options": "fan_speeds", + "icon": "mdi:fan", + }, + A1Attributes.water_level_set: { + "type": Platform.SELECT, + "name": "Water Level Setting", + "options": "water_level_sets", + "icon": "mdi:cup-water", + }, + A1Attributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + A1Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + A1Attributes.tank: { + "type": Platform.SENSOR, + "name": "Tank", + "icon": "mdi:cup-water", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + A1Attributes.tank_full: { + "type": Platform.BINARY_SENSOR, + "name": "Tank status", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + }, + }, + 0xAC: { + "name": "Air Conditioner", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "mdi:air-conditioner", + "default": True, + }, + "fresh_air": {"type": Platform.FAN, "icon": "mdi:fan", "name": "Fresh Air"}, + ACAttributes.aux_heating: { + "type": Platform.SWITCH, + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + ACAttributes.boost_mode: { + "type": Platform.SWITCH, + "name": "Boost Mode", + "icon": "mdi:turbine", + }, + ACAttributes.breezeless: { + "type": Platform.SWITCH, + "name": "Breezeless", + "icon": "mdi:tailwind", + }, + ACAttributes.comfort_mode: { + "type": Platform.SWITCH, + "name": "Comfort Mode", + "icon": "mdi:alpha-c-circle", + }, + ACAttributes.dry: { + "type": Platform.SWITCH, + "name": "Dry", + "icon": "mdi:air-filter", + }, + ACAttributes.eco_mode: { + "type": Platform.SWITCH, + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + ACAttributes.frost_protect: { + "type": Platform.SWITCH, + "name": "Frost Protect", + "icon": "mdi:snowflake-alert", + }, + ACAttributes.indirect_wind: { + "type": Platform.SWITCH, + "name": "Indirect Wind", + "icon": "mdi:tailwind", + }, + ACAttributes.natural_wind: { + "type": Platform.SWITCH, + "name": "Natural Wind", + "icon": "mdi:tailwind", + }, + ACAttributes.prompt_tone: { + "type": Platform.SWITCH, + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + ACAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + ACAttributes.screen_display: { + "type": Platform.SWITCH, + "name": "Screen Display", + "icon": "mdi:television-ambient-light", + }, + ACAttributes.screen_display_alternate: { + "type": Platform.SWITCH, + "name": "Screen Display Alternate", + "icon": "mdi:television-ambient-light", + }, + ACAttributes.sleep_mode: { + "type": Platform.SWITCH, + "name": "Sleep Mode", + "icon": "mdi:power-sleep", + }, + ACAttributes.smart_eye: { + "type": Platform.SWITCH, + "name": "Smart Eye", + "icon": "mdi:eye", + }, + ACAttributes.swing_horizontal: { + "type": Platform.SWITCH, + "name": "Swing Horizontal", + "icon": "mdi:arrow-split-vertical", + }, + ACAttributes.swing_vertical: { + "type": Platform.SWITCH, + "name": "Swing Vertical", + "icon": "mdi:arrow-split-horizontal", + }, + ACAttributes.full_dust: { + "type": Platform.BINARY_SENSOR, + "name": "Full of Dust", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + ACAttributes.indoor_humidity: { + "type": Platform.SENSOR, + "name": "Indoor Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.indoor_temperature: { + "type": Platform.SENSOR, + "name": "Indoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.outdoor_temperature: { + "type": Platform.SENSOR, + "name": "Outdoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.total_energy_consumption: { + "type": Platform.SENSOR, + "name": "Total Energy Consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + ACAttributes.current_energy_consumption: { + "type": Platform.SENSOR, + "name": "Current Energy Consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + ACAttributes.realtime_power: { + "type": Platform.SENSOR, + "name": "Realtime Power", + "device_class": SensorDeviceClass.POWER, + "unit": UnitOfPower.WATT, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB0: { + "name": "Microwave Oven", + "entities": { + B0Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B0Attributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "name": "Tank Ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B0Attributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B0Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B0Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B0Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + B0Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB1: { + "name": "Electric Oven", + "entities": { + B1Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B1Attributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "name": "Tank ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B1Attributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B1Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B1Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B1Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + B1Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB3: { + "name": "Dish Sterilizer", + "entities": { + B3Attributes.top_compartment_door: { + "type": Platform.BINARY_SENSOR, + "name": "Top Compartment Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B3Attributes.top_compartment_preheating: { + "type": Platform.BINARY_SENSOR, + "name": "Top Compartment Preheating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.top_compartment_cooling: { + "type": Platform.BINARY_SENSOR, + "name": "Top Compartment Cooling", + "icon": "snowflake-variant", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.middle_compartment_door: { + "type": Platform.BINARY_SENSOR, + "name": "Middle Compartment Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B3Attributes.middle_compartment_preheating: { + "type": Platform.BINARY_SENSOR, + "name": "Middle Compartment Preheating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.middle_compartment_cooling: { + "type": Platform.BINARY_SENSOR, + "name": "Middle Compartment Cooling", + "icon": "snowflake-variant", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.bottom_compartment_door: { + "type": Platform.BINARY_SENSOR, + "name": "Bottom Compartment Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B3Attributes.bottom_compartment_preheating: { + "type": Platform.BINARY_SENSOR, + "name": "Bottom Compartment Preheating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.bottom_compartment_cooling: { + "type": Platform.BINARY_SENSOR, + "name": "Bottom Compartment Cooling", + "icon": "snowflake-variant", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.top_compartment_status: { + "type": Platform.SENSOR, + "name": "Top Compartment Status", + "icon": "mdi:information", + }, + B3Attributes.top_compartment_temperature: { + "type": Platform.SENSOR, + "name": "Top Compartment Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.top_compartment_remaining: { + "type": Platform.SENSOR, + "name": "Top Compartment Remaining", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.middle_compartment_status: { + "type": Platform.SENSOR, + "name": "Middle Compartment Status", + "icon": "mdi:information", + }, + B3Attributes.middle_compartment_temperature: { + "type": Platform.SENSOR, + "name": "Middle Compartment Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.middle_compartment_remaining: { + "type": Platform.SENSOR, + "name": "Middle Compartment Remaining", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.bottom_compartment_status: { + "type": Platform.SENSOR, + "name": "Bottom Compartment Status", + "icon": "mdi:information", + }, + B3Attributes.bottom_compartment_temperature: { + "type": Platform.SENSOR, + "name": "Bottom Compartment Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.bottom_compartment_remaining: { + "type": Platform.SENSOR, + "name": "Bottom Compartment Remaining", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB4: { + "name": "Toaster", + "entities": { + B4Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B4Attributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "name": "Tank ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B4Attributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B4Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B4Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B4Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + B4Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB6: { + "name": "Range Hood", + "entities": { + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + B6Attributes.light: { + "type": Platform.SWITCH, + "name": "Light", + "icon": "mdi:lightbulb", + }, + B6Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + B6Attributes.cleaning_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Cleaning Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B6Attributes.oilcup_full: { + "type": Platform.BINARY_SENSOR, + "name": "Oil-cup Full", + "icon": "mdi:cup", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B6Attributes.fan_level: { + "type": Platform.SENSOR, + "name": "Fan level", + "icon": "mdi:fan", + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xBF: { + "name": "Microwave Steam Oven", + "entities": { + BFAttributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "name": "Tank ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + BFAttributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + BFAttributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + BFAttributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + BFAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + BFAttributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + BFAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xC2: { + "name": "Toilet", + "entities": { + C2Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + C2Attributes.sensor_light: { + "type": Platform.SWITCH, + "name": "Sensor Light", + "icon": "mdi:lightbulb", + }, + C2Attributes.foam_shield: { + "type": Platform.SWITCH, + "name": "Foam Shield", + "icon": "mdi:chart-bubble", + }, + C2Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + C2Attributes.seat_status: { + "type": Platform.BINARY_SENSOR, + "name": "Seat Status", + "icon": "mdi:seat-legroom-normal", + }, + C2Attributes.lid_status: { + "type": Platform.BINARY_SENSOR, + "name": "Lid Status", + "icon": "mdi:toilet", + }, + C2Attributes.light_status: { + "type": Platform.BINARY_SENSOR, + "name": "Light Status", + "icon": "mdi:lightbulb", + "device_class": BinarySensorDeviceClass.LIGHT, + }, + C2Attributes.water_temperature: { + "type": Platform.SENSOR, + "name": "Water Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C2Attributes.seat_temperature: { + "type": Platform.SENSOR, + "name": "Seat Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C2Attributes.filter_life: { + "type": Platform.SENSOR, + "name": "Filter Life", + "icon": "mdi:toilet", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + C2Attributes.dry_level: { + "type": Platform.NUMBER, + "name": "Dry Level", + "icon": "mdi:fire", + "max": "max_dry_level", + "min": 0, + "step": 1, + }, + C2Attributes.water_temp_level: { + "type": Platform.NUMBER, + "name": "Water Temperature Level", + "icon": "mdi:fire", + "max": "max_water_temp_level", + "min": 0, + "step": 1, + }, + C2Attributes.seat_temp_level: { + "type": Platform.NUMBER, + "name": "Seat Temperature Level", + "icon": "mdi:fire", + "max": "max_seat_temp_level", + "min": 0, + "step": 1, + }, + }, + }, + 0xC3: { + "name": "Heat Pump Wi-Fi Controller", + "entities": { + "climate_zone1": { + "type": Platform.CLIMATE, + "icon": "mdi:air-conditioner", + "name": "Zone1 Thermostat", + "zone": 0, + "default": True, + }, + "climate_zone2": { + "type": Platform.CLIMATE, + "icon": "mdi:air-conditioner", + "name": "Zone2 Thermostat", + "zone": 1, + "default": True, + }, + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:heat-pump", + "name": "Domestic hot water", + "default": True, + }, + C3Attributes.disinfect: { + "type": Platform.SWITCH, + "name": "Disinfect", + "icon": "mdi:water-plus-outline", + }, + C3Attributes.dhw_power: { + "type": Platform.SWITCH, + "name": "DHW Power", + "icon": "mdi:power", + }, + C3Attributes.eco_mode: { + "type": Platform.SWITCH, + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + C3Attributes.fast_dhw: { + "type": Platform.SWITCH, + "name": "Fast DHW", + "icon": "mdi:rotate-orbit", + }, + C3Attributes.silent_mode: { + "type": Platform.SWITCH, + "name": "Silent Mode", + "icon": "mdi:fan-remove", + }, + C3Attributes.tbh: { + "type": Platform.SWITCH, + "name": "TBH", + "icon": "mdi:water-boiler", + }, + C3Attributes.zone1_curve: { + "type": Platform.SWITCH, + "name": "Zone1 Curve", + "icon": "mdi:chart-bell-curve-cumulative", + }, + C3Attributes.zone2_curve: { + "type": Platform.SWITCH, + "name": "Zone2 Curve", + "icon": "mdi:chart-bell-curve-cumulative", + }, + C3Attributes.zone1_power: { + "type": Platform.SWITCH, + "name": "Zone1 Power", + "icon": "mdi:power", + }, + C3Attributes.zone2_power: { + "type": Platform.SWITCH, + "name": "Zone2 Power", + "icon": "mdi:power", + }, + C3Attributes.zone1_water_temp_mode: { + "type": Platform.BINARY_SENSOR, + "name": "Zone1 Water-temperature Mode", + "icon": "mdi:coolant-temperature", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.zone2_water_temp_mode: { + "type": Platform.BINARY_SENSOR, + "name": "Zone2 Water-temperature Mode", + "icon": "mdi:coolant-temperature", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.zone1_room_temp_mode: { + "type": Platform.BINARY_SENSOR, + "name": "Zone1 Room-temperature Mode", + "icon": "mdi:home-thermometer-outline", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.zone2_room_temp_mode: { + "type": Platform.BINARY_SENSOR, + "name": "Zone2 Room-temperature Mode", + "icon": "mdi:home-thermometer-outline", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.error_code: { + "type": Platform.SENSOR, + "name": "Error Code", + "icon": "mdi:alpha-e-circle", + }, + C3Attributes.tank_actual_temperature: { + "type": Platform.SENSOR, + "name": "Tank Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C3Attributes.status_dhw: { + "type": Platform.BINARY_SENSOR, + "name": "DHW status", + "icon": "mdi:heat-pump", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.status_tbh: { + "type": Platform.BINARY_SENSOR, + "name": "TBH status", + "icon": "mdi:water-boiler", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.status_ibh: { + "type": Platform.BINARY_SENSOR, + "name": "IBH status", + "icon": "mdi:coolant-temperature", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.status_heating: { + "type": Platform.BINARY_SENSOR, + "name": "Heating status", + "icon": "mdi:heat-pump", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.total_energy_consumption: { + "type": Platform.SENSOR, + "name": "Total energy consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + C3Attributes.total_produced_energy: { + "type": Platform.SENSOR, + "name": "Total produced energy", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + C3Attributes.outdoor_temperature: { + "type": Platform.SENSOR, + "name": "Outdoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xCA: { + "name": "Refrigerator", + "entities": { + CAAttributes.bar_door: { + "type": Platform.BINARY_SENSOR, + "name": "Bar Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.bar_door_overtime: { + "type": Platform.BINARY_SENSOR, + "name": "Bar Door Overtime", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.flex_zone_door: { + "type": Platform.BINARY_SENSOR, + "name": "Flex Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.flex_zone_door_overtime: { + "type": Platform.BINARY_SENSOR, + "name": "Flex Zone Door", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.freezer_door: { + "type": Platform.BINARY_SENSOR, + "name": "Freezer Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.freezer_door_overtime: { + "type": Platform.BINARY_SENSOR, + "name": "Freezer Door Overtime", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.refrigerator_door: { + "type": Platform.BINARY_SENSOR, + "name": "Refrigerator Door", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.refrigerator_door_overtime: { + "type": Platform.BINARY_SENSOR, + "name": "Refrigerator Door Overtime", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.flex_zone_actual_temp: { + "type": Platform.SENSOR, + "name": "Flex Zone Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.flex_zone_setting_temp: { + "type": Platform.SENSOR, + "name": "Flex Zone Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.freezer_actual_temp: { + "type": Platform.SENSOR, + "name": "Freezer Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.freezer_setting_temp: { + "type": Platform.SENSOR, + "name": "Freezer Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.energy_consumption: { + "type": Platform.SENSOR, + "name": "Energy Consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + CAAttributes.refrigerator_actual_temp: { + "type": Platform.SENSOR, + "name": "Refrigerator Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.refrigerator_setting_temp: { + "type": Platform.SENSOR, + "name": "Refrigerator Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.right_flex_zone_actual_temp: { + "type": Platform.SENSOR, + "name": "Right Flex Zone Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.right_flex_zone_setting_temp: { + "type": Platform.SENSOR, + "name": "Right Flex Zone Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xCC: { + "name": "MDV Wi-Fi Controller", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "hass:air-conditioner", + "default": True, + }, + CCAttributes.aux_heating: { + "type": Platform.SWITCH, + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + CCAttributes.eco_mode: { + "type": Platform.SWITCH, + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + CCAttributes.night_light: { + "type": Platform.SWITCH, + "name": "Night Light", + "icon": "mdi:lightbulb", + }, + CCAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + CCAttributes.sleep_mode: { + "type": Platform.SWITCH, + "name": "Sleep Mode", + "icon": "mdi:power-sleep", + }, + CCAttributes.swing: { + "type": Platform.SWITCH, + "name": "Swing", + "icon": "mdi:arrow-split-horizontal", + }, + CCAttributes.indoor_temperature: { + "type": Platform.SENSOR, + "name": "Indoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xCD: { + "name": "Heat Pump Water Heater", + "entities": { + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:heat-pump", + "default": True, + }, + CDAttributes.compressor_status: { + "type": Platform.BINARY_SENSOR, + "name": "Compressor Status", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + CDAttributes.compressor_temperature: { + "type": Platform.SENSOR, + "name": "Compressor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.condenser_temperature: { + "type": Platform.SENSOR, + "name": "Condenser Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.outdoor_temperature: { + "type": Platform.SENSOR, + "name": "Outdoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + }, + }, + 0xCE: { + "name": "Fresh Air Appliance", + "entities": { + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + CEAttributes.filter_cleaning_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Filter Cleaning Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CEAttributes.filter_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Filter Change Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CEAttributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.co2: { + "type": Platform.SENSOR, + "name": "Carbon Dioxide", + "device_class": SensorDeviceClass.CO2, + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.hcho: { + "type": Platform.SENSOR, + "name": "Methanal", + "icon": "mdi:molecule", + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.pm25: { + "type": Platform.SENSOR, + "name": "PM 2.5", + "device_class": SensorDeviceClass.PM25, + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + CEAttributes.aux_heating: { + "type": Platform.SWITCH, + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + CEAttributes.eco_mode: { + "type": Platform.SWITCH, + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + CEAttributes.link_to_ac: { + "type": Platform.SWITCH, + "name": "Link to AC", + "icon": "mdi:link", + }, + CEAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + CEAttributes.powerful_purify: { + "type": Platform.SWITCH, + "name": "Powerful Purification", + "icon": "mdi:turbine", + }, + CEAttributes.sleep_mode: { + "type": Platform.SWITCH, + "name": "Sleep Mode", + "icon": "mdi:power-sleep", + }, + }, + }, + 0xCF: { + "name": "Heat Pump", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "hass:air-conditioner", + "default": True, + }, + CFAttributes.aux_heating: { + "type": Platform.SWITCH, + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + CFAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + CFAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xDA: { + "name": "Top Load Washer", + "entities": { + DAAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.wash_time: { + "type": Platform.SENSOR, + "name": "wash time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.soak_time: { + "type": Platform.SENSOR, + "name": "soak time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.dehydration_time: { + "type": Platform.SENSOR, + "name": "dehydration time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.dehydration_speed: { + "type": Platform.SENSOR, + "name": "dehydration speed", + "icon": "mdi:speedometer", + }, + DAAttributes.error_code: { + "type": Platform.SENSOR, + "name": "error code", + "icon": "mdi:washing-machine-alert", + }, + DAAttributes.rinse_count: { + "type": Platform.SENSOR, + "name": "rinse count", + "icon": "mdi:water-sync", + }, + DAAttributes.rinse_level: { + "type": Platform.SENSOR, + "name": "rinse level", + "icon": "mdi:hydraulic-oil-level", + }, + DAAttributes.wash_level: { + "type": Platform.SENSOR, + "name": "rinse count", + "icon": "mdi:hydraulic-oil-level", + }, + DAAttributes.wash_strength: { + "type": Platform.SENSOR, + "name": "wash strength", + "icon": "mdi:network-strength-4-cog", + }, + DAAttributes.softener: { + "type": Platform.SENSOR, + "name": "softener", + "icon": "mdi:tshirt-crew", + }, + DAAttributes.detergent: { + "type": Platform.SENSOR, + "name": "detergent", + "icon": "mdi:spray-bottle", + }, + DAAttributes.program: { + "type": Platform.SENSOR, + "name": "Program", + "icon": "mdi:progress-wrench", + }, + DAAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + DAAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + DAAttributes.start: { + "type": Platform.SWITCH, + "name": "Start", + "icon": "mdi:motion-play-outline", + }, + }, + }, + 0xDB: { + "name": "Front Load Washer", + "entities": { + DBAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DBAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + DBAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + DBAttributes.start: { + "type": Platform.SWITCH, + "name": "Start", + "icon": "mdi:motion-play-outline", + }, + }, + }, + 0xDC: { + "name": "Clothes Dryer", + "entities": { + DCAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DCAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + DCAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + DCAttributes.start: { + "type": Platform.SWITCH, + "name": "Start", + "icon": "mdi:motion-play-outline", + }, + }, + }, + 0xE1: { + "name": "Dishwasher", + "entities": { + E1Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + E1Attributes.rinse_aid: { + "type": Platform.BINARY_SENSOR, + "name": "Rinse Aid Shortage", + "icon": "mdi:bottle-tonic", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + E1Attributes.salt: { + "type": Platform.BINARY_SENSOR, + "name": "Salt Shortage", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + E1Attributes.humidity: { + "type": Platform.SENSOR, + "name": "Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + E1Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + E1Attributes.storage_remaining: { + "type": Platform.SENSOR, + "name": "Storage Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.HOURS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.temperature: { + "type": Platform.SENSOR, + "name": "Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + E1Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + E1Attributes.storage: { + "type": Platform.SWITCH, + "name": "Storage", + "icon": "mdi:repeat-variant", + }, + E1Attributes.mode: { + "type": Platform.SENSOR, + "name": "Working Mode", + "icon": "mdi:dishwasher", + }, + E1Attributes.error_code: { + "type": Platform.SENSOR, + "name": "Error Code", + "icon": "mdi:alert-box", + }, + E1Attributes.softwater: { + "type": Platform.SENSOR, + "name": "Softwater Level", + "icon": "mdi:shaker-outline", + }, + E1Attributes.bright: { + "type": Platform.SENSOR, + "name": "Bright Level", + "icon": "mdi:star-four-points", + }, + }, + }, + 0xE2: { + "name": "Electric Water Heater", + "entities": { + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-electric-outline", + "default": True, + }, + E2Attributes.heating: { + "type": Platform.BINARY_SENSOR, + "name": "Heating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E2Attributes.keep_warm: { + "type": Platform.BINARY_SENSOR, + "name": "Keep Warm", + "icon": "mdi:menu", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E2Attributes.protection: { + "type": Platform.BINARY_SENSOR, + "name": "Protection", + "icon": "mdi:shield-check", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E2Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E2Attributes.heating_time_remaining: { + "type": Platform.SENSOR, + "name": "Heating Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + E2Attributes.heating_power: { + "type": Platform.SENSOR, + "name": "Heating Power", + "device_class": SensorDeviceClass.POWER, + "unit": UnitOfPower.WATT, + "state_class": SensorStateClass.MEASUREMENT, + }, + E2Attributes.water_consumption: { + "type": Platform.SENSOR, + "name": "Water Consumption", + "icon": "mdi:water", + "unit": UnitOfVolume.LITERS, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + E2Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + E2Attributes.variable_heating: { + "type": Platform.SWITCH, + "name": "Variable Heating", + "icon": "mdi:waves", + }, + E2Attributes.whole_tank_heating: { + "type": Platform.SWITCH, + "name": "Whole Tank Heating", + "icon": "mdi:restore", + }, + }, + }, + 0xE3: { + "name": "Gas Water Heater", + "entities": { + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-gas", + "default": True, + }, + E3Attributes.burning_state: { + "type": Platform.BINARY_SENSOR, + "name": "Burning State", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E3Attributes.protection: { + "type": Platform.BINARY_SENSOR, + "name": "Protection", + "icon": "mdi:shield-check", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E3Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E3Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + E3Attributes.smart_volume: { + "type": Platform.SWITCH, + "name": "Smart Volume", + "icon": "mdi:recycle", + }, + E3Attributes.zero_cold_water: { + "type": Platform.SWITCH, + "name": "Zero Cold Water", + "icon": "mdi:restore", + }, + E3Attributes.zero_cold_pulse: { + "type": Platform.SWITCH, + "name": "Zero Cold Water (Pulse)", + "icon": "mdi:restore-alert", + }, + }, + }, + 0xE6: { + "name": "Gas Boilers", + "entities": { + "water_heater_heating": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-gas", + "name": "Heating", + "use": 0, + "default": True, + }, + "water_heater_bathing": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-gas", + "name": "Bathing", + "use": 1, + "default": True, + }, + E6Attributes.heating_working: { + "type": Platform.BINARY_SENSOR, + "name": "Heating Working Status", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E6Attributes.bathing_working: { + "type": Platform.BINARY_SENSOR, + "name": "Bathing Working Status", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E6Attributes.heating_leaving_temperature: { + "type": Platform.SENSOR, + "name": "Heating Leaving Water Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E6Attributes.bathing_leaving_temperature: { + "type": Platform.SENSOR, + "name": "Bathing Leaving Water Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E6Attributes.main_power: { + "type": Platform.SWITCH, + "name": "Main Power", + "icon": "mdi:power", + }, + E6Attributes.heating_power: { + "type": Platform.SWITCH, + "name": "Heating Power", + "icon": "mdi:heating-coil", + }, + }, + }, + 0xE8: { + "name": "Electric Slow Cooker", + "entities": { + E8Attributes.finished: { + "type": Platform.BINARY_SENSOR, + "name": "Finished", + "icon": "", + }, + E8Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + E8Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + E8Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.keep_warm_remaining: { + "type": Platform.SENSOR, + "name": "Keep Warm Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.working_time: { + "type": Platform.SENSOR, + "name": "Working Time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.target_temperature: { + "type": Platform.SENSOR, + "name": "Target Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xEA: { + "name": "Electric Rice Cooker", + "entities": { + EAAttributes.cooking: { + "type": Platform.BINARY_SENSOR, + "name": "Cooking", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + EAAttributes.keep_warm: { + "type": Platform.BINARY_SENSOR, + "name": "Keep Warm", + "icon": "mdi:menu", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + EAAttributes.bottom_temperature: { + "type": Platform.SENSOR, + "name": "Bottom Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EAAttributes.keep_warm_time: { + "type": Platform.SENSOR, + "name": "Keep Warm Time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + EAAttributes.mode: { + "type": Platform.SENSOR, + "name": "Mode", + "icon": "mdi:orbit", + }, + EAAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + EAAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + EAAttributes.top_temperature: { + "type": Platform.SENSOR, + "name": "Top Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xEC: { + "name": "Electric Pressure Cooker", + "entities": { + ECAttributes.cooking: { + "type": Platform.BINARY_SENSOR, + "name": "Cooking", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ECAttributes.with_pressure: { + "type": Platform.BINARY_SENSOR, + "name": "With Pressure", + "icon": "mdi:information", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ECAttributes.bottom_temperature: { + "type": Platform.SENSOR, + "name": "Bottom Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ECAttributes.keep_warm_time: { + "type": Platform.SENSOR, + "name": "Keep Warm Time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + ECAttributes.mode: { + "type": Platform.SENSOR, + "name": "Mode", + "icon": "mdi:orbit", + }, + ECAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + ECAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + ECAttributes.top_temperature: { + "type": Platform.SENSOR, + "name": "Top Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xED: { + "name": "Water Drinking Appliance", + "entities": { + EDAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + EDAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + EDAttributes.filter1: { + "type": Platform.SENSOR, + "name": "Filter1 Available Days", + "icon": "mdi:air-filter", + "unit": UnitOfTime.DAYS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.filter2: { + "type": Platform.SENSOR, + "name": "Filter2 Available Days", + "icon": "mdi:air-filter", + "unit": UnitOfTime.DAYS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.filter3: { + "type": Platform.SENSOR, + "name": "Filter3 Available Days", + "icon": "mdi:air-filter", + "unit": UnitOfTime.DAYS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.life1: { + "type": Platform.SENSOR, + "name": "Filter1 Life Level", + "icon": "mdi:percent", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.life2: { + "type": Platform.SENSOR, + "name": "Filter2 Life Level", + "icon": "mdi:percent", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.life3: { + "type": Platform.SENSOR, + "name": "Filter3 Life Level", + "icon": "mdi:percent", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.in_tds: { + "type": Platform.SENSOR, + "name": "In TDS", + "icon": "mdi:water", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.out_tds: { + "type": Platform.SENSOR, + "name": "Out TDS", + "icon": "mdi:water-plus", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.water_consumption: { + "type": Platform.SENSOR, + "name": "Water Consumption", + "icon": "mdi:water-pump", + "unit": UnitOfVolume.LITERS, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + }, + }, + 0xFA: { + "name": "Fan", + "entities": { + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + FAAttributes.oscillation_mode: { + "type": Platform.SELECT, + "name": "Oscillation Mode", + "options": "oscillation_modes", + "icon": "mdi:swap-horizontal-variant", + }, + FAAttributes.oscillation_angle: { + "type": Platform.SELECT, + "name": "Oscillation Angle", + "options": "oscillation_angles", + "icon": "mdi:pan-horizontal", + }, + FAAttributes.tilting_angle: { + "type": Platform.SELECT, + "name": "Tilting Angle", + "options": "tilting_angles", + "icon": "mdi:pan-vertical", + }, + FAAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + FAAttributes.oscillate: { + "type": Platform.SWITCH, + "name": "Oscillate", + "icon": "mdi:swap-horizontal-bold", + }, + FAAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + }, + }, + 0xFB: { + "name": "Electric Heater", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "mdi:air-conditioner", + "default": True, + }, + FBAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + FBAttributes.heating_level: { + "type": Platform.NUMBER, + "name": "Heating Level", + "icon": "mdi:fire", + "max": 10, + "min": 1, + "step": 1, + }, + FBAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + FBAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xFC: { + "name": "Air Purifier", + "entities": { + FCAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + FCAttributes.anion: { + "type": Platform.SWITCH, + "name": "Anion", + "icon": "mdi:vanish", + }, + FCAttributes.prompt_tone: { + "type": Platform.SWITCH, + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + FCAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + FCAttributes.standby: { + "type": Platform.SWITCH, + "name": "Standby", + "icon": "mdi:smoke-detector-variant", + }, + FCAttributes.detect_mode: { + "type": Platform.SELECT, + "name": "Detect Mode", + "options": "detect_modes", + "icon": "mdi:smoke-detector-variant", + }, + FCAttributes.mode: { + "type": Platform.SELECT, + "name": "Mode", + "options": "modes", + "icon": "mdi:rotate-360", + }, + FCAttributes.fan_speed: { + "type": Platform.SELECT, + "name": "Fan Speed", + "options": "fan_speeds", + "icon": "mdi:fan", + }, + FCAttributes.screen_display: { + "type": Platform.SELECT, + "name": "Screen Display", + "options": "screen_displays", + "icon": "mdi:television-ambient-light", + }, + FCAttributes.pm25: { + "type": Platform.SENSOR, + "name": "PM 2.5", + "device_class": SensorDeviceClass.PM25, + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.tvoc: { + "type": Platform.SENSOR, + "name": "TVOC", + "icon": "mdi:heat-wave", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.hcho: { + "type": Platform.SENSOR, + "name": "Methanal", + "icon": "mdi:molecule", + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.filter1_life: { + "type": Platform.SENSOR, + "name": "Filter1 Life Level", + "icon": "mdi:air-filter", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.filter2_life: { + "type": Platform.SENSOR, + "name": "Filter2 Life Level", + "icon": "mdi:air-filter", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xFD: { + "name": "Humidifier", + "entities": { + Platform.HUMIDIFIER: { + "type": Platform.HUMIDIFIER, + "icon": "mdi:air-humidifier", + "default": True, + }, + FDAttributes.disinfect: { + "type": Platform.SWITCH, + "name": "Disinfect", + "icon": "mdi:water-plus-outline", + }, + FDAttributes.prompt_tone: { + "type": Platform.SWITCH, + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + FDAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + FDAttributes.fan_speed: { + "type": Platform.SELECT, + "name": "Fan Speed", + "options": "fan_speeds", + "icon": "mdi:fan", + }, + FDAttributes.screen_display: { + "type": Platform.SELECT, + "name": "Screen Display", + "options": "screen_displays", + "icon": "mdi:television-ambient-light", + }, + FDAttributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + FDAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, +} diff --git a/custom_components/midea_ac_lan/midea_entity.py b/custom_components/midea_ac_lan/midea_entity.py index 2b82abc9..678c13e7 100644 --- a/custom_components/midea_ac_lan/midea_entity.py +++ b/custom_components/midea_ac_lan/midea_entity.py @@ -1,67 +1,67 @@ -import logging - -from homeassistant.helpers.entity import Entity - -from .const import DOMAIN -from .midea_devices import MIDEA_DEVICES - -_LOGGER = logging.getLogger(__name__) - - -class MideaEntity(Entity): - def __init__(self, device, entity_key: str): - self._device = device - self._device.register_update(self.update_state) - self._config = MIDEA_DEVICES[self._device.device_type]["entities"][entity_key] - self._entity_key = entity_key - self._unique_id = f"{DOMAIN}.{self._device.device_id}_{entity_key}" - self.entity_id = self._unique_id - self._device_name = self._device.name - - @property - def device(self): - return self._device - - @property - def device_info(self): - return { - "manufacturer": "Midea", - "model": f"{MIDEA_DEVICES[self._device.device_type]['name']} " - f"{self._device.model}" - f" ({self._device.subtype})", - "identifiers": {(DOMAIN, self._device.device_id)}, - "name": self._device_name, - } - - @property - def unique_id(self): - return self._unique_id - - @property - def should_poll(self): - return False - - @property - def name(self): - return ( - f"{self._device_name} {self._config.get('name')}" - if "name" in self._config - else self._device_name - ) - - @property - def available(self): - return self._device.available - - @property - def icon(self): - return self._config.get("icon") - - def update_state(self, status): - if self._entity_key in status or "available" in status: - try: - self.schedule_update_ha_state() - except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) +import logging + +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN +from .midea_devices import MIDEA_DEVICES + +_LOGGER = logging.getLogger(__name__) + + +class MideaEntity(Entity): + def __init__(self, device, entity_key: str): + self._device = device + self._device.register_update(self.update_state) + self._config = MIDEA_DEVICES[self._device.device_type]["entities"][entity_key] + self._entity_key = entity_key + self._unique_id = f"{DOMAIN}.{self._device.device_id}_{entity_key}" + self.entity_id = self._unique_id + self._device_name = self._device.name + + @property + def device(self): + return self._device + + @property + def device_info(self): + return { + "manufacturer": "Midea", + "model": f"{MIDEA_DEVICES[self._device.device_type]['name']} " + f"{self._device.model}" + f" ({self._device.subtype})", + "identifiers": {(DOMAIN, self._device.device_id)}, + "name": self._device_name, + } + + @property + def unique_id(self): + return self._unique_id + + @property + def should_poll(self): + return False + + @property + def name(self): + return ( + f"{self._device_name} {self._config.get('name')}" + if "name" in self._config + else self._device_name + ) + + @property + def available(self): + return self._device.available + + @property + def icon(self): + return self._config.get("icon") + + def update_state(self, status): + if self._entity_key in status or "available" in status: + try: + self.schedule_update_ha_state() + except Exception as e: + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) diff --git a/custom_components/midea_ac_lan/number.py b/custom_components/midea_ac_lan/number.py index 32ab1ad3..dbe8dd19 100644 --- a/custom_components/midea_ac_lan/number.py +++ b/custom_components/midea_ac_lan/number.py @@ -1,69 +1,69 @@ -from homeassistant.components.number import NumberEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - - -async def async_setup_entry(hass, config_entry, async_add_entities): - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) - numbers = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.NUMBER and entity_key in extra_switches: - dev = MideaNumber(device, entity_key) - numbers.append(dev) - async_add_entities(numbers) - - -class MideaNumber(MideaEntity, NumberEntity): - def __init__(self, device, entity_key: str): - super().__init__(device, entity_key) - self._max_value = self._config.get("max") - self._min_value = self._config.get("min") - self._step_value = self._config.get("step") - - @property - def native_min_value(self): - return ( - self._min_value - if isinstance(self._min_value, int) - else ( - self._device.get_attribute(attr=self._min_value) - if self._device.get_attribute(attr=self._min_value) - else getattr(self._device, self._min_value) - ) - ) - - @property - def native_max_value(self): - return ( - self._max_value - if isinstance(self._max_value, int) - else ( - self._device.get_attribute(attr=self._max_value) - if self._device.get_attribute(attr=self._max_value) - else getattr(self._device, self._max_value) - ) - ) - - @property - def native_step(self): - return ( - self._step_value - if isinstance(self._step_value, int) - else ( - self._device.get_attribute(attr=self._step_value) - if self._device.get_attribute(attr=self._step_value) - else getattr(self._device, self._step_value) - ) - ) - - @property - def native_value(self): - return self._device.get_attribute(self._entity_key) - - def set_native_value(self, value): - self._device.set_attribute(self._entity_key, value) +from homeassistant.components.number import NumberEntity +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry(hass, config_entry, async_add_entities): + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + numbers = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.NUMBER and entity_key in extra_switches: + dev = MideaNumber(device, entity_key) + numbers.append(dev) + async_add_entities(numbers) + + +class MideaNumber(MideaEntity, NumberEntity): + def __init__(self, device, entity_key: str): + super().__init__(device, entity_key) + self._max_value = self._config.get("max") + self._min_value = self._config.get("min") + self._step_value = self._config.get("step") + + @property + def native_min_value(self): + return ( + self._min_value + if isinstance(self._min_value, int) + else ( + self._device.get_attribute(attr=self._min_value) + if self._device.get_attribute(attr=self._min_value) + else getattr(self._device, self._min_value) + ) + ) + + @property + def native_max_value(self): + return ( + self._max_value + if isinstance(self._max_value, int) + else ( + self._device.get_attribute(attr=self._max_value) + if self._device.get_attribute(attr=self._max_value) + else getattr(self._device, self._max_value) + ) + ) + + @property + def native_step(self): + return ( + self._step_value + if isinstance(self._step_value, int) + else ( + self._device.get_attribute(attr=self._step_value) + if self._device.get_attribute(attr=self._step_value) + else getattr(self._device, self._step_value) + ) + ) + + @property + def native_value(self): + return self._device.get_attribute(self._entity_key) + + def set_native_value(self, value): + self._device.set_attribute(self._entity_key, value) diff --git a/custom_components/midea_ac_lan/select.py b/custom_components/midea_ac_lan/select.py index 9ca0430c..67cf1aec 100644 --- a/custom_components/midea_ac_lan/select.py +++ b/custom_components/midea_ac_lan/select.py @@ -1,35 +1,35 @@ -from homeassistant.components.select import SelectEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - - -async def async_setup_entry(hass, config_entry, async_add_entities): - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) - selects = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.SELECT and entity_key in extra_switches: - dev = MideaSelect(device, entity_key) - selects.append(dev) - async_add_entities(selects) - - -class MideaSelect(MideaEntity, SelectEntity): - def __init__(self, device, entity_key: str): - super().__init__(device, entity_key) - self._options_name = self._config.get("options") - - @property - def options(self): - return getattr(self._device, self._options_name) - - @property - def current_option(self): - return self._device.get_attribute(self._entity_key) - - def select_option(self, option: str): - self._device.set_attribute(self._entity_key, option) +from homeassistant.components.select import SelectEntity +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry(hass, config_entry, async_add_entities): + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + selects = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.SELECT and entity_key in extra_switches: + dev = MideaSelect(device, entity_key) + selects.append(dev) + async_add_entities(selects) + + +class MideaSelect(MideaEntity, SelectEntity): + def __init__(self, device, entity_key: str): + super().__init__(device, entity_key) + self._options_name = self._config.get("options") + + @property + def options(self): + return getattr(self._device, self._options_name) + + @property + def current_option(self): + return self._device.get_attribute(self._entity_key) + + def select_option(self, option: str): + self._device.set_attribute(self._entity_key, option) diff --git a/custom_components/midea_ac_lan/sensor.py b/custom_components/midea_ac_lan/sensor.py index ed088358..78221898 100644 --- a/custom_components/midea_ac_lan/sensor.py +++ b/custom_components/midea_ac_lan/sensor.py @@ -1,40 +1,40 @@ -from homeassistant.components.sensor import SensorEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - - -async def async_setup_entry(hass, config_entry, async_add_entities): - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_sensors = config_entry.options.get(CONF_SENSORS, []) - sensors = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.SENSOR and entity_key in extra_sensors: - sensor = MideaSensor(device, entity_key) - sensors.append(sensor) - async_add_entities(sensors) - - -class MideaSensor(MideaEntity, SensorEntity): - @property - def native_value(self): - return self._device.get_attribute(self._entity_key) - - @property - def device_class(self): - return self._config.get("device_class") - - @property - def state_class(self): - return self._config.get("state_class") - - @property - def native_unit_of_measurement(self): - return self._config.get("unit") - - @property - def capability_attributes(self): - return {"state_class": self.state_class} if self.state_class else {} +from homeassistant.components.sensor import SensorEntity +from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry(hass, config_entry, async_add_entities): + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_sensors = config_entry.options.get(CONF_SENSORS, []) + sensors = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.SENSOR and entity_key in extra_sensors: + sensor = MideaSensor(device, entity_key) + sensors.append(sensor) + async_add_entities(sensors) + + +class MideaSensor(MideaEntity, SensorEntity): + @property + def native_value(self): + return self._device.get_attribute(self._entity_key) + + @property + def device_class(self): + return self._config.get("device_class") + + @property + def state_class(self): + return self._config.get("state_class") + + @property + def native_unit_of_measurement(self): + return self._config.get("unit") + + @property + def capability_attributes(self): + return {"state_class": self.state_class} if self.state_class else {} diff --git a/custom_components/midea_ac_lan/services.yaml b/custom_components/midea_ac_lan/services.yaml index 20bc9bdc..49103179 100644 --- a/custom_components/midea_ac_lan/services.yaml +++ b/custom_components/midea_ac_lan/services.yaml @@ -1,18 +1,18 @@ ---- -set_attribute: - fields: - device_id: - example: "1234567890" - attribute: - example: "eco_mode" - value: - example: true - -send_command: - fields: - device_id: - example: "1234567890" - cmd_type: - example: 2 - cmd_body: - example: "B0FF01370E0000A500" +--- +set_attribute: + fields: + device_id: + example: "1234567890" + attribute: + example: "eco_mode" + value: + example: true + +send_command: + fields: + device_id: + example: "1234567890" + cmd_type: + example: 2 + cmd_body: + example: "B0FF01370E0000A500" diff --git a/custom_components/midea_ac_lan/switch.py b/custom_components/midea_ac_lan/switch.py index b312edfb..c6a24cff 100644 --- a/custom_components/midea_ac_lan/switch.py +++ b/custom_components/midea_ac_lan/switch.py @@ -1,30 +1,30 @@ -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform -from homeassistant.helpers.entity import ToggleEntity - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - - -async def async_setup_entry(hass, config_entry, async_add_entities): - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) - switches = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.SWITCH and entity_key in extra_switches: - dev = MideaSwitch(device, entity_key) - switches.append(dev) - async_add_entities(switches) - - -class MideaSwitch(MideaEntity, ToggleEntity): - @property - def is_on(self) -> bool: - return self._device.get_attribute(self._entity_key) - - def turn_on(self): - self._device.set_attribute(attr=self._entity_key, value=True) - - def turn_off(self): - self._device.set_attribute(attr=self._entity_key, value=False) +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform +from homeassistant.helpers.entity import ToggleEntity + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry(hass, config_entry, async_add_entities): + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + switches = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.SWITCH and entity_key in extra_switches: + dev = MideaSwitch(device, entity_key) + switches.append(dev) + async_add_entities(switches) + + +class MideaSwitch(MideaEntity, ToggleEntity): + @property + def is_on(self) -> bool: + return self._device.get_attribute(self._entity_key) + + def turn_on(self): + self._device.set_attribute(attr=self._entity_key, value=True) + + def turn_off(self): + self._device.set_attribute(attr=self._entity_key, value=False) diff --git a/custom_components/midea_ac_lan/translations/en.json b/custom_components/midea_ac_lan/translations/en.json index 526cc9ea..05796b09 100644 --- a/custom_components/midea_ac_lan/translations/en.json +++ b/custom_components/midea_ac_lan/translations/en.json @@ -1,119 +1,119 @@ -{ - "config": { - "error": { - "preset_account": "Failed to login with preset account, please report this issue", - "login_failed": "Failed to login, account or password was wrong", - "no_devices": "No new available appliances found on the network", - "device_exist": "Appliance is already configured", - "config_incorrect": "The configuration is incorrect", - "connect_error": "Can't connect to appliance", - "invalid_token": "Token or Key in an incorrect format" - }, - "step": { - "user": { - "data": { - "way": "Adding appliances" - }, - "description": "Choose the way to add a appliance", - "title": "Add new appliance" - }, - "login": { - "data": { - "account": "Account", - "password": "Password" - }, - "description": "Login and storage your Midea account only for getting the appliance info.\nYou can remove this configuration after all appliance configured.", - "title": "Login" - }, - "discovery": { - "description": "IP address for device, enter \"auto\" to discover automatically\nYou can also use an IP address to search within a specified network, such as \"192.168.1.255\"", - "title": "Search", - "data": { - "ip_address": "IP address" - } - }, - "list":{ - "description": "{table}", - "title": "Appliances" - }, - "auto": { - "data": { - "device": "Appliances" - }, - "description": "Choose a appliance to add", - "title": "New appliance found" - }, - "manually": { - "data": { - "name": "Name (e.g. Living room AC)", - "device_id": "Appliance code", - "type": "Type", - "ip_address": "IP address", - "port": "Port", - "model": "Model", - "subtype": "Subtype", - "protocol": "Protocol", - "token": "Token", - "key": "Key" - }, - "description": "Configuration of appliance", - "title": "New appliance" - } - } - }, - "options": { - "step": { - "init": { - "data": { - "ip_address": "IP address", - "refresh_interval": "Refresh interval (0 means not refreshing actively)", - "sensors": "Extra sensors", - "switches": "Extra controls", - "customize": "Customize" - }, - "title": "Configure" - } - }, - "abort": { - "account_option": "The account does not supports this operation.\nClick \"ADD DEVICE\" to add a new device." - } - }, - "services": { - "set_attribute": { - "name": "Set attribute", - "description": "Set the attribute value of device", - "fields" : { - "device_id": { - "name": "Appliance code", - "description": "The appliance code (Device ID) of appliance" - }, - "attribute": { - "name": "Attribute", - "description": "The attribute name want to set" - }, - "value": { - "name": "Value", - "description": "The attribute value want to set" - } - } - }, - "send_command": { - "name": "Customize command", - "description": "Send a customize command to device", - "fields" : { - "device_id": { - "name": "Appliance code", - "description": "The appliance code (Device ID) of appliance" - }, - "cmd_type": { - "name": "command type", - "description": "The type of command,should be 3(query) or 2(set)" - }, - "cmd_body": { - "name": "command body", - "description": "The body of command, (not include MSmart protocol head and cheksum at the end)" - } - } - } - } -} +{ + "config": { + "error": { + "preset_account": "Failed to login with preset account, please report this issue", + "login_failed": "Failed to login, account or password was wrong", + "no_devices": "No new available appliances found on the network", + "device_exist": "Appliance is already configured", + "config_incorrect": "The configuration is incorrect", + "connect_error": "Can't connect to appliance", + "invalid_token": "Token or Key in an incorrect format" + }, + "step": { + "user": { + "data": { + "way": "Adding appliances" + }, + "description": "Choose the way to add a appliance", + "title": "Add new appliance" + }, + "login": { + "data": { + "account": "Account", + "password": "Password" + }, + "description": "Login and storage your Midea account only for getting the appliance info.\nYou can remove this configuration after all appliance configured.", + "title": "Login" + }, + "discovery": { + "description": "IP address for device, enter \"auto\" to discover automatically\nYou can also use an IP address to search within a specified network, such as \"192.168.1.255\"", + "title": "Search", + "data": { + "ip_address": "IP address" + } + }, + "list":{ + "description": "{table}", + "title": "Appliances" + }, + "auto": { + "data": { + "device": "Appliances" + }, + "description": "Choose a appliance to add", + "title": "New appliance found" + }, + "manually": { + "data": { + "name": "Name (e.g. Living room AC)", + "device_id": "Appliance code", + "type": "Type", + "ip_address": "IP address", + "port": "Port", + "model": "Model", + "subtype": "Subtype", + "protocol": "Protocol", + "token": "Token", + "key": "Key" + }, + "description": "Configuration of appliance", + "title": "New appliance" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "IP address", + "refresh_interval": "Refresh interval (0 means not refreshing actively)", + "sensors": "Extra sensors", + "switches": "Extra controls", + "customize": "Customize" + }, + "title": "Configure" + } + }, + "abort": { + "account_option": "The account does not supports this operation.\nClick \"ADD DEVICE\" to add a new device." + } + }, + "services": { + "set_attribute": { + "name": "Set attribute", + "description": "Set the attribute value of device", + "fields" : { + "device_id": { + "name": "Appliance code", + "description": "The appliance code (Device ID) of appliance" + }, + "attribute": { + "name": "Attribute", + "description": "The attribute name want to set" + }, + "value": { + "name": "Value", + "description": "The attribute value want to set" + } + } + }, + "send_command": { + "name": "Customize command", + "description": "Send a customize command to device", + "fields" : { + "device_id": { + "name": "Appliance code", + "description": "The appliance code (Device ID) of appliance" + }, + "cmd_type": { + "name": "command type", + "description": "The type of command,should be 3(query) or 2(set)" + }, + "cmd_body": { + "name": "command body", + "description": "The body of command, (not include MSmart protocol head and cheksum at the end)" + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/translations/zh-Hans.json b/custom_components/midea_ac_lan/translations/zh-Hans.json index 43010504..bb68410c 100644 --- a/custom_components/midea_ac_lan/translations/zh-Hans.json +++ b/custom_components/midea_ac_lan/translations/zh-Hans.json @@ -1,119 +1,119 @@ -{ - "config": { - "error": { - "preset_account": "预置账户登录失败,请报告此问题", - "login_failed": "登录失败, 用户名或密码错", - "no_devices": "未在网络上发现可用新设备", - "device_exist": "设备已经存在, 请添加其它设备", - "config_incorrect": "配置信息不正确, 请检查后重新输入", - "connect_error": "无法连接到指定设备", - "invalid_token": "Token或Key格式不正确" - }, - "step": { - "user": { - "data": { - "way": "添加方式" - }, - "description": "选择添加设备的方式", - "title": "添加新设备" - }, - "login": { - "data": { - "account": "账号", - "password": "密码" - }, - "description": "登录并保存你的美的账户,仅用于获取添加设备时设备信息\n添加设备完成后,你可以删除该配置", - "title": "登录" - }, - "discovery": { - "description": "输入设备的IP地址, 输入\"auto\"自动搜索设备\n你也可以使用IP地址在指定网络中搜索, 比如`192.168.1.255`", - "title": "搜索", - "data": { - "ip_address": "IP地址" - } - }, - "list":{ - "description": "{table}", - "title": "设备" - }, - "auto": { - "data": { - "device": "设备" - }, - "description": "选择设备并添加", - "title": "发现新设备" - }, - "manually": { - "data": { - "name": "名称(如客厅空调)", - "device_id": "设备编号", - "type": "设备类型", - "ip_address": "IP地址", - "port": "端口", - "model": "型号", - "subtype": "子型号", - "protocol": "协议版本", - "token": "Token", - "key": "Key" - }, - "description": "配置设备参数", - "title": "新设备" - } - } - }, - "options": { - "step": { - "init": { - "data": { - "ip_address": "IP地址", - "refresh_interval": "刷新间隔(设0为不进行主动刷新)", - "sensors": "扩展传感器", - "switches": "扩展控制", - "customize": "自定义" - }, - "title": "配置" - } - }, - "abort": { - "account_option": "账户配置不支持此操作.\n点击\"添加设备\"完成添加新设备的操作" - } - }, - "services": { - "set_attribute": { - "name": "设置属性", - "description": "设置设备的属性值", - "fields" : { - "device_id": { - "name": "设备编码", - "description": "设备编码(Deivce ID)" - }, - "attribute": { - "name": "属性", - "description": "要设置的属性名称" - }, - "value": { - "name": "值", - "description": "要设置的属性值" - } - } - }, - "send_command": { - "name": "自定义命令", - "description": "向设备发送一个自定义命令", - "fields" : { - "device_id": { - "name": "设备编码", - "description": "设备编码(Device ID)" - }, - "cmd_type": { - "name": "命令类型", - "description": "命令类型,可以为2(查询)或3(设置)" - }, - "cmd_body": { - "name": "命令体", - "description": "命令的消息体(不包括前部的MSmart协议头及后部的校验码)" - } - } - } - } +{ + "config": { + "error": { + "preset_account": "预置账户登录失败,请报告此问题", + "login_failed": "登录失败, 用户名或密码错", + "no_devices": "未在网络上发现可用新设备", + "device_exist": "设备已经存在, 请添加其它设备", + "config_incorrect": "配置信息不正确, 请检查后重新输入", + "connect_error": "无法连接到指定设备", + "invalid_token": "Token或Key格式不正确" + }, + "step": { + "user": { + "data": { + "way": "添加方式" + }, + "description": "选择添加设备的方式", + "title": "添加新设备" + }, + "login": { + "data": { + "account": "账号", + "password": "密码" + }, + "description": "登录并保存你的美的账户,仅用于获取添加设备时设备信息\n添加设备完成后,你可以删除该配置", + "title": "登录" + }, + "discovery": { + "description": "输入设备的IP地址, 输入\"auto\"自动搜索设备\n你也可以使用IP地址在指定网络中搜索, 比如`192.168.1.255`", + "title": "搜索", + "data": { + "ip_address": "IP地址" + } + }, + "list":{ + "description": "{table}", + "title": "设备" + }, + "auto": { + "data": { + "device": "设备" + }, + "description": "选择设备并添加", + "title": "发现新设备" + }, + "manually": { + "data": { + "name": "名称(如客厅空调)", + "device_id": "设备编号", + "type": "设备类型", + "ip_address": "IP地址", + "port": "端口", + "model": "型号", + "subtype": "子型号", + "protocol": "协议版本", + "token": "Token", + "key": "Key" + }, + "description": "配置设备参数", + "title": "新设备" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "IP地址", + "refresh_interval": "刷新间隔(设0为不进行主动刷新)", + "sensors": "扩展传感器", + "switches": "扩展控制", + "customize": "自定义" + }, + "title": "配置" + } + }, + "abort": { + "account_option": "账户配置不支持此操作.\n点击\"添加设备\"完成添加新设备的操作" + } + }, + "services": { + "set_attribute": { + "name": "设置属性", + "description": "设置设备的属性值", + "fields" : { + "device_id": { + "name": "设备编码", + "description": "设备编码(Deivce ID)" + }, + "attribute": { + "name": "属性", + "description": "要设置的属性名称" + }, + "value": { + "name": "值", + "description": "要设置的属性值" + } + } + }, + "send_command": { + "name": "自定义命令", + "description": "向设备发送一个自定义命令", + "fields" : { + "device_id": { + "name": "设备编码", + "description": "设备编码(Device ID)" + }, + "cmd_type": { + "name": "命令类型", + "description": "命令类型,可以为2(查询)或3(设置)" + }, + "cmd_body": { + "name": "命令体", + "description": "命令的消息体(不包括前部的MSmart协议头及后部的校验码)" + } + } + } + } } \ No newline at end of file diff --git a/custom_components/midea_ac_lan/water_heater.py b/custom_components/midea_ac_lan/water_heater.py index 13d2a9e6..20d3959c 100644 --- a/custom_components/midea_ac_lan/water_heater.py +++ b/custom_components/midea_ac_lan/water_heater.py @@ -1,305 +1,305 @@ -import functools as ft -import logging - -from homeassistant.components.water_heater import ( - WaterHeaterEntity, - WaterHeaterEntityFeature, -) -from homeassistant.const import ( - ATTR_TEMPERATURE, - CONF_DEVICE_ID, - CONF_SWITCHES, - PRECISION_HALVES, - PRECISION_WHOLE, - STATE_OFF, - STATE_ON, - Platform, - UnitOfTemperature, -) - -from .const import DEVICES, DOMAIN -from .midea.devices.c3.device import DeviceAttributes as C3Attributes -from .midea.devices.cd.device import DeviceAttributes as CDAttributes -from .midea.devices.e6.device import DeviceAttributes as E6Attributes -from .midea_devices import MIDEA_DEVICES -from .midea_entity import MideaEntity - -_LOGGER = logging.getLogger(__name__) - -E2_TEMPERATURE_MAX = 75 -E2_TEMPERATURE_MIN = 30 -E3_TEMPERATURE_MAX = 65 -E3_TEMPERATURE_MIN = 35 - - -async def async_setup_entry(hass, config_entry, async_add_entities): - device_id = config_entry.data.get(CONF_DEVICE_ID) - device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) - devs = [] - for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.WATER_HEATER and ( - config.get("default") or entity_key in extra_switches - ): - if device.device_type == 0xE2: - devs.append(MideaE2WaterHeater(device, entity_key)) - elif device.device_type == 0xE3: - devs.append(MideaE3WaterHeater(device, entity_key)) - elif device.device_type == 0xE6: - devs.append(MideaE6WaterHeater(device, entity_key, config["use"])) - elif device.device_type == 0xC3: - devs.append(MideaC3WaterHeater(device, entity_key)) - elif device.device_type == 0xCD: - devs.append(MideaCDWaterHeater(device, entity_key)) - async_add_entities(devs) - - -class MideaWaterHeater(MideaEntity, WaterHeaterEntity): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - self._operations = [] - - @property - def supported_features(self): - return WaterHeaterEntityFeature.TARGET_TEMPERATURE - - @property - def extra_state_attributes(self) -> dict: - attrs = self._device.attributes - if hasattr(self._device, "temperature_step"): - attrs["target_temp_step"] = self._device.temperature_step - return attrs - - @property - def min_temp(self): - return NotImplementedError - - @property - def max_temp(self): - return NotImplementedError - - @property - def target_temperature_low(self): - return self.min_temp - - @property - def target_temperature_high(self): - return self.max_temp - - @property - def precision(self): - return PRECISION_WHOLE - - @property - def temperature_unit(self): - return UnitOfTemperature.CELSIUS - - @property - def current_operation(self): - return ( - self._device.get_attribute("mode") - if self._device.get_attribute("power") - else STATE_OFF - ) - - @property - def current_temperature(self): - return self._device.get_attribute("current_temperature") - - @property - def target_temperature(self): - return self._device.get_attribute("target_temperature") - - def set_temperature(self, **kwargs): - if ATTR_TEMPERATURE not in kwargs: - return - temperature = int(kwargs.get(ATTR_TEMPERATURE)) - self._device.set_attribute("target_temperature", temperature) - - def set_operation_mode(self, operation_mode): - self._device.set_attribute(attr="mode", value=operation_mode) - - @property - def operation_list(self): - return getattr(self._device, "preset_modes") - - def turn_on(self): - self._device.set_attribute(attr="power", value=True) - - def turn_off(self): - self._device.set_attribute(attr="power", value=False) - - async def async_turn_on(self, **kwargs): - await self.hass.async_add_executor_job(ft.partial(self.turn_on, **kwargs)) - - async def async_turn_off(self, **kwargs): - await self.hass.async_add_executor_job(ft.partial(self.turn_off, **kwargs)) - - def update_state(self, status): - try: - self.schedule_update_ha_state() - except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) - - -class MideaE2WaterHeater(MideaWaterHeater): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - - @property - def min_temp(self): - return E2_TEMPERATURE_MIN - - @property - def max_temp(self): - return E2_TEMPERATURE_MAX - - -class MideaE3WaterHeater(MideaWaterHeater): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - - @property - def min_temp(self): - return E3_TEMPERATURE_MIN - - @property - def max_temp(self): - return E3_TEMPERATURE_MAX - - @property - def precision(self): - return PRECISION_HALVES if self._device.precision_halves else PRECISION_WHOLE - - -class MideaC3WaterHeater(MideaWaterHeater): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - - @property - def state(self): - return ( - STATE_ON - if self._device.get_attribute(C3Attributes.dhw_power) - else STATE_OFF - ) - - @property - def current_temperature(self): - return self._device.get_attribute(C3Attributes.tank_actual_temperature) - - @property - def target_temperature(self): - return self._device.get_attribute(C3Attributes.dhw_target_temp) - - def set_temperature(self, **kwargs): - if ATTR_TEMPERATURE not in kwargs: - return - temperature = int(kwargs.get(ATTR_TEMPERATURE)) - self._device.set_attribute(C3Attributes.dhw_target_temp, temperature) - - @property - def min_temp(self): - return self._device.get_attribute(C3Attributes.dhw_temp_min) - - @property - def max_temp(self): - return self._device.get_attribute(C3Attributes.dhw_temp_max) - - def turn_on(self): - self._device.set_attribute(attr=C3Attributes.dhw_power, value=True) - - def turn_off(self): - self._device.set_attribute(attr=C3Attributes.dhw_power, value=False) - - -class MideaE6WaterHeater(MideaWaterHeater): - _powers = [ - E6Attributes.heating_power, - E6Attributes.main_power, - ] - _current_temperatures = [ - E6Attributes.heating_leaving_temperature, - E6Attributes.bathing_leaving_temperature, - ] - _target_temperatures = [ - E6Attributes.heating_temperature, - E6Attributes.bathing_temperature, - ] - - def __init__(self, device, entity_key, use): - super().__init__(device, entity_key) - self._use = use - self._power_attr = MideaE6WaterHeater._powers[self._use] - self._current_temperature_attr = MideaE6WaterHeater._current_temperatures[ - self._use - ] - self._target_temperature_attr = MideaE6WaterHeater._target_temperatures[ - self._use - ] - - @property - def state(self): - if self._use == 0: # for heating - return ( - STATE_ON - if self._device.get_attribute(E6Attributes.main_power) - and self._device.get_attribute(E6Attributes.heating_power) - else STATE_OFF - ) - else: # for bathing - return ( - STATE_ON - if self._device.get_attribute(E6Attributes.main_power) - else STATE_OFF - ) - - @property - def current_temperature(self): - return self._device.get_attribute(self._current_temperature_attr) - - @property - def target_temperature(self): - return self._device.get_attribute(self._target_temperature_attr) - - def set_temperature(self, **kwargs): - if ATTR_TEMPERATURE not in kwargs: - return - temperature = int(kwargs.get(ATTR_TEMPERATURE)) - self._device.set_attribute(self._target_temperature_attr, temperature) - - @property - def min_temp(self): - return self._device.get_attribute(E6Attributes.min_temperature)[self._use] - - @property - def max_temp(self): - return self._device.get_attribute(E6Attributes.max_temperature)[self._use] - - def turn_on(self): - self._device.set_attribute(attr=self._power_attr, value=True) - - def turn_off(self): - self._device.set_attribute(attr=self._power_attr, value=False) - - -class MideaCDWaterHeater(MideaWaterHeater): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - - @property - def supported_features(self): - return ( - WaterHeaterEntityFeature.TARGET_TEMPERATURE - | WaterHeaterEntityFeature.OPERATION_MODE - ) - - @property - def min_temp(self): - return self._device.get_attribute(CDAttributes.min_temperature) - - @property - def max_temp(self): - return self._device.get_attribute(CDAttributes.max_temperature) +import functools as ft +import logging + +from homeassistant.components.water_heater import ( + WaterHeaterEntity, + WaterHeaterEntityFeature, +) +from homeassistant.const import ( + ATTR_TEMPERATURE, + CONF_DEVICE_ID, + CONF_SWITCHES, + PRECISION_HALVES, + PRECISION_WHOLE, + STATE_OFF, + STATE_ON, + Platform, + UnitOfTemperature, +) + +from .const import DEVICES, DOMAIN +from .midea.devices.c3.device import DeviceAttributes as C3Attributes +from .midea.devices.cd.device import DeviceAttributes as CDAttributes +from .midea.devices.e6.device import DeviceAttributes as E6Attributes +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + +_LOGGER = logging.getLogger(__name__) + +E2_TEMPERATURE_MAX = 75 +E2_TEMPERATURE_MIN = 30 +E3_TEMPERATURE_MAX = 65 +E3_TEMPERATURE_MIN = 35 + + +async def async_setup_entry(hass, config_entry, async_add_entities): + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + devs = [] + for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): + if config["type"] == Platform.WATER_HEATER and ( + config.get("default") or entity_key in extra_switches + ): + if device.device_type == 0xE2: + devs.append(MideaE2WaterHeater(device, entity_key)) + elif device.device_type == 0xE3: + devs.append(MideaE3WaterHeater(device, entity_key)) + elif device.device_type == 0xE6: + devs.append(MideaE6WaterHeater(device, entity_key, config["use"])) + elif device.device_type == 0xC3: + devs.append(MideaC3WaterHeater(device, entity_key)) + elif device.device_type == 0xCD: + devs.append(MideaCDWaterHeater(device, entity_key)) + async_add_entities(devs) + + +class MideaWaterHeater(MideaEntity, WaterHeaterEntity): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + self._operations = [] + + @property + def supported_features(self): + return WaterHeaterEntityFeature.TARGET_TEMPERATURE + + @property + def extra_state_attributes(self) -> dict: + attrs = self._device.attributes + if hasattr(self._device, "temperature_step"): + attrs["target_temp_step"] = self._device.temperature_step + return attrs + + @property + def min_temp(self): + return NotImplementedError + + @property + def max_temp(self): + return NotImplementedError + + @property + def target_temperature_low(self): + return self.min_temp + + @property + def target_temperature_high(self): + return self.max_temp + + @property + def precision(self): + return PRECISION_WHOLE + + @property + def temperature_unit(self): + return UnitOfTemperature.CELSIUS + + @property + def current_operation(self): + return ( + self._device.get_attribute("mode") + if self._device.get_attribute("power") + else STATE_OFF + ) + + @property + def current_temperature(self): + return self._device.get_attribute("current_temperature") + + @property + def target_temperature(self): + return self._device.get_attribute("target_temperature") + + def set_temperature(self, **kwargs): + if ATTR_TEMPERATURE not in kwargs: + return + temperature = int(kwargs.get(ATTR_TEMPERATURE)) + self._device.set_attribute("target_temperature", temperature) + + def set_operation_mode(self, operation_mode): + self._device.set_attribute(attr="mode", value=operation_mode) + + @property + def operation_list(self): + return getattr(self._device, "preset_modes") + + def turn_on(self): + self._device.set_attribute(attr="power", value=True) + + def turn_off(self): + self._device.set_attribute(attr="power", value=False) + + async def async_turn_on(self, **kwargs): + await self.hass.async_add_executor_job(ft.partial(self.turn_on, **kwargs)) + + async def async_turn_off(self, **kwargs): + await self.hass.async_add_executor_job(ft.partial(self.turn_off, **kwargs)) + + def update_state(self, status): + try: + self.schedule_update_ha_state() + except Exception as e: + _LOGGER.debug( + f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" + ) + + +class MideaE2WaterHeater(MideaWaterHeater): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + + @property + def min_temp(self): + return E2_TEMPERATURE_MIN + + @property + def max_temp(self): + return E2_TEMPERATURE_MAX + + +class MideaE3WaterHeater(MideaWaterHeater): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + + @property + def min_temp(self): + return E3_TEMPERATURE_MIN + + @property + def max_temp(self): + return E3_TEMPERATURE_MAX + + @property + def precision(self): + return PRECISION_HALVES if self._device.precision_halves else PRECISION_WHOLE + + +class MideaC3WaterHeater(MideaWaterHeater): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + + @property + def state(self): + return ( + STATE_ON + if self._device.get_attribute(C3Attributes.dhw_power) + else STATE_OFF + ) + + @property + def current_temperature(self): + return self._device.get_attribute(C3Attributes.tank_actual_temperature) + + @property + def target_temperature(self): + return self._device.get_attribute(C3Attributes.dhw_target_temp) + + def set_temperature(self, **kwargs): + if ATTR_TEMPERATURE not in kwargs: + return + temperature = int(kwargs.get(ATTR_TEMPERATURE)) + self._device.set_attribute(C3Attributes.dhw_target_temp, temperature) + + @property + def min_temp(self): + return self._device.get_attribute(C3Attributes.dhw_temp_min) + + @property + def max_temp(self): + return self._device.get_attribute(C3Attributes.dhw_temp_max) + + def turn_on(self): + self._device.set_attribute(attr=C3Attributes.dhw_power, value=True) + + def turn_off(self): + self._device.set_attribute(attr=C3Attributes.dhw_power, value=False) + + +class MideaE6WaterHeater(MideaWaterHeater): + _powers = [ + E6Attributes.heating_power, + E6Attributes.main_power, + ] + _current_temperatures = [ + E6Attributes.heating_leaving_temperature, + E6Attributes.bathing_leaving_temperature, + ] + _target_temperatures = [ + E6Attributes.heating_temperature, + E6Attributes.bathing_temperature, + ] + + def __init__(self, device, entity_key, use): + super().__init__(device, entity_key) + self._use = use + self._power_attr = MideaE6WaterHeater._powers[self._use] + self._current_temperature_attr = MideaE6WaterHeater._current_temperatures[ + self._use + ] + self._target_temperature_attr = MideaE6WaterHeater._target_temperatures[ + self._use + ] + + @property + def state(self): + if self._use == 0: # for heating + return ( + STATE_ON + if self._device.get_attribute(E6Attributes.main_power) + and self._device.get_attribute(E6Attributes.heating_power) + else STATE_OFF + ) + else: # for bathing + return ( + STATE_ON + if self._device.get_attribute(E6Attributes.main_power) + else STATE_OFF + ) + + @property + def current_temperature(self): + return self._device.get_attribute(self._current_temperature_attr) + + @property + def target_temperature(self): + return self._device.get_attribute(self._target_temperature_attr) + + def set_temperature(self, **kwargs): + if ATTR_TEMPERATURE not in kwargs: + return + temperature = int(kwargs.get(ATTR_TEMPERATURE)) + self._device.set_attribute(self._target_temperature_attr, temperature) + + @property + def min_temp(self): + return self._device.get_attribute(E6Attributes.min_temperature)[self._use] + + @property + def max_temp(self): + return self._device.get_attribute(E6Attributes.max_temperature)[self._use] + + def turn_on(self): + self._device.set_attribute(attr=self._power_attr, value=True) + + def turn_off(self): + self._device.set_attribute(attr=self._power_attr, value=False) + + +class MideaCDWaterHeater(MideaWaterHeater): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + + @property + def supported_features(self): + return ( + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.OPERATION_MODE + ) + + @property + def min_temp(self): + return self._device.get_attribute(CDAttributes.min_temperature) + + @property + def max_temp(self): + return self._device.get_attribute(CDAttributes.max_temperature) diff --git a/doc/A1.md b/doc/A1.md index e9dc6910..349749db 100644 --- a/doc/A1.md +++ b/doc/A1.md @@ -1,71 +1,71 @@ -# Dehumidifier -## Features -- Supports preset mode -- Supports fan mode -- Supports swing -- Supports humidity setting - -## Entities -### Default entity -| EntityID | Class | Description | -|----------------------------------|------------|-------------------| -| humidifier.{DEVICEID}_humidifier | humidifier | Humidifier entity | - -### Extra entities - -| EntityID | Class | Description | -|---------------------------------------|---------------|---------------------| -| sensor.{DEVICEID}_tank_full | binary_sensor | Tank Status | -| sensor.{DEVICEID}_tank | sensor | Tank | -| sensor.{DEVICEID}_current_humidity | sensor | Current Humidity | -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | -| lock.{DEVICEID}_child_lock | lock | Child Lock | -| switch.{DEVICEID}_anion | switch | Anion | -| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_swing | switch | Swing | -| select.{DEVICEID}_fan_speed | select | Fan Speed | -| select.{DEVICEID}_water_level_set | select | Water Level Setting | - -## Service - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|--------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "child_lock"
"anion"
"prompt_tone"
"power"
"swing" | -| value | true or false | - -| Name | Description | -|-----------|-----------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "fan_speed" | -| value | "Lowest"
"Low"
"Medium"
"High"
"Auto"
"Off" | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "water_level_set" | -| value | "25"
"50"
"75"
"100" | - -Example -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: prompt_tone - value: true -``` - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: fan_speed - value: Medium +# Dehumidifier +## Features +- Supports preset mode +- Supports fan mode +- Supports swing +- Supports humidity setting + +## Entities +### Default entity +| EntityID | Class | Description | +|----------------------------------|------------|-------------------| +| humidifier.{DEVICEID}_humidifier | humidifier | Humidifier entity | + +### Extra entities + +| EntityID | Class | Description | +|---------------------------------------|---------------|---------------------| +| sensor.{DEVICEID}_tank_full | binary_sensor | Tank Status | +| sensor.{DEVICEID}_tank | sensor | Tank | +| sensor.{DEVICEID}_current_humidity | sensor | Current Humidity | +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | +| lock.{DEVICEID}_child_lock | lock | Child Lock | +| switch.{DEVICEID}_anion | switch | Anion | +| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_swing | switch | Swing | +| select.{DEVICEID}_fan_speed | select | Fan Speed | +| select.{DEVICEID}_water_level_set | select | Water Level Setting | + +## Service + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|--------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "child_lock"
"anion"
"prompt_tone"
"power"
"swing" | +| value | true or false | + +| Name | Description | +|-----------|-----------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "fan_speed" | +| value | "Lowest"
"Low"
"Medium"
"High"
"Auto"
"Off" | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "water_level_set" | +| value | "25"
"50"
"75"
"100" | + +Example +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: prompt_tone + value: true +``` + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: fan_speed + value: Medium ``` \ No newline at end of file diff --git a/doc/A1_hans.md b/doc/A1_hans.md index 3acc176c..8a0d93ea 100644 --- a/doc/A1_hans.md +++ b/doc/A1_hans.md @@ -1,71 +1,71 @@ -# 家用空调 -## 特性 -- 支持运行模式 -- 支持风扇模式设定 -- 支持摆风 -- 支持湿度设定 - -## 生成实体 -### 默认生成实体 -| 实体ID | 类型 | 描述 | -|----------------------------------|------------|-------| -| humidifier.{DEVICEID}_humidifier | humidifier | 加湿器实体 | - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|---------------------------------------|---------------|---------------------|----------| -| sensor.{DEVICEID}_tank_full | binary_sensor | Tank Full | 水箱已达设置水位 | -| sensor.{DEVICEID}_tank | sensor | Tank | 水箱水位 | -| sensor.{DEVICEID}_current_humidity | sensor | Current Humidity | 当前湿度 | -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | -| lock.{DEVICEID}_child_lock | lock | Child Lock | 童锁 | -| switch.{DEVICEID}_anion | switch | Anion | 负离子开关 | -| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | 提示音 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_swing | switch | Swing | 摆风开关 | -| select.{DEVICEID}_fan_speed | select | Fan Speed | 风速设定 | -| select.{DEVICEID}_water_level_set | select | Water Level Setting | 水位设定 | - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|--------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "child_lock"
"anion"
"prompt_tone"
"power"
"swing" | -| value | true 或 false | - -| 名称 | 描述 | -|-----------|-----------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "fan_speed" | -| value | "Lowest"
"Low"
"Medium"
"High"
"Auto"
"Off" | - -| 名称 | 描述 | -|-----------|----------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "water_level_set" | -| value | "25"
"50"
"75"
"100" | - -示例 -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: prompt_tone - value: true -``` - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: fan_speed - value: Medium +# 家用空调 +## 特性 +- 支持运行模式 +- 支持风扇模式设定 +- 支持摆风 +- 支持湿度设定 + +## 生成实体 +### 默认生成实体 +| 实体ID | 类型 | 描述 | +|----------------------------------|------------|-------| +| humidifier.{DEVICEID}_humidifier | humidifier | 加湿器实体 | + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|---------------------------------------|---------------|---------------------|----------| +| sensor.{DEVICEID}_tank_full | binary_sensor | Tank Full | 水箱已达设置水位 | +| sensor.{DEVICEID}_tank | sensor | Tank | 水箱水位 | +| sensor.{DEVICEID}_current_humidity | sensor | Current Humidity | 当前湿度 | +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | +| lock.{DEVICEID}_child_lock | lock | Child Lock | 童锁 | +| switch.{DEVICEID}_anion | switch | Anion | 负离子开关 | +| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | 提示音 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_swing | switch | Swing | 摆风开关 | +| select.{DEVICEID}_fan_speed | select | Fan Speed | 风速设定 | +| select.{DEVICEID}_water_level_set | select | Water Level Setting | 水位设定 | + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|--------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "child_lock"
"anion"
"prompt_tone"
"power"
"swing" | +| value | true 或 false | + +| 名称 | 描述 | +|-----------|-----------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "fan_speed" | +| value | "Lowest"
"Low"
"Medium"
"High"
"Auto"
"Off" | + +| 名称 | 描述 | +|-----------|----------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "water_level_set" | +| value | "25"
"50"
"75"
"100" | + +示例 +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: prompt_tone + value: true +``` + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: fan_speed + value: Medium ``` \ No newline at end of file diff --git a/doc/AC.md b/doc/AC.md index 2d17a630..972231a4 100644 --- a/doc/AC.md +++ b/doc/AC.md @@ -1,112 +1,112 @@ -# Air Conditioner - -## Features - -- Supports target temperature -- Supports run mode -- Supports fan mode -- Supports swing mode -- Supports preset mode -- Supports build-in fresh air system - -### Supported Run-Modes - -- Comfort Mode -- ECO Mode -- Boost Mode - -## Customize - -- Set the temperature step of AC (0.5 by default). - -```json -{"temperature_step": 1} -``` - -- Power consumption analysis method (1 by default) - - There are 3 different methods to analyze the power consumption of an AC, but I don’t know which is right for your device. - If the power consumption data looks incorrect, try another method and see if they are correct. - The options include 1, 2, and 3. - -```json -{"power_analysis_method": 2} -``` - -## Entities - -### Default entity - -| EntityID | Class | Description | -|----------------------------|---------|----------------| -| climate.{DEVICEID}_climate | climate | Climate entity | - -### Extra entities - -| EntityID | Class | Description | -|----------------------------------------------|---------------|----------------------------| -| sensor.{DEVICEID}_full_dust | binary_sensor | Full of Dust | -| sensor.{DEVICEID}_indoor_humidity | sensor | Indoor humidity | -| sensor.{DEVICEID}_indoor_temperature | sensor | Indoor Temperature | -| sensor.{DEVICEID}_outdoor_temperature | sensor | Outdoor Temperature | -| sensor.{DEVICEID}_total_energy_consumption | sensor | Total Energy Consumption | -| sensor.{DEVICEID}_current_energy_consumption | sensor | Current Energy Consumption | -| sensor.{DEVICEID}_realtime_power | sensor | Realtime Power | -| fan.{DEVICEID}_fresh_air | fan | Fresh Air Fan | -| switch.{DEVICEID}_aux_heating | switch | Aux Heating | -| switch.{DEVICEID}_boost_mode | switch | Boost Mode | -| switch.{DEVICEID}_breezeless | switch | Breezeless | -| switch.{DEVICEID}_comfort_mode | switch | Comfort Mode | -| switch.{DEVICEID}_dry | switch | Dry | -| switch.{DEVICEID}_eco_mode | switch | ECO Mode | -| switch.{DEVICEID}_indirect_wind | switch | Indirect Wind | -| switch.{DEVICEID}_natural_wind | switch | Natural Wind | -| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_screen_display | switch | Screen Display | -| switch.{DEVICEID}_screen_display_alternate | switch | Screen Display Alternate | -| switch.{DEVICEID}_smart_eye | switch | Smart Eye | -| switch.{DEVICEID}_swing_horizontal | switch | Swing Horizontal | -| switch.{DEVICEID}_swing_vertical | switch | Swing Vertical | - -## Build-in fresh air system - -Some Midea appliance be named "Fresh Air Appliance", the protocol that actually uses the air conditioner. If your fresh air appliance is identified as an air conditioner, you should check the Fresh Air Fan entity in CONFIGURE, and use this fan entity to control your fresh air appliance.*** - -## Services - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "aux_heating"
"breezeless"
"comfort_mode"
"dry"
"eco_mode"
"indirect_wind"
"natural_wind"
"prompt_tone"
"power"
"screen_display"
"screen_display_2"
"smart_eye"
"swing_horizontal"
"swing_vertical"
"turbo_mode" | -| value | true or false | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | fan_speed | -| value | Range 1 to 100 or "auto" | - -Example - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: eco_mode - value: true -``` - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: fan_speed - value: auto -``` +# Air Conditioner + +## Features + +- Supports target temperature +- Supports run mode +- Supports fan mode +- Supports swing mode +- Supports preset mode +- Supports build-in fresh air system + +### Supported Run-Modes + +- Comfort Mode +- ECO Mode +- Boost Mode + +## Customize + +- Set the temperature step of AC (0.5 by default). + +```json +{"temperature_step": 1} +``` + +- Power consumption analysis method (1 by default) + + There are 3 different methods to analyze the power consumption of an AC, but I don’t know which is right for your device. + If the power consumption data looks incorrect, try another method and see if they are correct. + The options include 1, 2, and 3. + +```json +{"power_analysis_method": 2} +``` + +## Entities + +### Default entity + +| EntityID | Class | Description | +|----------------------------|---------|----------------| +| climate.{DEVICEID}_climate | climate | Climate entity | + +### Extra entities + +| EntityID | Class | Description | +|----------------------------------------------|---------------|----------------------------| +| sensor.{DEVICEID}_full_dust | binary_sensor | Full of Dust | +| sensor.{DEVICEID}_indoor_humidity | sensor | Indoor humidity | +| sensor.{DEVICEID}_indoor_temperature | sensor | Indoor Temperature | +| sensor.{DEVICEID}_outdoor_temperature | sensor | Outdoor Temperature | +| sensor.{DEVICEID}_total_energy_consumption | sensor | Total Energy Consumption | +| sensor.{DEVICEID}_current_energy_consumption | sensor | Current Energy Consumption | +| sensor.{DEVICEID}_realtime_power | sensor | Realtime Power | +| fan.{DEVICEID}_fresh_air | fan | Fresh Air Fan | +| switch.{DEVICEID}_aux_heating | switch | Aux Heating | +| switch.{DEVICEID}_boost_mode | switch | Boost Mode | +| switch.{DEVICEID}_breezeless | switch | Breezeless | +| switch.{DEVICEID}_comfort_mode | switch | Comfort Mode | +| switch.{DEVICEID}_dry | switch | Dry | +| switch.{DEVICEID}_eco_mode | switch | ECO Mode | +| switch.{DEVICEID}_indirect_wind | switch | Indirect Wind | +| switch.{DEVICEID}_natural_wind | switch | Natural Wind | +| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_screen_display | switch | Screen Display | +| switch.{DEVICEID}_screen_display_alternate | switch | Screen Display Alternate | +| switch.{DEVICEID}_smart_eye | switch | Smart Eye | +| switch.{DEVICEID}_swing_horizontal | switch | Swing Horizontal | +| switch.{DEVICEID}_swing_vertical | switch | Swing Vertical | + +## Build-in fresh air system + +Some Midea appliance be named "Fresh Air Appliance", the protocol that actually uses the air conditioner. If your fresh air appliance is identified as an air conditioner, you should check the Fresh Air Fan entity in CONFIGURE, and use this fan entity to control your fresh air appliance.*** + +## Services + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "aux_heating"
"breezeless"
"comfort_mode"
"dry"
"eco_mode"
"indirect_wind"
"natural_wind"
"prompt_tone"
"power"
"screen_display"
"screen_display_2"
"smart_eye"
"swing_horizontal"
"swing_vertical"
"turbo_mode" | +| value | true or false | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | fan_speed | +| value | Range 1 to 100 or "auto" | + +Example + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: eco_mode + value: true +``` + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: fan_speed + value: auto +``` diff --git a/doc/AC_hans.md b/doc/AC_hans.md index aed567d8..113b6c69 100644 --- a/doc/AC_hans.md +++ b/doc/AC_hans.md @@ -1,106 +1,106 @@ -# 家用空调 -## 特性 -- 支持目标温度设定 -- 支持运行模式设定 -- 支持风扇模式设定 -- 支持摆风模式设定 -- 支持预设模式设定 -- 支持内置新风系统 - -### 支持的模式 -- 舒适模式 -- 节能模式 -- 强力模式 - -## 自定义 - -- 设置温度调整步长(默认为0.5). - -```json -{"temperature_step": 1} -``` - -- 空调功耗分析方法(默认为1) - - 我找到了三种不同的办法去分析空调功耗数据, 但我无从得知哪种适合你的空调设备 - 如果空调的功耗数据看起来不正确, 换用其它方法试试看 - 可选值有(1/2/3) - -```json -{"power_analysis_method": 2} -``` - -## 生成实体 -### 默认生成实体 -| 实体ID | 类型 | 描述 | -|----------------------------|---------|-------| -| climate.{DEVICEID}_climate | climate | 恒温器实体 | - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|----------------------------------------------|---------------|----------------------------|----------| -| sensor.{DEVICEID}_full_dust | binary_sensor | Full of Dust | 尘满 | -| sensor.{DEVICEID}_indoor_humidity | sensor | Indoor humidity | 湿度 | -| sensor.{DEVICEID}_indoor_temperature | sensor | Indoor Temperature | 室内温度 | -| sensor.{DEVICEID}_outdoor_temperature | sensor | Outdoor Temperature | 室外机温度 | -| sensor.{DEVICEID}_total_energy_consumption | sensor | Total Energy Consumption | 总能耗 | -| sensor.{DEVICEID}_current_energy_consumption | sensor | Current Energy Consumption | 当前能耗 | -| sensor.{DEVICEID}_realtime_power | sensor | Realtime Power | 实时功率 | -| fan.{DEVICEID}_fresh_air | fan | Fresh Air | 新风 | -| switch.{DEVICEID}_aux_heating | switch | Aux Heating | 电辅热 | -| switch.{DEVICEID}_boost_mode | switch | Boost Mode | 强劲模式 | -| switch.{DEVICEID}_breezeless | switch | Breezeless | 无风感 | -| switch.{DEVICEID}_comfort_mode | switch | Comfort Mode | 舒省模式 | -| switch.{DEVICEID}_dry | switch | Dry | 干燥 | -| switch.{DEVICEID}_eco_mode | switch | ECO Mode | ECO模式 | -| switch.{DEVICEID}_indirect_wind | switch | Indirect Wind | 防直吹 | -| switch.{DEVICEID}_natural_wind | switch | Natural Wind | 自然风 | -| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | 提示音 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_screen_display | switch | Screen Display | 屏幕显示 | -| switch.{DEVICEID}_screen_display_alternate | switch | Screen Display Alternate | 屏幕显示备用开关 | -| switch.{DEVICEID}_smart_eye | switch | Smart Eye | 智慧眼 | -| switch.{DEVICEID}_swing_horizontal | switch | Swing Horizontal | 水平摆风 | -| switch.{DEVICEID}_swing_vertical | switch | Swing Vertical | 垂直摆风 | - -## 内置新风系统 - -部分美的的"中央新风机"产品,其实使用了空调的协议。如果你的新风机被识别为空调,则只用在选项中勾选"Fresh Air"的fan实体,然后使用该fan实体控制新风机即可。 - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "aux_heating"
"breezeless"
"comfort_mode"
"dry"
"eco_mode"
"indirect_wind"
"natural_wind"
"prompt_tone"
"power"
"screen_display"
"screen_display_2"
"smart_eye"
"swing_horizontal"
"swing_vertical"
"turbo_mode" | -| value | true 或 false | - -| 名称 | 描述 | -|-----------|------------------| -| device_id | 设备的编号(Device ID) | -| attribute | fan_speed | -| value | 范围为1-100, 或者auto | - -示例 - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: eco_mode - value: true -``` -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: fan_speed - value: 65 +# 家用空调 +## 特性 +- 支持目标温度设定 +- 支持运行模式设定 +- 支持风扇模式设定 +- 支持摆风模式设定 +- 支持预设模式设定 +- 支持内置新风系统 + +### 支持的模式 +- 舒适模式 +- 节能模式 +- 强力模式 + +## 自定义 + +- 设置温度调整步长(默认为0.5). + +```json +{"temperature_step": 1} +``` + +- 空调功耗分析方法(默认为1) + + 我找到了三种不同的办法去分析空调功耗数据, 但我无从得知哪种适合你的空调设备 + 如果空调的功耗数据看起来不正确, 换用其它方法试试看 + 可选值有(1/2/3) + +```json +{"power_analysis_method": 2} +``` + +## 生成实体 +### 默认生成实体 +| 实体ID | 类型 | 描述 | +|----------------------------|---------|-------| +| climate.{DEVICEID}_climate | climate | 恒温器实体 | + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|----------------------------------------------|---------------|----------------------------|----------| +| sensor.{DEVICEID}_full_dust | binary_sensor | Full of Dust | 尘满 | +| sensor.{DEVICEID}_indoor_humidity | sensor | Indoor humidity | 湿度 | +| sensor.{DEVICEID}_indoor_temperature | sensor | Indoor Temperature | 室内温度 | +| sensor.{DEVICEID}_outdoor_temperature | sensor | Outdoor Temperature | 室外机温度 | +| sensor.{DEVICEID}_total_energy_consumption | sensor | Total Energy Consumption | 总能耗 | +| sensor.{DEVICEID}_current_energy_consumption | sensor | Current Energy Consumption | 当前能耗 | +| sensor.{DEVICEID}_realtime_power | sensor | Realtime Power | 实时功率 | +| fan.{DEVICEID}_fresh_air | fan | Fresh Air | 新风 | +| switch.{DEVICEID}_aux_heating | switch | Aux Heating | 电辅热 | +| switch.{DEVICEID}_boost_mode | switch | Boost Mode | 强劲模式 | +| switch.{DEVICEID}_breezeless | switch | Breezeless | 无风感 | +| switch.{DEVICEID}_comfort_mode | switch | Comfort Mode | 舒省模式 | +| switch.{DEVICEID}_dry | switch | Dry | 干燥 | +| switch.{DEVICEID}_eco_mode | switch | ECO Mode | ECO模式 | +| switch.{DEVICEID}_indirect_wind | switch | Indirect Wind | 防直吹 | +| switch.{DEVICEID}_natural_wind | switch | Natural Wind | 自然风 | +| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | 提示音 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_screen_display | switch | Screen Display | 屏幕显示 | +| switch.{DEVICEID}_screen_display_alternate | switch | Screen Display Alternate | 屏幕显示备用开关 | +| switch.{DEVICEID}_smart_eye | switch | Smart Eye | 智慧眼 | +| switch.{DEVICEID}_swing_horizontal | switch | Swing Horizontal | 水平摆风 | +| switch.{DEVICEID}_swing_vertical | switch | Swing Vertical | 垂直摆风 | + +## 内置新风系统 + +部分美的的"中央新风机"产品,其实使用了空调的协议。如果你的新风机被识别为空调,则只用在选项中勾选"Fresh Air"的fan实体,然后使用该fan实体控制新风机即可。 + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "aux_heating"
"breezeless"
"comfort_mode"
"dry"
"eco_mode"
"indirect_wind"
"natural_wind"
"prompt_tone"
"power"
"screen_display"
"screen_display_2"
"smart_eye"
"swing_horizontal"
"swing_vertical"
"turbo_mode" | +| value | true 或 false | + +| 名称 | 描述 | +|-----------|------------------| +| device_id | 设备的编号(Device ID) | +| attribute | fan_speed | +| value | 范围为1-100, 或者auto | + +示例 + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: eco_mode + value: true +``` +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: fan_speed + value: 65 ``` \ No newline at end of file diff --git a/doc/B6.md b/doc/B6.md index 42422f6b..11328466 100644 --- a/doc/B6.md +++ b/doc/B6.md @@ -1,34 +1,34 @@ -# Range Hood -## Features -- Supports fan speed -- Supports preset mode - -## Customize - -- Set the fan levels of the range hood device. -- Set the power-on fan level. - -```json -{"speeds": {"0": "Off","1": "Level 1","2": "Level 2","3": "Level 3","22": "Variable"},"default_speed": 2} -``` - -***Note: For different models of range hoods, maybe has different fan level settings. 4 means Boost in some devices but Variable in others, 20 means Variable in some devices but 22 means Variable in others. So you can try the different settings in your own device.*** - -## Entities -### Default entity -| EntityID | Class | Description | -|--------------------|-------|-------------| -| fan.{DEVICEID}_fan | fan | Fan entity | - -### Extra entities - -| EntityID | Class | Description | -|--------------------------------------------|--------|-------------------| -| binary_sensor.{DEVICEID}_cleaning_reminder | lock | Cleaning Reminder | -| binary_sensor.{DEVICEID}_oilcup_full | select | Oil-cup Full | -| sensor.{DEVICEID}_fan_level | sensor | Current Fan Level | -| switch.{DEVICEID}_light | switch | Light | -| switch.{DEVICEID}_power | switch | Power | - -## Service +# Range Hood +## Features +- Supports fan speed +- Supports preset mode + +## Customize + +- Set the fan levels of the range hood device. +- Set the power-on fan level. + +```json +{"speeds": {"0": "Off","1": "Level 1","2": "Level 2","3": "Level 3","22": "Variable"},"default_speed": 2} +``` + +***Note: For different models of range hoods, maybe has different fan level settings. 4 means Boost in some devices but Variable in others, 20 means Variable in some devices but 22 means Variable in others. So you can try the different settings in your own device.*** + +## Entities +### Default entity +| EntityID | Class | Description | +|--------------------|-------|-------------| +| fan.{DEVICEID}_fan | fan | Fan entity | + +### Extra entities + +| EntityID | Class | Description | +|--------------------------------------------|--------|-------------------| +| binary_sensor.{DEVICEID}_cleaning_reminder | lock | Cleaning Reminder | +| binary_sensor.{DEVICEID}_oilcup_full | select | Oil-cup Full | +| sensor.{DEVICEID}_fan_level | sensor | Current Fan Level | +| switch.{DEVICEID}_light | switch | Light | +| switch.{DEVICEID}_power | switch | Power | + +## Service No service \ No newline at end of file diff --git a/doc/B6_hans.md b/doc/B6_hans.md index 0019766c..55d90fc1 100644 --- a/doc/B6_hans.md +++ b/doc/B6_hans.md @@ -1,34 +1,34 @@ -# 油烟机 -## 特性 -- 支持风速调节 -- 支持预设模式 - -## 自定义 - -- 自定义油烟机档位 -- 设置开机默认档位 - -```json -{"speeds": {"0": "关闭","1": "档位1","2": "档位2","3": "档位3","22": "变频巡航"},"default_speed": 2} -``` - -***注意: 对于不同型号的油烟机,档位设置可能各不相同。有的4是爆炒档,有的4是巡航档,有的20是变频档,有的22是变频档,可以自己尝试不同设置适配你的油烟机。*** - -## 生成实体 -### 默认生成实体 -| 实体ID | 类型 | 描述 | -|--------------------|-----|------| -| fan.{DEVICEID}_fan | fan | 风扇实体 | - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|--------------------------------------------|--------|-------------------|--------| -| binary_sensor.{DEVICEID}_cleaning_reminder | lock | Cleaning Reminder | 清洁提示 | -| binary_sensor.{DEVICEID}_oilcup_full | select | Oil-cup Full | 油杯满提示 | -| sensor.{DEVICEID}_fan_level | sensor | Current Fan Level | 当前风扇档位 | -| switch.{DEVICEID}_light | switch | Light | 灯开关 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | - -## 服务 +# 油烟机 +## 特性 +- 支持风速调节 +- 支持预设模式 + +## 自定义 + +- 自定义油烟机档位 +- 设置开机默认档位 + +```json +{"speeds": {"0": "关闭","1": "档位1","2": "档位2","3": "档位3","22": "变频巡航"},"default_speed": 2} +``` + +***注意: 对于不同型号的油烟机,档位设置可能各不相同。有的4是爆炒档,有的4是巡航档,有的20是变频档,有的22是变频档,可以自己尝试不同设置适配你的油烟机。*** + +## 生成实体 +### 默认生成实体 +| 实体ID | 类型 | 描述 | +|--------------------|-----|------| +| fan.{DEVICEID}_fan | fan | 风扇实体 | + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|--------------------------------------------|--------|-------------------|--------| +| binary_sensor.{DEVICEID}_cleaning_reminder | lock | Cleaning Reminder | 清洁提示 | +| binary_sensor.{DEVICEID}_oilcup_full | select | Oil-cup Full | 油杯满提示 | +| sensor.{DEVICEID}_fan_level | sensor | Current Fan Level | 当前风扇档位 | +| switch.{DEVICEID}_light | switch | Light | 灯开关 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | + +## 服务 无服务 \ No newline at end of file diff --git a/doc/C3.md b/doc/C3.md index 9934854b..f7438269 100644 --- a/doc/C3.md +++ b/doc/C3.md @@ -1,68 +1,68 @@ -# Heat Pump Wi-Fi Controller - -## Features - -- Supports target temperature -- Supports run mode - -## Entities - -### Default entity - -| EntityID | Class | Description | -|--------------------------------------|--------------|----------------------| -| climate.{DEVICEID}_climate_zone1 | climate | Zone1 climate entity | -| climate.{DEVICEID}_climate_zone2 | climate | Zone2 climate entity | -| water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | - -### Extra entities - -| EntityID | Class | Description | -|------------------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------| -| binary_sensor.{DEVICEID}_zone1_water_temp_mode | binary_sensor | Zone1 Water Temperature Mode | -| binary_sensor.{DEVICEID}_zone2_water_temp_mode | binary_sensor | Zone2 Water Temperature Mode | -| binary_sensor.{DEVICEID}_zone1_room_temp_mode | binary_sensor | Zone1 Room Temperature Mode | -| binary_sensor.{DEVICEID}_zone2_room_temp_mode | binary_sensor | Zone2 Room Temperature Mode | -| binary_sensor.{DEVICEID}_status_dhw | binary_sensor | DHW Status | -| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status | -| binary_sensor.{DEVICEID}_status_ibh | binary_sensor | IBH Status | -| binary_sensor.{DEVICEID}_status_heating | binary_sensor | Heating Status | -| sensor.{DEVICEID}_error_code | sensor | Error Code | -| sensor.{DEVICEID}_tank_actual_temperature | sensor | Tank Actual Temperature | -| sensor.{DEVICEID}_total_energy_consumption | sensor | Total Energy Consumption.
The first value may be delayed because updates are only sent when the device is active | -| sensor.{DEVICEID}_total_produced_energy | sensor | Total Produced Energy | -| sensor.{DEVICEID}_outdoor_temperature | sensor | Outdoor Temperature | -| switch.{DEVICEID}_disinfect | switch | Disinfect | -| switch.{DEVICEID}_dhw_power | switch | DHW Power | -| switch.{DEVICEID}_eco_mode | switch | ECO Mode | -| switch.{DEVICEID}_fast_dhw | switch | Fast DHW | -| switch.{DEVICEID}_silent_mode | switch | Silent Mode | -| switch.{DEVICEID}_tbh | switch | TBH | -| switch.{DEVICEID}_zone1_curve | switch | Zone1 Curve | -| switch.{DEVICEID}_zone2_curve | switch | Zone2 Curve | -| switch.{DEVICEID}_zone1_power | switch | Zone1 Power | -| switch.{DEVICEID}_zone2_power | switch | Zone2 Power | - -## Service - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|--------------------------------------------------------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "disinfect"
"dhw_power"
"fast_dhw"
"zone1_curve"
"zone2_curve"
"zone1_power"
"zone2_power" | -| value | true or false | - -Example - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: zone1_curve - value: true -``` +# Heat Pump Wi-Fi Controller + +## Features + +- Supports target temperature +- Supports run mode + +## Entities + +### Default entity + +| EntityID | Class | Description | +|--------------------------------------|--------------|----------------------| +| climate.{DEVICEID}_climate_zone1 | climate | Zone1 climate entity | +| climate.{DEVICEID}_climate_zone2 | climate | Zone2 climate entity | +| water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | + +### Extra entities + +| EntityID | Class | Description | +|------------------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------| +| binary_sensor.{DEVICEID}_zone1_water_temp_mode | binary_sensor | Zone1 Water Temperature Mode | +| binary_sensor.{DEVICEID}_zone2_water_temp_mode | binary_sensor | Zone2 Water Temperature Mode | +| binary_sensor.{DEVICEID}_zone1_room_temp_mode | binary_sensor | Zone1 Room Temperature Mode | +| binary_sensor.{DEVICEID}_zone2_room_temp_mode | binary_sensor | Zone2 Room Temperature Mode | +| binary_sensor.{DEVICEID}_status_dhw | binary_sensor | DHW Status | +| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status | +| binary_sensor.{DEVICEID}_status_ibh | binary_sensor | IBH Status | +| binary_sensor.{DEVICEID}_status_heating | binary_sensor | Heating Status | +| sensor.{DEVICEID}_error_code | sensor | Error Code | +| sensor.{DEVICEID}_tank_actual_temperature | sensor | Tank Actual Temperature | +| sensor.{DEVICEID}_total_energy_consumption | sensor | Total Energy Consumption.
The first value may be delayed because updates are only sent when the device is active | +| sensor.{DEVICEID}_total_produced_energy | sensor | Total Produced Energy | +| sensor.{DEVICEID}_outdoor_temperature | sensor | Outdoor Temperature | +| switch.{DEVICEID}_disinfect | switch | Disinfect | +| switch.{DEVICEID}_dhw_power | switch | DHW Power | +| switch.{DEVICEID}_eco_mode | switch | ECO Mode | +| switch.{DEVICEID}_fast_dhw | switch | Fast DHW | +| switch.{DEVICEID}_silent_mode | switch | Silent Mode | +| switch.{DEVICEID}_tbh | switch | TBH | +| switch.{DEVICEID}_zone1_curve | switch | Zone1 Curve | +| switch.{DEVICEID}_zone2_curve | switch | Zone2 Curve | +| switch.{DEVICEID}_zone1_power | switch | Zone1 Power | +| switch.{DEVICEID}_zone2_power | switch | Zone2 Power | + +## Service + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|--------------------------------------------------------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "disinfect"
"dhw_power"
"fast_dhw"
"zone1_curve"
"zone2_curve"
"zone1_power"
"zone2_power" | +| value | true or false | + +Example + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: zone1_curve + value: true +``` diff --git a/doc/C3_hans.md b/doc/C3_hans.md index 53503c28..2feed9d2 100644 --- a/doc/C3_hans.md +++ b/doc/C3_hans.md @@ -1,68 +1,68 @@ -# 热泵空调Wi-Fi线控器 - -## 特性 - -- 支持目标温度设定 -- 支持运行模式设定 - -## 生成实体 - -### 默认生成实体 - -| 实体ID | 类型 | 描述 | -|--------------------------------------|--------------|----------| -| climate.{DEVICEID}_climate_zone1 | climate | 区域1恒温器实体 | -| climate.{DEVICEID}_climate_zone2 | climate | 区域2恒温器实体 | -| water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|------------------------------------------------|---------------|------------------------------|--------------------------------------| -| binary_sensor.{DEVICEID}_zone1_water_temp_mode | binary_sensor | Zone1 Water Temperature Mode | 区域1水温模式 | -| binary_sensor.{DEVICEID}_zone2_water_temp_mode | binary_sensor | Zone2 Water Temperature Mode | 区域2水温模式 | -| binary_sensor.{DEVICEID}_zone1_room_temp_mode | binary_sensor | Zone1 Room Temperature Mode | 区域1室温模式 | -| binary_sensor.{DEVICEID}_zone2_room_temp_mode | binary_sensor | Zone2 Room Temperature Mode | 区域2室温模式 | -| binary_sensor.{DEVICEID}_status_dhw | binary_sensor | DHW Status | DHW状态 | -| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status | TBH状态 | -| binary_sensor.{DEVICEID}_status_ibh | binary_sensor | IBH Status | IBH状态 | -| binary_sensor.{DEVICEID}_status_heating | binary_sensor | Heating Status | 加热状态 | -| sensor.{DEVICEID}_error_code | sensor | Error Code | 错误码 | -| sensor.{DEVICEID}_tank_actual_temperature | sensor | Tank Actual Temperature | 水箱实际温度 | -| sensor.{DEVICEID}_total_energy_consumption | sensor | Total Energy Consumption | 总能耗。
第一个值可能会延迟,因为更新仅在设备处于活动状态时发送 | -| sensor.{DEVICEID}_total_produced_energy | sensor | Total Produced Energy | 总计产生能量 | -| sensor.{DEVICEID}_outdoor_temperature | sensor | Outdoor Temperature | 室外温度 | -| switch.{DEVICEID}_disinfect | switch | Disinfect | 消毒 | -| switch.{DEVICEID}_dhw_power | switch | DHW Power | 生活热水电源开关 | -| switch.{DEVICEID}_eco_mode | switch | ECO Mode | ECO模式 | -| switch.{DEVICEID}_fast_dhw | switch | Fast DHW | 快速生活热水 | -| switch.{DEVICEID}_silent_mode | switch | Silent Mode | 静音模式 | -| switch.{DEVICEID}_tbh | switch | TBH | TBH | -| switch.{DEVICEID}_zone1_curve | switch | Zone1 Curve | 区域1曲线 | -| switch.{DEVICEID}_zone2_curve | switch | Zone2 Curve | 区域2曲线 | -| switch.{DEVICEID}_zone1_power | switch | Zone1 Power | 区域1恒温器开关 | -| switch.{DEVICEID}_zone2_power | switch | Zone2 Power | 区域2恒温器开关 | - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|--------------------------------------------------------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "disinfect"
"dhw_power"
"fast_dhw"
"zone1_curve"
"zone2_curve"
"zone1_power"
"zone2_power" | -| value | true 或 false | - -示例 - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: zone1_curve - value: true -``` +# 热泵空调Wi-Fi线控器 + +## 特性 + +- 支持目标温度设定 +- 支持运行模式设定 + +## 生成实体 + +### 默认生成实体 + +| 实体ID | 类型 | 描述 | +|--------------------------------------|--------------|----------| +| climate.{DEVICEID}_climate_zone1 | climate | 区域1恒温器实体 | +| climate.{DEVICEID}_climate_zone2 | climate | 区域2恒温器实体 | +| water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|------------------------------------------------|---------------|------------------------------|--------------------------------------| +| binary_sensor.{DEVICEID}_zone1_water_temp_mode | binary_sensor | Zone1 Water Temperature Mode | 区域1水温模式 | +| binary_sensor.{DEVICEID}_zone2_water_temp_mode | binary_sensor | Zone2 Water Temperature Mode | 区域2水温模式 | +| binary_sensor.{DEVICEID}_zone1_room_temp_mode | binary_sensor | Zone1 Room Temperature Mode | 区域1室温模式 | +| binary_sensor.{DEVICEID}_zone2_room_temp_mode | binary_sensor | Zone2 Room Temperature Mode | 区域2室温模式 | +| binary_sensor.{DEVICEID}_status_dhw | binary_sensor | DHW Status | DHW状态 | +| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status | TBH状态 | +| binary_sensor.{DEVICEID}_status_ibh | binary_sensor | IBH Status | IBH状态 | +| binary_sensor.{DEVICEID}_status_heating | binary_sensor | Heating Status | 加热状态 | +| sensor.{DEVICEID}_error_code | sensor | Error Code | 错误码 | +| sensor.{DEVICEID}_tank_actual_temperature | sensor | Tank Actual Temperature | 水箱实际温度 | +| sensor.{DEVICEID}_total_energy_consumption | sensor | Total Energy Consumption | 总能耗。
第一个值可能会延迟,因为更新仅在设备处于活动状态时发送 | +| sensor.{DEVICEID}_total_produced_energy | sensor | Total Produced Energy | 总计产生能量 | +| sensor.{DEVICEID}_outdoor_temperature | sensor | Outdoor Temperature | 室外温度 | +| switch.{DEVICEID}_disinfect | switch | Disinfect | 消毒 | +| switch.{DEVICEID}_dhw_power | switch | DHW Power | 生活热水电源开关 | +| switch.{DEVICEID}_eco_mode | switch | ECO Mode | ECO模式 | +| switch.{DEVICEID}_fast_dhw | switch | Fast DHW | 快速生活热水 | +| switch.{DEVICEID}_silent_mode | switch | Silent Mode | 静音模式 | +| switch.{DEVICEID}_tbh | switch | TBH | TBH | +| switch.{DEVICEID}_zone1_curve | switch | Zone1 Curve | 区域1曲线 | +| switch.{DEVICEID}_zone2_curve | switch | Zone2 Curve | 区域2曲线 | +| switch.{DEVICEID}_zone1_power | switch | Zone1 Power | 区域1恒温器开关 | +| switch.{DEVICEID}_zone2_power | switch | Zone2 Power | 区域2恒温器开关 | + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|--------------------------------------------------------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "disinfect"
"dhw_power"
"fast_dhw"
"zone1_curve"
"zone2_curve"
"zone1_power"
"zone2_power" | +| value | true 或 false | + +示例 + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: zone1_curve + value: true +``` diff --git a/doc/CA.md b/doc/CA.md index 4e01c208..049243ae 100644 --- a/doc/CA.md +++ b/doc/CA.md @@ -1,30 +1,30 @@ -# Refrigerator - -## Entities -### Default entity -No default entity. - -### Extra entities - -| EntityID | Class | Description | -|-----------------------------------------------------|---------------|-------------------------------------| -| binary_sensor.{DEVICEID}_bar_door | binary_sensor | Bar Door | -| binary_sensor.{DEVICEID}_bar_door_overtime | binary_sensor | Bar Door Overtime | -| binary_sensor.{DEVICEID}_flex_zone_door | binary_sensor | Flex-zone Door | -| binary_sensor.{DEVICEID}_flex_zone_door_overtime | binary_sensor | Flex-zone Door Overtime | -| binary_sensor.{DEVICEID}_freezer_door | binary_sensor | Freezer Door | -| binary_sensor.{DEVICEID}_freezer_door_overtime | binary_sensor | Freezer Door Overtime | -| binary_sensor.{DEVICEID}_refrigerator_door | binary_sensor | Refrigerator Door | -| binary_sensor.{DEVICEID}_refrigerator_door_overtime | binary_sensor | Refrigerator Door Overtime | -| sensor.{DEVICEID}_flex_zone_actual_temp | sensor | Flex-zone Actual Temperature | -| sensor.{DEVICEID}_flex_zone_setting_temp | sensor | Flex-zone Setting Temperature | -| sensor.{DEVICEID}_freezer_actual_temp | sensor | Freezer Actual Temperature | -| sensor.{DEVICEID}_freezer_setting_temp | sensor | Freezer Setting Temperature | -| sensor.{DEVICEID}_energy_consumption | sensor | Energy Consumptio | -| sensor.{DEVICEID}_refrigerator_actual_temp | sensor | Refrigerator Actual Temperature | -| sensor.{DEVICEID}_refrigerator_setting_temp | sensor | Refrigerator setting Temperature | -| sensor.{DEVICEID}_right_flex_zone_actual_temp | sensor | Right Flex-zone Actual Temperature | -| sensor.{DEVICEID}_right_flex_zone_setting_temp | sensor | Right Flex-zone Setting Temperature | - -## Service +# Refrigerator + +## Entities +### Default entity +No default entity. + +### Extra entities + +| EntityID | Class | Description | +|-----------------------------------------------------|---------------|-------------------------------------| +| binary_sensor.{DEVICEID}_bar_door | binary_sensor | Bar Door | +| binary_sensor.{DEVICEID}_bar_door_overtime | binary_sensor | Bar Door Overtime | +| binary_sensor.{DEVICEID}_flex_zone_door | binary_sensor | Flex-zone Door | +| binary_sensor.{DEVICEID}_flex_zone_door_overtime | binary_sensor | Flex-zone Door Overtime | +| binary_sensor.{DEVICEID}_freezer_door | binary_sensor | Freezer Door | +| binary_sensor.{DEVICEID}_freezer_door_overtime | binary_sensor | Freezer Door Overtime | +| binary_sensor.{DEVICEID}_refrigerator_door | binary_sensor | Refrigerator Door | +| binary_sensor.{DEVICEID}_refrigerator_door_overtime | binary_sensor | Refrigerator Door Overtime | +| sensor.{DEVICEID}_flex_zone_actual_temp | sensor | Flex-zone Actual Temperature | +| sensor.{DEVICEID}_flex_zone_setting_temp | sensor | Flex-zone Setting Temperature | +| sensor.{DEVICEID}_freezer_actual_temp | sensor | Freezer Actual Temperature | +| sensor.{DEVICEID}_freezer_setting_temp | sensor | Freezer Setting Temperature | +| sensor.{DEVICEID}_energy_consumption | sensor | Energy Consumptio | +| sensor.{DEVICEID}_refrigerator_actual_temp | sensor | Refrigerator Actual Temperature | +| sensor.{DEVICEID}_refrigerator_setting_temp | sensor | Refrigerator setting Temperature | +| sensor.{DEVICEID}_right_flex_zone_actual_temp | sensor | Right Flex-zone Actual Temperature | +| sensor.{DEVICEID}_right_flex_zone_setting_temp | sensor | Right Flex-zone Setting Temperature | + +## Service No services. \ No newline at end of file diff --git a/doc/CA_hans.md b/doc/CA_hans.md index a42e045c..37a46265 100644 --- a/doc/CA_hans.md +++ b/doc/CA_hans.md @@ -1,30 +1,30 @@ -# 冰箱 - -## 生成实体 -### 默认实体 -无默认实体 - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|-----------------------------------------------------|---------------|-------------------------------------|----------| -| binary_sensor.{DEVICEID}_bar_door | binary_sensor | Bar Door | 吧台门状态 | -| binary_sensor.{DEVICEID}_bar_door_overtime | binary_sensor | Bar Door Overtime | 吧台门超时 | -| binary_sensor.{DEVICEID}_flex_zone_door | binary_sensor | Flex-zone Door | 变温区门状态 | -| binary_sensor.{DEVICEID}_flex_zone_door_overtime | binary_sensor | Flex-zone Door Overtime | 变温区门超时 | -| binary_sensor.{DEVICEID}_freezer_door | binary_sensor | Freezer Door | 冷冻室门状态 | -| binary_sensor.{DEVICEID}_freezer_door_overtime | binary_sensor | Freezer Door Overtime | 冷冻室门超时 | -| binary_sensor.{DEVICEID}_refrigerator_door | binary_sensor | Refrigerator Door | 冷藏室门状态 | -| binary_sensor.{DEVICEID}_refrigerator_door_overtime | binary_sensor | Refrigerator Door Overtime | 冷藏室门超时 | -| sensor.{DEVICEID}_flex_zone_actual_temp | sensor | Flex-zone Actual Temperature | 变温区实际温度 | -| sensor.{DEVICEID}_flex_zone_setting_temp | sensor | Flex-zone Setting Temperature | 变温区设置温度 | -| sensor.{DEVICEID}_freezer_actual_temp | sensor | Freezer Actual Temperature | 冷冻室实际温度 | -| sensor.{DEVICEID}_freezer_setting_temp | sensor | Freezer Setting Temperature | 冷冻室设置温度 | -| sensor.{DEVICEID}_energy_consumption | sensor | Energy Consumption | 能耗 | -| sensor.{DEVICEID}_refrigerator_actual_temp | sensor | Refrigerator Actual Temperature | 冷藏室实际温度 | -| sensor.{DEVICEID}_refrigerator_setting_temp | sensor | Refrigerator setting Temperature | 冷藏室设置温度 | -| sensor.{DEVICEID}_right_flex_zone_actual_temp | sensor | Right Flex-zone Actual Temperature | 右变温区实际温度 | -| sensor.{DEVICEID}_right_flex_zone_setting_temp | sensor | Right Flex-zone Setting Temperature | 右变温区设置温度 | - -## 服务 +# 冰箱 + +## 生成实体 +### 默认实体 +无默认实体 + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|-----------------------------------------------------|---------------|-------------------------------------|----------| +| binary_sensor.{DEVICEID}_bar_door | binary_sensor | Bar Door | 吧台门状态 | +| binary_sensor.{DEVICEID}_bar_door_overtime | binary_sensor | Bar Door Overtime | 吧台门超时 | +| binary_sensor.{DEVICEID}_flex_zone_door | binary_sensor | Flex-zone Door | 变温区门状态 | +| binary_sensor.{DEVICEID}_flex_zone_door_overtime | binary_sensor | Flex-zone Door Overtime | 变温区门超时 | +| binary_sensor.{DEVICEID}_freezer_door | binary_sensor | Freezer Door | 冷冻室门状态 | +| binary_sensor.{DEVICEID}_freezer_door_overtime | binary_sensor | Freezer Door Overtime | 冷冻室门超时 | +| binary_sensor.{DEVICEID}_refrigerator_door | binary_sensor | Refrigerator Door | 冷藏室门状态 | +| binary_sensor.{DEVICEID}_refrigerator_door_overtime | binary_sensor | Refrigerator Door Overtime | 冷藏室门超时 | +| sensor.{DEVICEID}_flex_zone_actual_temp | sensor | Flex-zone Actual Temperature | 变温区实际温度 | +| sensor.{DEVICEID}_flex_zone_setting_temp | sensor | Flex-zone Setting Temperature | 变温区设置温度 | +| sensor.{DEVICEID}_freezer_actual_temp | sensor | Freezer Actual Temperature | 冷冻室实际温度 | +| sensor.{DEVICEID}_freezer_setting_temp | sensor | Freezer Setting Temperature | 冷冻室设置温度 | +| sensor.{DEVICEID}_energy_consumption | sensor | Energy Consumption | 能耗 | +| sensor.{DEVICEID}_refrigerator_actual_temp | sensor | Refrigerator Actual Temperature | 冷藏室实际温度 | +| sensor.{DEVICEID}_refrigerator_setting_temp | sensor | Refrigerator setting Temperature | 冷藏室设置温度 | +| sensor.{DEVICEID}_right_flex_zone_actual_temp | sensor | Right Flex-zone Actual Temperature | 右变温区实际温度 | +| sensor.{DEVICEID}_right_flex_zone_setting_temp | sensor | Right Flex-zone Setting Temperature | 右变温区设置温度 | + +## 服务 无服务 \ No newline at end of file diff --git a/doc/CC.md b/doc/CC.md index ce981f6a..48b8df52 100644 --- a/doc/CC.md +++ b/doc/CC.md @@ -1,52 +1,52 @@ -# MDV Wi-Fi Controller -## Features -- Supports target temperature -- Supports run mode -- Supports fan mode -- Supports swing mode -- Supports auxiliary heating - -### Supported Run-Modes -- Sleep Mode -- ECO Mode - -## Entities -### Default entity -| EntityID | Class | Description | -|----------------------------|---------|----------------| -| climate.{DEVICEID}_climate | climate | Climate entity | - -### Extra entities - -| EntityID | Class | Description | -|--------------------------------------|--------|--------------------| -| sensor.{DEVICEID}_indoor_temperature | sensor | Indoor Temperature | -| switch.{DEVICEID}_aux_heating | switch | Aux Heating | -| switch.{DEVICEID}_eco_mode | switch | ECO Mode | -| switch.{DEVICEID}_night_light | switch | Night Light | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_sleep_mode | switch | Sleep Mode | -| switch.{DEVICEID}_swing | switch | Swing | - -## Service - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|------------------------------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "aux_heating"
"eco_mode"
"night_light"
"power"
"sleep_mode"
"swing" | -| value | true or false | - -Example -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: eco_mode - value: true +# MDV Wi-Fi Controller +## Features +- Supports target temperature +- Supports run mode +- Supports fan mode +- Supports swing mode +- Supports auxiliary heating + +### Supported Run-Modes +- Sleep Mode +- ECO Mode + +## Entities +### Default entity +| EntityID | Class | Description | +|----------------------------|---------|----------------| +| climate.{DEVICEID}_climate | climate | Climate entity | + +### Extra entities + +| EntityID | Class | Description | +|--------------------------------------|--------|--------------------| +| sensor.{DEVICEID}_indoor_temperature | sensor | Indoor Temperature | +| switch.{DEVICEID}_aux_heating | switch | Aux Heating | +| switch.{DEVICEID}_eco_mode | switch | ECO Mode | +| switch.{DEVICEID}_night_light | switch | Night Light | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_sleep_mode | switch | Sleep Mode | +| switch.{DEVICEID}_swing | switch | Swing | + +## Service + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|------------------------------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "aux_heating"
"eco_mode"
"night_light"
"power"
"sleep_mode"
"swing" | +| value | true or false | + +Example +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: eco_mode + value: true ``` \ No newline at end of file diff --git a/doc/CC_hans.md b/doc/CC_hans.md index e95fc294..a3ed26b8 100644 --- a/doc/CC_hans.md +++ b/doc/CC_hans.md @@ -1,53 +1,53 @@ -# 中央空调Wi-Fi线控器 -## 特性 -- 支持目标温度设定 -- 支持运行模式设定 -- 支持风扇模式设定 -- 支持摆风模式设定 -- 支持电辅热 - -### 支持的模式 -- 睡眠模式 -- 节能模式 - -## 生成实体 -### 默认生成实体 -| 实体ID | 类型 | 描述 | -|----------------------------|---------|-------| -| climate.{DEVICEID}_climate | climate | 恒温器实体 | - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|--------------------------------------|--------|--------------------|-------| -| sensor.{DEVICEID}_indoor_temperature | sensor | Indoor Temperature | 室内温度 | -| switch.{DEVICEID}_aux_heating | switch | Aux Heating | 电辅热 | -| switch.{DEVICEID}_eco_mode | switch | ECO Mode | ECO模式 | -| switch.{DEVICEID}_night_light | switch | Night Light | 夜灯 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_sleep_mode | switch | Sleep Mode | 睡眠模式 | -| switch.{DEVICEID}_swing | switch | Swing | 摆风 | - -## 服务 -生成以下扩展服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|------------------------------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "aux_heating"
"eco_mode"
"night_light"
"power"
"sleep_mode"
"swing" | -| value | true 或 false | - -示例 -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: eco_mode - value: true +# 中央空调Wi-Fi线控器 +## 特性 +- 支持目标温度设定 +- 支持运行模式设定 +- 支持风扇模式设定 +- 支持摆风模式设定 +- 支持电辅热 + +### 支持的模式 +- 睡眠模式 +- 节能模式 + +## 生成实体 +### 默认生成实体 +| 实体ID | 类型 | 描述 | +|----------------------------|---------|-------| +| climate.{DEVICEID}_climate | climate | 恒温器实体 | + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|--------------------------------------|--------|--------------------|-------| +| sensor.{DEVICEID}_indoor_temperature | sensor | Indoor Temperature | 室内温度 | +| switch.{DEVICEID}_aux_heating | switch | Aux Heating | 电辅热 | +| switch.{DEVICEID}_eco_mode | switch | ECO Mode | ECO模式 | +| switch.{DEVICEID}_night_light | switch | Night Light | 夜灯 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_sleep_mode | switch | Sleep Mode | 睡眠模式 | +| switch.{DEVICEID}_swing | switch | Swing | 摆风 | + +## 服务 +生成以下扩展服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|------------------------------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "aux_heating"
"eco_mode"
"night_light"
"power"
"sleep_mode"
"swing" | +| value | true 或 false | + +示例 +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: eco_mode + value: true ``` \ No newline at end of file diff --git a/doc/CF.md b/doc/CF.md index b3b1cb61..dc89c24f 100644 --- a/doc/CF.md +++ b/doc/CF.md @@ -1,43 +1,43 @@ -# Heat Bump -## Features -- Supports target temperature -- Supports run mode -- Supports auxiliary heating - -## Entities -### Default entity -| EntityID | Class | Description | -|----------------------------|---------|----------------| -| climate.{DEVICEID}_climate | climate | Climate entity | - -### Extra entities - -| EntityID | Class | Description | -|---------------------------------------|--------|---------------------| -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | -| switch.{DEVICEID}_aux_heating | switch | Aux Heating | -| switch.{DEVICEID}_power | switch | Power | - -## Service - - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "aux_heating"
"power" | -| value | true or false | - -Example -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: aux_heating - value: true +# Heat Bump +## Features +- Supports target temperature +- Supports run mode +- Supports auxiliary heating + +## Entities +### Default entity +| EntityID | Class | Description | +|----------------------------|---------|----------------| +| climate.{DEVICEID}_climate | climate | Climate entity | + +### Extra entities + +| EntityID | Class | Description | +|---------------------------------------|--------|---------------------| +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | +| switch.{DEVICEID}_aux_heating | switch | Aux Heating | +| switch.{DEVICEID}_power | switch | Power | + +## Service + + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "aux_heating"
"power" | +| value | true or false | + +Example +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: aux_heating + value: true ``` \ No newline at end of file diff --git a/doc/CF_hans.md b/doc/CF_hans.md index 1f051213..28d74641 100644 --- a/doc/CF_hans.md +++ b/doc/CF_hans.md @@ -1,42 +1,42 @@ -# 中央空调暖家 -## 特性 -- 支持目标温度设定 -- 支持运行模式设定 -- 支持电辅热 - -## 生成实体 -### 默认生成实体 -| 实体ID | 类型 | 描述 | -|----------------------------|---------|-------| -| climate.{DEVICEID}_climate | climate | 恒温器实体 | - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|---------------------------------------|--------|---------------------|------| -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | -| switch.{DEVICEID}_aux_heating | switch | Aux Heating | 电辅热 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | - -## Service - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|---------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "aux_heating"
"power" | -| value | true 或 false | - -示例 -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: aux_heating - value: true +# 中央空调暖家 +## 特性 +- 支持目标温度设定 +- 支持运行模式设定 +- 支持电辅热 + +## 生成实体 +### 默认生成实体 +| 实体ID | 类型 | 描述 | +|----------------------------|---------|-------| +| climate.{DEVICEID}_climate | climate | 恒温器实体 | + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|---------------------------------------|--------|---------------------|------| +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | +| switch.{DEVICEID}_aux_heating | switch | Aux Heating | 电辅热 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | + +## Service + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|---------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "aux_heating"
"power" | +| value | true 或 false | + +示例 +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: aux_heating + value: true ``` \ No newline at end of file diff --git a/doc/DA.md b/doc/DA.md index 056d7a25..0a41db9f 100644 --- a/doc/DA.md +++ b/doc/DA.md @@ -1,38 +1,38 @@ -# Top Load Washer - -## Entities -### Default entity -No default entity - -### Extra entities - -| EntityID | Class | Description | -|----------------------------------|--------|----------------| -| sensor.{DEVICEID}_progress | sensor | Progress | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_start | switch | Start | - -## Service - - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "power"
"start" | -| value | true or false | - -Example -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true +# Top Load Washer + +## Entities +### Default entity +No default entity + +### Extra entities + +| EntityID | Class | Description | +|----------------------------------|--------|----------------| +| sensor.{DEVICEID}_progress | sensor | Progress | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_start | switch | Start | + +## Service + + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "power"
"start" | +| value | true or false | + +Example +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true ``` \ No newline at end of file diff --git a/doc/DA_hans.md b/doc/DA_hans.md index ec7bdccf..f8ea513e 100644 --- a/doc/DA_hans.md +++ b/doc/DA_hans.md @@ -1,38 +1,38 @@ -# 波轮洗衣机 - -## 生成实体 -### 默认实体 -无默认实体 - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|----------------------------------|--------|----------------|------| -| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_start | switch | Start | 启动暂停 | - -## 服务 -生成以下扩展服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|---------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "power"
"start" | -| value | true 或 false | - -示例 -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true +# 波轮洗衣机 + +## 生成实体 +### 默认实体 +无默认实体 + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|----------------------------------|--------|----------------|------| +| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_start | switch | Start | 启动暂停 | + +## 服务 +生成以下扩展服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|---------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "power"
"start" | +| value | true 或 false | + +示例 +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true ``` \ No newline at end of file diff --git a/doc/DB.md b/doc/DB.md index 278e010a..8be5a6a9 100644 --- a/doc/DB.md +++ b/doc/DB.md @@ -1,38 +1,38 @@ -# Front Load Washer - -## Entities -### Default entity -No default entity - -### Extra entities - -| EntityID | Class | Description | -|----------------------------------|--------|----------------| -| sensor.{DEVICEID}_progress | sensor | Progress | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_start | switch | Start | - -## Service - - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "power"
"start" | -| value | true or false | - -Example -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true +# Front Load Washer + +## Entities +### Default entity +No default entity + +### Extra entities + +| EntityID | Class | Description | +|----------------------------------|--------|----------------| +| sensor.{DEVICEID}_progress | sensor | Progress | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_start | switch | Start | + +## Service + + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "power"
"start" | +| value | true or false | + +Example +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true ``` \ No newline at end of file diff --git a/doc/DB_hans.md b/doc/DB_hans.md index 365b92cc..34ac0110 100644 --- a/doc/DB_hans.md +++ b/doc/DB_hans.md @@ -1,37 +1,37 @@ -# 滚筒洗衣机 - -## 生成实体 -### 默认实体 -无默认实体 - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|----------------------------------|--------|----------------|------| -| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_start | switch | Start | 启动暂停 | - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|---------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "power"
"start" | -| value | true 或 false | - -示例 -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true +# 滚筒洗衣机 + +## 生成实体 +### 默认实体 +无默认实体 + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|----------------------------------|--------|----------------|------| +| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_start | switch | Start | 启动暂停 | + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|---------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "power"
"start" | +| value | true 或 false | + +示例 +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true ``` \ No newline at end of file diff --git a/doc/DC.md b/doc/DC.md index 79f87fe7..66eb297b 100644 --- a/doc/DC.md +++ b/doc/DC.md @@ -1,38 +1,38 @@ -# Clothes Dryer - -## Entities -### Default entity -No default entity - -### Extra entities - -| EntityID | Class | Description | -|----------------------------------|--------|----------------| -| sensor.{DEVICEID}_progress | sensor | Progress | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_start | switch | Start | - -## Service - - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "power"
"start" | -| value | true or false | - -Example -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true +# Clothes Dryer + +## Entities +### Default entity +No default entity + +### Extra entities + +| EntityID | Class | Description | +|----------------------------------|--------|----------------| +| sensor.{DEVICEID}_progress | sensor | Progress | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_start | switch | Start | + +## Service + + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "power"
"start" | +| value | true or false | + +Example +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true ``` \ No newline at end of file diff --git a/doc/DC_hans.md b/doc/DC_hans.md index f2d3d34b..3ca63cff 100644 --- a/doc/DC_hans.md +++ b/doc/DC_hans.md @@ -1,37 +1,37 @@ -# 干衣机 - -## 生成实体 -### 默认实体 -无默认实体 - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|----------------------------------|--------|----------------|------| -| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_start | switch | Start | 启动暂停 | - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|---------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "power"
"start" | -| value | true 或 false | - -示例 -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true +# 干衣机 + +## 生成实体 +### 默认实体 +无默认实体 + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|----------------------------------|--------|----------------|------| +| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_start | switch | Start | 启动暂停 | + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|---------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "power"
"start" | +| value | true 或 false | + +示例 +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true ``` \ No newline at end of file diff --git a/doc/E1.md b/doc/E1.md index 139242b7..1550a964 100644 --- a/doc/E1.md +++ b/doc/E1.md @@ -1,50 +1,50 @@ -# Dishwasher - -## Entities -### Default entity -No default entity - -### Extra entities - -| EntityID | Class | Description | -|-------------------------------------|---------------|------------------------| -| binary_sensor.{DEVICEID}_door | binary_sensor | Door | -| binary_sensor.{DEVICEID}_rinse_aid | binary_sensor | Rinse Aid Shortage | -| binary_sensor.{DEVICEID}_salt | binary_sensor | Salt Shortage | -| sensor.{DEVICEID}_humidity | sensor | Humidity | -| sensor.{DEVICEID}_progress | sensor | Progress | -| sensor.{DEVICEID}_status | sensor | Status | -| sensor.{DEVICEID}_storage_remaining | sensor | Storage Time Remaining | -| sensor.{DEVICEID}_temperature | sensor | Temperature | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | -| sensor.{DEVICEID}_mode | sensor | Mode | -| sensor.{DEVICEID}_error_code | sensor | Error Code | -| sensor.{DEVICEID}_softwater | sensor | Softwater Level | -| sensor.{DEVICEID}_bright | sensor | Bright Level | -| lock.{DEVICEID}_child_lock | lock | Child Lock | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_storage | switch | Storage | - -## Service - - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "child_lock"
"power"
"storage" | -| value | true or false | - -Example -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true +# Dishwasher + +## Entities +### Default entity +No default entity + +### Extra entities + +| EntityID | Class | Description | +|-------------------------------------|---------------|------------------------| +| binary_sensor.{DEVICEID}_door | binary_sensor | Door | +| binary_sensor.{DEVICEID}_rinse_aid | binary_sensor | Rinse Aid Shortage | +| binary_sensor.{DEVICEID}_salt | binary_sensor | Salt Shortage | +| sensor.{DEVICEID}_humidity | sensor | Humidity | +| sensor.{DEVICEID}_progress | sensor | Progress | +| sensor.{DEVICEID}_status | sensor | Status | +| sensor.{DEVICEID}_storage_remaining | sensor | Storage Time Remaining | +| sensor.{DEVICEID}_temperature | sensor | Temperature | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | +| sensor.{DEVICEID}_mode | sensor | Mode | +| sensor.{DEVICEID}_error_code | sensor | Error Code | +| sensor.{DEVICEID}_softwater | sensor | Softwater Level | +| sensor.{DEVICEID}_bright | sensor | Bright Level | +| lock.{DEVICEID}_child_lock | lock | Child Lock | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_storage | switch | Storage | + +## Service + + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "child_lock"
"power"
"storage" | +| value | true or false | + +Example +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true ``` \ No newline at end of file diff --git a/doc/E1_hans.md b/doc/E1_hans.md index d9ae848a..6ce6a45b 100644 --- a/doc/E1_hans.md +++ b/doc/E1_hans.md @@ -1,50 +1,50 @@ -# 洗碗机 - -## 生成实体 -### 默认实体 -无默认实体 - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|-------------------------------------|---------------|------------------------|--------| -| binary_sensor.{DEVICEID}_door | binary_sensor | Door | 门状态 | -| binary_sensor.{DEVICEID}_rinse_aid | binary_sensor | Rinse Aid Shortage | 漂洗剂不足 | -| binary_sensor.{DEVICEID}_salt | binary_sensor | Salt Shortage | 软水盐不足 | -| sensor.{DEVICEID}_humidity | sensor | Humidity | 湿度 | -| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | -| sensor.{DEVICEID}_status | sensor | Status | 状态 | -| sensor.{DEVICEID}_storage_remaining | sensor | Storage Time Remaining | 保管剩余时间 | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | -| sensor.{DEVICEID}_temperature | sensor | Temperature | 温度 | -| sensor.{DEVICEID}_mode | sensor | Working Mode | 工作模式 | -| sensor.{DEVICEID}_error_code | sensor | Error Code | 错误码 | -| sensor.{DEVICEID}_softwater | sensor | Softwater Level | 软水盐档位 | -| sensor.{DEVICEID}_bright | sensor | Bright Level | 亮碟剂档位 | -| lock.{DEVICEID}_child_lock | lock | Child Lock | 童锁 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_storage | switch | Storage | 保管开关 | - -## 服务 -生成以下扩展服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|-------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "child_lock"
"power"
"storage" | -| value | true 或 false | - -示例 -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true +# 洗碗机 + +## 生成实体 +### 默认实体 +无默认实体 + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|-------------------------------------|---------------|------------------------|--------| +| binary_sensor.{DEVICEID}_door | binary_sensor | Door | 门状态 | +| binary_sensor.{DEVICEID}_rinse_aid | binary_sensor | Rinse Aid Shortage | 漂洗剂不足 | +| binary_sensor.{DEVICEID}_salt | binary_sensor | Salt Shortage | 软水盐不足 | +| sensor.{DEVICEID}_humidity | sensor | Humidity | 湿度 | +| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | +| sensor.{DEVICEID}_status | sensor | Status | 状态 | +| sensor.{DEVICEID}_storage_remaining | sensor | Storage Time Remaining | 保管剩余时间 | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | +| sensor.{DEVICEID}_temperature | sensor | Temperature | 温度 | +| sensor.{DEVICEID}_mode | sensor | Working Mode | 工作模式 | +| sensor.{DEVICEID}_error_code | sensor | Error Code | 错误码 | +| sensor.{DEVICEID}_softwater | sensor | Softwater Level | 软水盐档位 | +| sensor.{DEVICEID}_bright | sensor | Bright Level | 亮碟剂档位 | +| lock.{DEVICEID}_child_lock | lock | Child Lock | 童锁 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_storage | switch | Storage | 保管开关 | + +## 服务 +生成以下扩展服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|-------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "child_lock"
"power"
"storage" | +| value | true 或 false | + +示例 +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true ``` \ No newline at end of file diff --git a/doc/E2.md b/doc/E2.md index b40c6a18..efb72367 100644 --- a/doc/E2.md +++ b/doc/E2.md @@ -1,62 +1,62 @@ -# Electric Water Heater - -## Features - -- Supports target temperature - -## Customize - -- Set the protocol of device ("auto" by default). -There are 2 different protocol version to control Electric Water Heater, "old protocol" or "new protocol". -If you can't control your device, try change this item and see if it works. -The options include true, false, and "auto". - -```json -{"old_protocol": true} -``` - -## Entities - -### Default entity - -| EntityID | Class | Description | -|--------------------------------------|--------------|---------------------| -| water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | - -### Extra entities - -| EntityID | Class | Description | -|------------------------------------------|---------------|---------------------| -| binary_sensor.{DEVICEID}_heating | binary_sensor | Heating | -| binary_sensor.{DEVICEID}_heat_insulating | binary_sensor | Heat Insulating | -| binary_sensor.{DEVICEID}_protection | binary_sensor | Protection | -| sensor.{DEVICEID}_heating_power | sensor | Heating Power | -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | -| switch.{DEVICEID}_auto_cut_out | switch | Auto Cut-out | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_variable_heating | switch | Variable Heating | -| switch.{DEVICEID}_whole_tank_heating | switch | Whole Tank Heating | - -## Services - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|------------------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "auto_cut_out"
"power"
"variable_heating"
"whole_tank_heating" | -| value | true or false | - -Example - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: variable_heating - value: true -``` +# Electric Water Heater + +## Features + +- Supports target temperature + +## Customize + +- Set the protocol of device ("auto" by default). +There are 2 different protocol version to control Electric Water Heater, "old protocol" or "new protocol". +If you can't control your device, try change this item and see if it works. +The options include true, false, and "auto". + +```json +{"old_protocol": true} +``` + +## Entities + +### Default entity + +| EntityID | Class | Description | +|--------------------------------------|--------------|---------------------| +| water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | + +### Extra entities + +| EntityID | Class | Description | +|------------------------------------------|---------------|---------------------| +| binary_sensor.{DEVICEID}_heating | binary_sensor | Heating | +| binary_sensor.{DEVICEID}_heat_insulating | binary_sensor | Heat Insulating | +| binary_sensor.{DEVICEID}_protection | binary_sensor | Protection | +| sensor.{DEVICEID}_heating_power | sensor | Heating Power | +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | +| switch.{DEVICEID}_auto_cut_out | switch | Auto Cut-out | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_variable_heating | switch | Variable Heating | +| switch.{DEVICEID}_whole_tank_heating | switch | Whole Tank Heating | + +## Services + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|------------------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "auto_cut_out"
"power"
"variable_heating"
"whole_tank_heating" | +| value | true or false | + +Example + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: variable_heating + value: true +``` diff --git a/doc/E2_hans.md b/doc/E2_hans.md index ffc4d6a9..90be96a3 100644 --- a/doc/E2_hans.md +++ b/doc/E2_hans.md @@ -1,56 +1,56 @@ -# 电热水器 -## 特性 -- 支持温度设定 - -## 自定义 -- 设置设备控制协议 (默认为"auto"). -美的使用了两种不同的协议来控制电热水器,"旧协议" 或 "新协议". -如果你无法控制你的热水器,可以尝试改变此项的值试试看。 -可选值包括true,false,"auto" - -```json -{"old_protocol": true} -``` - -## 生成实体 -### 默认生成实体 -| 实体ID | 类型 | 描述 | -|--------------------------------------|--------------|-------| -| water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|------------------------------------------|---------------|---------------------|------| -| binary_sensor.{DEVICEID}_heating | binary_sensor | Heating | 加热 | -| binary_sensor.{DEVICEID}_heat_insulating | binary_sensor | Heat Insulating | 保温 | -| binary_sensor.{DEVICEID}_protection | binary_sensor | Protection | 安全防护 | -| sensor.{DEVICEID}_heating_power | sensor | Heating Power | 加热功率 | -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | -| switch.{DEVICEID}_auto_cut_out | switch | Auto Cut-out | 出水断电 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_variable_heating | switch | Variable Heating | 变频加热 | -| switch.{DEVICEID}_whole_tank_heating | switch | Whole Tank Heating | 全胆速热 | - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|------------------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "auto_cut_out"
"power"
"variable_heating"
"whole_tank_heating" | -| value | true 或 false | - -示例 -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: variable_heating - value: true +# 电热水器 +## 特性 +- 支持温度设定 + +## 自定义 +- 设置设备控制协议 (默认为"auto"). +美的使用了两种不同的协议来控制电热水器,"旧协议" 或 "新协议". +如果你无法控制你的热水器,可以尝试改变此项的值试试看。 +可选值包括true,false,"auto" + +```json +{"old_protocol": true} +``` + +## 生成实体 +### 默认生成实体 +| 实体ID | 类型 | 描述 | +|--------------------------------------|--------------|-------| +| water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|------------------------------------------|---------------|---------------------|------| +| binary_sensor.{DEVICEID}_heating | binary_sensor | Heating | 加热 | +| binary_sensor.{DEVICEID}_heat_insulating | binary_sensor | Heat Insulating | 保温 | +| binary_sensor.{DEVICEID}_protection | binary_sensor | Protection | 安全防护 | +| sensor.{DEVICEID}_heating_power | sensor | Heating Power | 加热功率 | +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | +| switch.{DEVICEID}_auto_cut_out | switch | Auto Cut-out | 出水断电 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_variable_heating | switch | Variable Heating | 变频加热 | +| switch.{DEVICEID}_whole_tank_heating | switch | Whole Tank Heating | 全胆速热 | + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|------------------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "auto_cut_out"
"power"
"variable_heating"
"whole_tank_heating" | +| value | true 或 false | + +示例 +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: variable_heating + value: true ``` \ No newline at end of file diff --git a/doc/E3.md b/doc/E3.md index a732db42..420f740b 100644 --- a/doc/E3.md +++ b/doc/E3.md @@ -1,59 +1,59 @@ -# Gas Water Heater - -## Features - -- Supports target temperature - -## Customize - -- Set the temperature precision for whole or halves (false for whole and true for halves, default by false) - - If the temperature value displayed on your water heater is twice the actual value, please set this value to true. - -```json -{"precision_halves": true} -``` - -## Entities - -### Default entity - -| EntityID | Class | Description | -|--------------------------------------|--------------|---------------------| -| water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | - -### Extra entities - -| EntityID | Class | Description | -|----------------------------------------|---------------|-------------------------| -| binary_sensor.{DEVICEID}_burning_state | binary_sensor | Burning State | -| binary_sensor.{DEVICEID}_protection | binary_sensor | Protection | -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_smart_volume | switch | Smart Volume | -| switch.{DEVICEID}_zero_cold_water | switch | Zero Cold Water | -| switch.{DEVICEID}_zero_cold_pulse | switch | Zero Cold Water (Pulse) | - -## Services - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "energy_saving"
"power"
"smart_volume"
"zero_cold_water"
"zero_cold_pulse" | -| value | true or false | - -Example - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: smart_volume - value: true -``` +# Gas Water Heater + +## Features + +- Supports target temperature + +## Customize + +- Set the temperature precision for whole or halves (false for whole and true for halves, default by false) + + If the temperature value displayed on your water heater is twice the actual value, please set this value to true. + +```json +{"precision_halves": true} +``` + +## Entities + +### Default entity + +| EntityID | Class | Description | +|--------------------------------------|--------------|---------------------| +| water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | + +### Extra entities + +| EntityID | Class | Description | +|----------------------------------------|---------------|-------------------------| +| binary_sensor.{DEVICEID}_burning_state | binary_sensor | Burning State | +| binary_sensor.{DEVICEID}_protection | binary_sensor | Protection | +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_smart_volume | switch | Smart Volume | +| switch.{DEVICEID}_zero_cold_water | switch | Zero Cold Water | +| switch.{DEVICEID}_zero_cold_pulse | switch | Zero Cold Water (Pulse) | + +## Services + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "energy_saving"
"power"
"smart_volume"
"zero_cold_water"
"zero_cold_pulse" | +| value | true or false | + +Example + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: smart_volume + value: true +``` diff --git a/doc/E3_hans.md b/doc/E3_hans.md index dcab3919..35eeb79f 100644 --- a/doc/E3_hans.md +++ b/doc/E3_hans.md @@ -1,59 +1,59 @@ -# 燃气热水器 - -## 特性 - -- 支持温度设定 - -## 自定义 - -- 设置热水器温度基数为整度还是半度 (false 为整度 true 为半度, 默认为 false) - - 如果你的热水器显示的温度为实际温度的两倍,请将该值设为true。 - -```json -{"precision_halves": true} -``` - -## 生成实体 - -### 默认生成实体 - -| 实体ID | 类型 | 描述 | -|--------------------------------------|--------------|-------| -| water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | - -### 额外生成实体 - -| 实体ID | 类型 | 名称 | 描述 | -|----------------------------------------|---------------|-------------------------|---------| -| binary_sensor.{DEVICEID}_burning_state | binary_sensor | Burning State | 燃烧状态 | -| binary_sensor.{DEVICEID}_protection | binary_sensor | Protection | 安全防护 | -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 温度 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_smart_volume | switch | Smart Volume | 智能变容 | -| switch.{DEVICEID}_zero_cold_water | switch | Zero Cold Water | 零冷水 | -| switch.{DEVICEID}_zero_cold_pulse | switch | Zero Cold Water (Pulse) | 零冷水(点动) | - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "energy_saving"
"power"
"smart_volume"
"zero_cold_water"
"zero_cold_pulse" | -| value | true or false | - -示例 - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: smart_volume - value: true -``` +# 燃气热水器 + +## 特性 + +- 支持温度设定 + +## 自定义 + +- 设置热水器温度基数为整度还是半度 (false 为整度 true 为半度, 默认为 false) + + 如果你的热水器显示的温度为实际温度的两倍,请将该值设为true。 + +```json +{"precision_halves": true} +``` + +## 生成实体 + +### 默认生成实体 + +| 实体ID | 类型 | 描述 | +|--------------------------------------|--------------|-------| +| water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | + +### 额外生成实体 + +| 实体ID | 类型 | 名称 | 描述 | +|----------------------------------------|---------------|-------------------------|---------| +| binary_sensor.{DEVICEID}_burning_state | binary_sensor | Burning State | 燃烧状态 | +| binary_sensor.{DEVICEID}_protection | binary_sensor | Protection | 安全防护 | +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 温度 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_smart_volume | switch | Smart Volume | 智能变容 | +| switch.{DEVICEID}_zero_cold_water | switch | Zero Cold Water | 零冷水 | +| switch.{DEVICEID}_zero_cold_pulse | switch | Zero Cold Water (Pulse) | 零冷水(点动) | + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "energy_saving"
"power"
"smart_volume"
"zero_cold_water"
"zero_cold_pulse" | +| value | true or false | + +示例 + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: smart_volume + value: true +``` diff --git a/doc/EA.md b/doc/EA.md index c399f3e3..1e51d4d9 100644 --- a/doc/EA.md +++ b/doc/EA.md @@ -1,24 +1,24 @@ -# Electric Rice Cooker - -## Entities - -### Default entity - -No default entity. - -### Extra entities - -| EntityID | Class | Description | -|--------------------------------------|---------------|--------------------| -| binary_sensor.{DEVICEID}_cooking | binary_sensor | Cooking | -| binary_sensor.{DEVICEID}_keep_warm | binary_sensor | Keep Warm | -| sensor.{DEVICEID}_bottom_temperature | sensor | Bottom Temperature | -| sensor.{DEVICEID}_keep_warm_time | sensor | Keep Warm Time | -| sensor.{DEVICEID}_mode | sensor | Mode | -| sensor.{DEVICEID}_progress | sensor | Progress | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | -| sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | - -## Service - -No services. +# Electric Rice Cooker + +## Entities + +### Default entity + +No default entity. + +### Extra entities + +| EntityID | Class | Description | +|--------------------------------------|---------------|--------------------| +| binary_sensor.{DEVICEID}_cooking | binary_sensor | Cooking | +| binary_sensor.{DEVICEID}_keep_warm | binary_sensor | Keep Warm | +| sensor.{DEVICEID}_bottom_temperature | sensor | Bottom Temperature | +| sensor.{DEVICEID}_keep_warm_time | sensor | Keep Warm Time | +| sensor.{DEVICEID}_mode | sensor | Mode | +| sensor.{DEVICEID}_progress | sensor | Progress | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | +| sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | + +## Service + +No services. diff --git a/doc/EA_hans.md b/doc/EA_hans.md index dbb1a0c3..8fb91828 100644 --- a/doc/EA_hans.md +++ b/doc/EA_hans.md @@ -1,24 +1,24 @@ -# 电饭煲 - -## 生成实体 - -### 默认实体 - -无默认实体 - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|--------------------------------------|---------------|--------------------|------| -| binary_sensor.{DEVICEID}_cooking | binary_sensor | Cooking | 烹饪中 | -| binary_sensor.{DEVICEID}_keep_warm | binary_sensor | Keep Warm | 保温中 | -| sensor.{DEVICEID}_bottom_temperature | sensor | Bottom Temperature | 底部温度 | -| sensor.{DEVICEID}_keep_warm_time | sensor | Keep Warm Time | 保温时间 | -| sensor.{DEVICEID}_mode | sensor | Mode | 模式 | -| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | -| sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | - -## 服务 - -无服务 +# 电饭煲 + +## 生成实体 + +### 默认实体 + +无默认实体 + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|--------------------------------------|---------------|--------------------|------| +| binary_sensor.{DEVICEID}_cooking | binary_sensor | Cooking | 烹饪中 | +| binary_sensor.{DEVICEID}_keep_warm | binary_sensor | Keep Warm | 保温中 | +| sensor.{DEVICEID}_bottom_temperature | sensor | Bottom Temperature | 底部温度 | +| sensor.{DEVICEID}_keep_warm_time | sensor | Keep Warm Time | 保温时间 | +| sensor.{DEVICEID}_mode | sensor | Mode | 模式 | +| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | +| sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | + +## 服务 + +无服务 diff --git a/doc/EC.md b/doc/EC.md index 6a383310..ed9d7bec 100644 --- a/doc/EC.md +++ b/doc/EC.md @@ -1,24 +1,24 @@ -# Electric Pressure Cooker - -## Entities - -### Default entity - -No default entity. - -### Extra entities - -| EntityID | Class | Description | -|----------------------------------------|---------------|--------------------| -| binary_sensor.{DEVICEID}_cooking | binary_sensor | Cooking | -| binary_sensor.{DEVICEID}_with_pressure | binary_sensor | With Pressure | -| sensor.{DEVICEID}_bottom_temperature | sensor | Bottom Temperature | -| sensor.{DEVICEID}_keep_warm_time | sensor | Keep Warm Time | -| sensor.{DEVICEID}_mode | sensor | Mode | -| sensor.{DEVICEID}_progress | sensor | Progress | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | -| sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | - -## Service - -No services. +# Electric Pressure Cooker + +## Entities + +### Default entity + +No default entity. + +### Extra entities + +| EntityID | Class | Description | +|----------------------------------------|---------------|--------------------| +| binary_sensor.{DEVICEID}_cooking | binary_sensor | Cooking | +| binary_sensor.{DEVICEID}_with_pressure | binary_sensor | With Pressure | +| sensor.{DEVICEID}_bottom_temperature | sensor | Bottom Temperature | +| sensor.{DEVICEID}_keep_warm_time | sensor | Keep Warm Time | +| sensor.{DEVICEID}_mode | sensor | Mode | +| sensor.{DEVICEID}_progress | sensor | Progress | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | +| sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | + +## Service + +No services. diff --git a/doc/EC_hans.md b/doc/EC_hans.md index 22fba03b..5dcf98f6 100644 --- a/doc/EC_hans.md +++ b/doc/EC_hans.md @@ -1,24 +1,24 @@ -# 电压力锅 - -## 生成实体 - -### 默认实体 - -无默认实体 - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|----------------------------------------|---------------|--------------------|------| -| binary_sensor.{DEVICEID}_cooking | binary_sensor | Cooking | 烹饪中 | -| binary_sensor.{DEVICEID}_with_pressure | binary_sensor | With Pressure | 带压中 | -| sensor.{DEVICEID}_bottom_temperature | sensor | Bottom Temperature | 底部温度 | -| sensor.{DEVICEID}_keep_warm_time | sensor | Keep Warm Time | 保温时间 | -| sensor.{DEVICEID}_mode | sensor | Mode | 模式 | -| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | -| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | -| sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | - -## 服务 - -无服务 +# 电压力锅 + +## 生成实体 + +### 默认实体 + +无默认实体 + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|----------------------------------------|---------------|--------------------|------| +| binary_sensor.{DEVICEID}_cooking | binary_sensor | Cooking | 烹饪中 | +| binary_sensor.{DEVICEID}_with_pressure | binary_sensor | With Pressure | 带压中 | +| sensor.{DEVICEID}_bottom_temperature | sensor | Bottom Temperature | 底部温度 | +| sensor.{DEVICEID}_keep_warm_time | sensor | Keep Warm Time | 保温时间 | +| sensor.{DEVICEID}_mode | sensor | Mode | 模式 | +| sensor.{DEVICEID}_progress | sensor | Progress | 当前程序 | +| sensor.{DEVICEID}_time_remaining | sensor | Time Remaining | 剩余时间 | +| sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | + +## 服务 + +无服务 diff --git a/doc/ED.md b/doc/ED.md index 6301bfa8..fffd2401 100644 --- a/doc/ED.md +++ b/doc/ED.md @@ -1,27 +1,27 @@ -# Water Drinking Appliance - -## Entities - -### Default entity - -No default entity. - -### Extra entities - -| EntityID | Class | Description | -|-------------------------------------|--------|------------------------| -| sensor.{DEVICEID}_filter1 | sensor | Filter1 Available Days | -| sensor.{DEVICEID}_filter2 | sensor | Filter2 Available Days | -| sensor.{DEVICEID}_filter3 | sensor | Filter3 Available Days | -| sensor.{DEVICEID}_life1 | sensor | Filter1 Life Level | -| sensor.{DEVICEID}_life2 | sensor | Filter2 Life Level | -| sensor.{DEVICEID}_life3 | sensor | Filter3 Life Level | -| sensor.{DEVICEID}_in_tds | sensor | In TDS | -| sensor.{DEVICEID}_out_tds | sensor | Out TDS | -| sensor.{DEVICEID}_water_consumption | sensor | Water Consumption | -| lock.{DEVICEID}_child_lock | switch | Child Lock | -| switch.{DEVICEID}_power | switch | Power | - -## Service - -No services. +# Water Drinking Appliance + +## Entities + +### Default entity + +No default entity. + +### Extra entities + +| EntityID | Class | Description | +|-------------------------------------|--------|------------------------| +| sensor.{DEVICEID}_filter1 | sensor | Filter1 Available Days | +| sensor.{DEVICEID}_filter2 | sensor | Filter2 Available Days | +| sensor.{DEVICEID}_filter3 | sensor | Filter3 Available Days | +| sensor.{DEVICEID}_life1 | sensor | Filter1 Life Level | +| sensor.{DEVICEID}_life2 | sensor | Filter2 Life Level | +| sensor.{DEVICEID}_life3 | sensor | Filter3 Life Level | +| sensor.{DEVICEID}_in_tds | sensor | In TDS | +| sensor.{DEVICEID}_out_tds | sensor | Out TDS | +| sensor.{DEVICEID}_water_consumption | sensor | Water Consumption | +| lock.{DEVICEID}_child_lock | switch | Child Lock | +| switch.{DEVICEID}_power | switch | Power | + +## Service + +No services. diff --git a/doc/ED_hans.md b/doc/ED_hans.md index a3a1d038..711d9e9e 100644 --- a/doc/ED_hans.md +++ b/doc/ED_hans.md @@ -1,27 +1,27 @@ -# 饮用水设备 - -## 生成实体 - -### 默认实体 - -无默认实体 - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|-------------------------------------|--------|------------------------|---------| -| sensor.{DEVICEID}_filter1 | sensor | Filter1 Available Days | 滤芯1可用天数 | -| sensor.{DEVICEID}_filter2 | sensor | Filter2 Available Days | 滤芯2可用天数 | -| sensor.{DEVICEID}_filter3 | sensor | Filter3 Available Days | 滤芯3可用天数 | -| sensor.{DEVICEID}_life1 | sensor | Filter1 Life Level | 滤芯1剩余寿命 | -| sensor.{DEVICEID}_life2 | sensor | Filter2 Life Level | 滤芯2剩余寿命 | -| sensor.{DEVICEID}_life3 | sensor | Filter3 Life Level | 滤芯3剩余寿命 | -| sensor.{DEVICEID}_in_tds | sensor | In TDS | 进水TDS | -| sensor.{DEVICEID}_out_tds | sensor | Out TDS | 出水TDS | -| sensor.{DEVICEID}_water_consumption | sensor | Water Consumption | 总耗水量 | -| lock.{DEVICEID}_child_lock | switch | 童锁 | | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | - -## 服务 - -无服务 +# 饮用水设备 + +## 生成实体 + +### 默认实体 + +无默认实体 + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|-------------------------------------|--------|------------------------|---------| +| sensor.{DEVICEID}_filter1 | sensor | Filter1 Available Days | 滤芯1可用天数 | +| sensor.{DEVICEID}_filter2 | sensor | Filter2 Available Days | 滤芯2可用天数 | +| sensor.{DEVICEID}_filter3 | sensor | Filter3 Available Days | 滤芯3可用天数 | +| sensor.{DEVICEID}_life1 | sensor | Filter1 Life Level | 滤芯1剩余寿命 | +| sensor.{DEVICEID}_life2 | sensor | Filter2 Life Level | 滤芯2剩余寿命 | +| sensor.{DEVICEID}_life3 | sensor | Filter3 Life Level | 滤芯3剩余寿命 | +| sensor.{DEVICEID}_in_tds | sensor | In TDS | 进水TDS | +| sensor.{DEVICEID}_out_tds | sensor | Out TDS | 出水TDS | +| sensor.{DEVICEID}_water_consumption | sensor | Water Consumption | 总耗水量 | +| lock.{DEVICEID}_child_lock | switch | 童锁 | | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | + +## 服务 + +无服务 diff --git a/doc/FA.md b/doc/FA.md index a4e08289..cfc8d86b 100644 --- a/doc/FA.md +++ b/doc/FA.md @@ -1,85 +1,85 @@ -# Fan - -## Features - -- Supports fan speed -- Supports preset mode -- Supports oscillation -- Supports tilting - -## Customize - -Set the levels of the fan device except "Off" (3 by default). - -```json -{"speed_count": 5} -``` - -## Entities - -### Default entity - -| EntityID | Class | Description | -|--------------------|-------|-------------| -| fan.{DEVICEID}_fan | fan | Fan entity | - -### Extra entities - -| EntityID | Class | Description | -|-------------------------------------|--------|-------------------| -| select.{DEVICEID}_oscillation_mode | select | Oscillation Mode | -| select.{DEVICEID}_oscillation_angle | select | Oscillation Angle | -| select.{DEVICEID}_tilting_angle | select | Tilting Angle | -| lock.{DEVICEID}_child_lock | lock | Child Lock | -| switch.{DEVICEID}_oscillate | switch | Oscillate | -| switch.{DEVICEID}_power | switch | Power | - -## Services - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "child_lock"
"oscillate" | -| value | true or false | - -| Name | Description | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "oscillation_mode" | -| value | "Off"
"Oscillation"
"Tilting"
"Curve-W"
"Curve-8"
"Reserved"
"Both" | - -| Name | Description | -|-----------|----------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "oscillation_angle" | -| value | "Off"
"30"
"60"
"90"
"120"
"180"
"360" | - -| Name | Description | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "tilting_angle" | -| value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | - -Example - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true -``` - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: oscillation_angle - value: "90" -``` +# Fan + +## Features + +- Supports fan speed +- Supports preset mode +- Supports oscillation +- Supports tilting + +## Customize + +Set the levels of the fan device except "Off" (3 by default). + +```json +{"speed_count": 5} +``` + +## Entities + +### Default entity + +| EntityID | Class | Description | +|--------------------|-------|-------------| +| fan.{DEVICEID}_fan | fan | Fan entity | + +### Extra entities + +| EntityID | Class | Description | +|-------------------------------------|--------|-------------------| +| select.{DEVICEID}_oscillation_mode | select | Oscillation Mode | +| select.{DEVICEID}_oscillation_angle | select | Oscillation Angle | +| select.{DEVICEID}_tilting_angle | select | Tilting Angle | +| lock.{DEVICEID}_child_lock | lock | Child Lock | +| switch.{DEVICEID}_oscillate | switch | Oscillate | +| switch.{DEVICEID}_power | switch | Power | + +## Services + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "child_lock"
"oscillate" | +| value | true or false | + +| Name | Description | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "oscillation_mode" | +| value | "Off"
"Oscillation"
"Tilting"
"Curve-W"
"Curve-8"
"Reserved"
"Both" | + +| Name | Description | +|-----------|----------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "oscillation_angle" | +| value | "Off"
"30"
"60"
"90"
"120"
"180"
"360" | + +| Name | Description | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "tilting_angle" | +| value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | + +Example + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true +``` + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: oscillation_angle + value: "90" +``` diff --git a/doc/FA_hans.md b/doc/FA_hans.md index 4e2bcd15..33426dd0 100644 --- a/doc/FA_hans.md +++ b/doc/FA_hans.md @@ -1,85 +1,85 @@ -# 电风扇 - -## 特性 - -- 支持风速调节 -- 支持预设模式 -- 支持水平摆头 -- 支持垂直摆头 - -## 自定义 - -设置风扇的挡位, 不包括"Off"在内(默认为3)。 - -```json -{"speed_count": 5} -``` - -## 生成实体 - -### 默认生成实体 - -| 实体ID | 类型 | 描述 | -|--------------------|-----|------| -| fan.{DEVICEID}_fan | fan | 风扇实体 | - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|-------------------------------------|--------|-------------------|--------| -| select.{DEVICEID}_oscillation_mode | select | Oscillation Mode | 摆头模式 | -| select.{DEVICEID}_oscillation_angle | select | Oscillation Angle | 水平摆头角度 | -| select.{DEVICEID}_tilting_angle | select | Tilting Angle | 垂直摆头角度 | -| lock.{DEVICEID}_child_lock | lock | Child Lock | 童锁 | -| switch.{DEVICEID}_oscillate | switch | Oscillate | 摆头开关 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "child_lock"
"oscillate" | -| value | true 或 false | - -| 名称 | 描述 | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "oscillation_mode" | -| value | "Off"
"Oscillation"
"Tilting"
"Curve-W"
"Curve-8"
"Reserved"
"Both" | - -| 名称 | 描述 | -|-----------|----------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "oscillation_angle" | -| value | "Off"
"30"
"60"
"90"
"120"
"180"
"360" | - -| 名称 | 描述 | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "tilting_angle" | -| value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | - -示例 - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true -``` - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: oscillation_angle - value: "90" -``` +# 电风扇 + +## 特性 + +- 支持风速调节 +- 支持预设模式 +- 支持水平摆头 +- 支持垂直摆头 + +## 自定义 + +设置风扇的挡位, 不包括"Off"在内(默认为3)。 + +```json +{"speed_count": 5} +``` + +## 生成实体 + +### 默认生成实体 + +| 实体ID | 类型 | 描述 | +|--------------------|-----|------| +| fan.{DEVICEID}_fan | fan | 风扇实体 | + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|-------------------------------------|--------|-------------------|--------| +| select.{DEVICEID}_oscillation_mode | select | Oscillation Mode | 摆头模式 | +| select.{DEVICEID}_oscillation_angle | select | Oscillation Angle | 水平摆头角度 | +| select.{DEVICEID}_tilting_angle | select | Tilting Angle | 垂直摆头角度 | +| lock.{DEVICEID}_child_lock | lock | Child Lock | 童锁 | +| switch.{DEVICEID}_oscillate | switch | Oscillate | 摆头开关 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "child_lock"
"oscillate" | +| value | true 或 false | + +| 名称 | 描述 | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "oscillation_mode" | +| value | "Off"
"Oscillation"
"Tilting"
"Curve-W"
"Curve-8"
"Reserved"
"Both" | + +| 名称 | 描述 | +|-----------|----------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "oscillation_angle" | +| value | "Off"
"30"
"60"
"90"
"120"
"180"
"360" | + +| 名称 | 描述 | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "tilting_angle" | +| value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | + +示例 + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true +``` + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: oscillation_angle + value: "90" +``` diff --git a/doc/FC.md b/doc/FC.md index 3ce9ca06..e71b41bd 100644 --- a/doc/FC.md +++ b/doc/FC.md @@ -1,90 +1,90 @@ -# Air Purifier - -## Customize - -Set the high/low value of PM2.5 to automatically turn standby mode on or off. - -```json -{"standby_detect": [50, 20]} -``` - -## Entities - -### Default entity - -No default entity. - -### Extra entities - -| EntityID | Class | Description | -|----------------------------------|--------|--------------------| -| sensor.{DEVICEID}_pm25 | sensor | PM 2.5 | -| sensor.{DEVICEID}_tvoc | sensor | TVOC | -| sensor.{DEVICEID}_hcho | sensor | Methanal | -| sensor.{DEVICEID}_filter1_life | sensor | Filter1 Life Level | -| sensor.{DEVICEID}_filter2_life | sensor | Filter2 Life Level | -| lock.{DEVICEID}_child_lock | lock | Child Lock | -| switch.{DEVICEID}_anion | switch | Anion | -| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | -| switch.{DEVICEID}_power | switch | Power | -| switch.{DEVICEID}_standby | switch | Standby | -| select.{DEVICEID}_detect_mode | select | Detect Mode | -| select.{DEVICEID}_mode | select | Mode | -| select.{DEVICEID}_fan_speed | select | Fan Speed | -| select.{DEVICEID}_screen_display | select | Screen Display | - -## Service - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|--------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "child_lock"
"anion"
"prompt_tone"
"power" | -| value | true or false | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "detect_mode" | -| value | "Off"
"PM 2.5"
"Methanal" | - -| Name | Description | -|-----------|--------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "mode" | -| value | "Auto"
"Manual"
"Sleep"
"Fast"
"Smoke" | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "fan_speed" | -| value | "Auto"
"Low"
"Medium"
"High" | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "screen_display" | -| value | "Bright"
"Dim"
"Off" | - -Example - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: prompt_tone - value: true -``` - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: fan_speed - value: Auto -``` +# Air Purifier + +## Customize + +Set the high/low value of PM2.5 to automatically turn standby mode on or off. + +```json +{"standby_detect": [50, 20]} +``` + +## Entities + +### Default entity + +No default entity. + +### Extra entities + +| EntityID | Class | Description | +|----------------------------------|--------|--------------------| +| sensor.{DEVICEID}_pm25 | sensor | PM 2.5 | +| sensor.{DEVICEID}_tvoc | sensor | TVOC | +| sensor.{DEVICEID}_hcho | sensor | Methanal | +| sensor.{DEVICEID}_filter1_life | sensor | Filter1 Life Level | +| sensor.{DEVICEID}_filter2_life | sensor | Filter2 Life Level | +| lock.{DEVICEID}_child_lock | lock | Child Lock | +| switch.{DEVICEID}_anion | switch | Anion | +| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | +| switch.{DEVICEID}_power | switch | Power | +| switch.{DEVICEID}_standby | switch | Standby | +| select.{DEVICEID}_detect_mode | select | Detect Mode | +| select.{DEVICEID}_mode | select | Mode | +| select.{DEVICEID}_fan_speed | select | Fan Speed | +| select.{DEVICEID}_screen_display | select | Screen Display | + +## Service + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|--------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "child_lock"
"anion"
"prompt_tone"
"power" | +| value | true or false | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "detect_mode" | +| value | "Off"
"PM 2.5"
"Methanal" | + +| Name | Description | +|-----------|--------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "mode" | +| value | "Auto"
"Manual"
"Sleep"
"Fast"
"Smoke" | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "fan_speed" | +| value | "Auto"
"Low"
"Medium"
"High" | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "screen_display" | +| value | "Bright"
"Dim"
"Off" | + +Example + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: prompt_tone + value: true +``` + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: fan_speed + value: Auto +``` diff --git a/doc/FC_hans.md b/doc/FC_hans.md index 145909fb..f6dc04ae 100644 --- a/doc/FC_hans.md +++ b/doc/FC_hans.md @@ -1,89 +1,89 @@ -# 空气净化器 - -## 自定义 - -设置自动打开或关闭待机模式的PM2.5检测数值 - -```json -{"standby_detect": [50, 20]} -``` - -## 生成实体 - -### 默认生成实体 - -无默认实体 - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|----------------------------------|--------|--------------------|--------| -| sensor.{DEVICEID}_pm25 | sensor | PM 2.5 | PM 2.5 | -| sensor.{DEVICEID}_tvoc | sensor | TVOC | 可挥发有机物 | -| sensor.{DEVICEID}_hcho | sensor | Methanal | 甲醛 | -| sensor.{DEVICEID}_filter1_life | sensor | Filter1 Life Level | 滤芯1寿命 | -| sensor.{DEVICEID}_filter2_life | sensor | Filter2 Life Level | 滤芯2寿命 | -| lock.{DEVICEID}_child_lock | lock | Child Lock | 童锁 | -| switch.{DEVICEID}_anion | switch | Anion | 负离子 | -| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | 提示音 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| switch.{DEVICEID}_standby | switch | Standby | 待机 | -| select.{DEVICEID}_detect_mode | select | Detect Mode | 检测模式 | -| select.{DEVICEID}_mode | select | Mode | 运行模式 | -| select.{DEVICEID}_fan_speed | select | Fan Speed | 风速设定 | -| select.{DEVICEID}_screen_display | select | Screen Display | 屏显 | - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|--------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "child_lock"
"anion"
"prompt_tone"
"power" | -| value | true 或 false | - -| 名称 | 描述 | -|-----------|-----------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "detect_mode" | -| value | "Off"
"PM 2.5"
"Methanal" | - -| 名称 | 描述 | -|-----------|--------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "mode" | -| value | "Auto"
"Manual"
"Sleep"
"Fast"
"Smoke" | - -| 名称 | 描述 | -|-----------|------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "fan_speed" | -| value | "Auto"
"Low"
"Medium"
"High" | - -| 名称 | 描述 | -|-----------|------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "screen_display" | -| value | "Bright"
"Dim"
"Off" | - -示例 -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: prompt_tone - value: true -``` - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: fan_speed - value: Auto -``` +# 空气净化器 + +## 自定义 + +设置自动打开或关闭待机模式的PM2.5检测数值 + +```json +{"standby_detect": [50, 20]} +``` + +## 生成实体 + +### 默认生成实体 + +无默认实体 + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|----------------------------------|--------|--------------------|--------| +| sensor.{DEVICEID}_pm25 | sensor | PM 2.5 | PM 2.5 | +| sensor.{DEVICEID}_tvoc | sensor | TVOC | 可挥发有机物 | +| sensor.{DEVICEID}_hcho | sensor | Methanal | 甲醛 | +| sensor.{DEVICEID}_filter1_life | sensor | Filter1 Life Level | 滤芯1寿命 | +| sensor.{DEVICEID}_filter2_life | sensor | Filter2 Life Level | 滤芯2寿命 | +| lock.{DEVICEID}_child_lock | lock | Child Lock | 童锁 | +| switch.{DEVICEID}_anion | switch | Anion | 负离子 | +| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | 提示音 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| switch.{DEVICEID}_standby | switch | Standby | 待机 | +| select.{DEVICEID}_detect_mode | select | Detect Mode | 检测模式 | +| select.{DEVICEID}_mode | select | Mode | 运行模式 | +| select.{DEVICEID}_fan_speed | select | Fan Speed | 风速设定 | +| select.{DEVICEID}_screen_display | select | Screen Display | 屏显 | + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|--------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "child_lock"
"anion"
"prompt_tone"
"power" | +| value | true 或 false | + +| 名称 | 描述 | +|-----------|-----------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "detect_mode" | +| value | "Off"
"PM 2.5"
"Methanal" | + +| 名称 | 描述 | +|-----------|--------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "mode" | +| value | "Auto"
"Manual"
"Sleep"
"Fast"
"Smoke" | + +| 名称 | 描述 | +|-----------|------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "fan_speed" | +| value | "Auto"
"Low"
"Medium"
"High" | + +| 名称 | 描述 | +|-----------|------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "screen_display" | +| value | "Bright"
"Dim"
"Off" | + +示例 +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: prompt_tone + value: true +``` + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: fan_speed + value: Auto +``` diff --git a/doc/FD.md b/doc/FD.md index 0bfd3765..325d4728 100644 --- a/doc/FD.md +++ b/doc/FD.md @@ -1,71 +1,71 @@ -# Humidifier - -## Features - -- Supports preset mode -- Supports fan mode -- Supports humidity setting - -## Entities - -### Default entity - -| EntityID | Class | Description | -|----------------------------------|------------|-------------------| -| humidifier.{DEVICEID}_humidifier | humidifier | Humidifier entity | - -### Extra entities - -| EntityID | Class | Description | -|---------------------------------------|--------|---------------------| -| sensor.{DEVICEID}_current_humidity | sensor | Current Humidity | -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | -| switch.{DEVICEID}_disinfect | switch | Disinfect | -| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | -| switch.{DEVICEID}_power | switch | Power | -| select.{DEVICEID}_fan_speed | select | Fan Speed | -| select.{DEVICEID}_screen_display | select | Screen Display | - -## Service - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -Set the attribute of appliance. Service data: - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "disinfect"
"prompt_tone"
"power" | -| value | true or false | - -| Name | Description | -|-----------|-----------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "fan_speed" | -| value | "Lowest"
"Low"
"Medium"
"High"
"Auto"
"Off" | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "screen_display" | -| value | "Bright"
"Dim"
"Off" | - -Example - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: prompt_tone - value: true -``` - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: fan_speed - value: Medium -``` +# Humidifier + +## Features + +- Supports preset mode +- Supports fan mode +- Supports humidity setting + +## Entities + +### Default entity + +| EntityID | Class | Description | +|----------------------------------|------------|-------------------| +| humidifier.{DEVICEID}_humidifier | humidifier | Humidifier entity | + +### Extra entities + +| EntityID | Class | Description | +|---------------------------------------|--------|---------------------| +| sensor.{DEVICEID}_current_humidity | sensor | Current Humidity | +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | +| switch.{DEVICEID}_disinfect | switch | Disinfect | +| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | +| switch.{DEVICEID}_power | switch | Power | +| select.{DEVICEID}_fan_speed | select | Fan Speed | +| select.{DEVICEID}_screen_display | select | Screen Display | + +## Service + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +Set the attribute of appliance. Service data: + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "disinfect"
"prompt_tone"
"power" | +| value | true or false | + +| Name | Description | +|-----------|-----------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "fan_speed" | +| value | "Lowest"
"Low"
"Medium"
"High"
"Auto"
"Off" | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "screen_display" | +| value | "Bright"
"Dim"
"Off" | + +Example + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: prompt_tone + value: true +``` + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: fan_speed + value: Medium +``` diff --git a/doc/FD_hans.md b/doc/FD_hans.md index a0c9e8d2..3cd9f5fe 100644 --- a/doc/FD_hans.md +++ b/doc/FD_hans.md @@ -1,71 +1,71 @@ -# 加湿器 - -## 特性 - -- 支持运行模式 -- 支持风扇模式设定 -- 支持湿度设定 - -## 生成实体 - -### 默认生成实体 - -| 实体ID | 类型 | 描述 | -|----------------------------------|------------|-------| -| humidifier.{DEVICEID}_humidifier | humidifier | 加湿器实体 | - -### 额外生成实体 - -| EntityID | 类型 | 名称 | 描述 | -|---------------------------------------|--------|---------------------|------| -| sensor.{DEVICEID}_current_humidity | sensor | Current Humidity | 当前湿度 | -| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | -| switch.{DEVICEID}_disinfect | switch | Disinfect | 消毒开关 | -| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | 提示音 | -| switch.{DEVICEID}_power | switch | Power | 电源开关 | -| select.{DEVICEID}_fan_speed | select | Fan Speed | 风速设定 | -| select.{DEVICEID}_screen_display | select | Screen Display | 屏幕设定 | - -## 服务 - -### midea_ac_lan.set_attribute - -[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) - -设置设备属性, 服务数据: - -| 名称 | 描述 | -|-----------|-------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "disinfect"
"prompt_tone"
"power" | -| value | true 或 false | - -| 名称 | 描述 | -|-----------|-----------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "fan_speed" | -| value | "Lowest"
"Low"
"Medium"
"High"
"Auto"
"Off" | - -| 名称 | 描述 | -|-----------|------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "screen_display" | -| value | "Bright"
"Dim"
"Off" | - -示例 - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: prompt_tone - value: true -``` - -```yaml -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: fan_speed - value: Medium -``` +# 加湿器 + +## 特性 + +- 支持运行模式 +- 支持风扇模式设定 +- 支持湿度设定 + +## 生成实体 + +### 默认生成实体 + +| 实体ID | 类型 | 描述 | +|----------------------------------|------------|-------| +| humidifier.{DEVICEID}_humidifier | humidifier | 加湿器实体 | + +### 额外生成实体 + +| EntityID | 类型 | 名称 | 描述 | +|---------------------------------------|--------|---------------------|------| +| sensor.{DEVICEID}_current_humidity | sensor | Current Humidity | 当前湿度 | +| sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | +| switch.{DEVICEID}_disinfect | switch | Disinfect | 消毒开关 | +| switch.{DEVICEID}_prompt_tone | switch | Prompt Tone | 提示音 | +| switch.{DEVICEID}_power | switch | Power | 电源开关 | +| select.{DEVICEID}_fan_speed | select | Fan Speed | 风速设定 | +| select.{DEVICEID}_screen_display | select | Screen Display | 屏幕设定 | + +## 服务 + +### midea_ac_lan.set_attribute + +[![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) + +设置设备属性, 服务数据: + +| 名称 | 描述 | +|-----------|-------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "disinfect"
"prompt_tone"
"power" | +| value | true 或 false | + +| 名称 | 描述 | +|-----------|-----------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "fan_speed" | +| value | "Lowest"
"Low"
"Medium"
"High"
"Auto"
"Off" | + +| 名称 | 描述 | +|-----------|------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "screen_display" | +| value | "Bright"
"Dim"
"Off" | + +示例 + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: prompt_tone + value: true +``` + +```yaml +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: fan_speed + value: Medium +``` From 569e37042f52cd583921741a49881812022e4084 Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Tue, 14 May 2024 17:35:14 +0800 Subject: [PATCH 08/14] update with superlinter error --- .github/linters/.python-lint | 2 +- .github/workflows/release.yml | 33 ++-- README.md | 21 ++- README_hans.md | 18 ++- custom_components/midea_ac_lan/climate.py | 17 ++- custom_components/midea_ac_lan/config_flow.py | 141 +++++++++--------- custom_components/midea_ac_lan/humidifier.py | 3 + .../midea_ac_lan/midea/core/discover.py | 21 ++- 8 files changed, 148 insertions(+), 108 deletions(-) diff --git a/.github/linters/.python-lint b/.github/linters/.python-lint index d911cb0f..a348dd17 100644 --- a/.github/linters/.python-lint +++ b/.github/linters/.python-lint @@ -1,4 +1,4 @@ [FORMAT] max-line-length=500 [MESSAGES CONTROL] -disable=import-error, logging-fstring-interpolation +disable=import-error, logging-fstring-interpolation, missing-class-docstring diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7962bcc3..6b2dc879 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,29 +24,28 @@ jobs: clean: true - name: Zip release file run: | - apt update && apt install zip || exit 1 - echo 'show runner hostname' + apt-get update && apt-get install zip || exit 1 + echo "show runner hostname" hostname - echo 'show runner user' + echo "show runner user" whoami - echo 'show runner pwd' + echo "show runner pwd" pwd - echo 'show runner kernel' + echo "show runner kernel" uname -a - echo 'show runner pwd file list' + echo "show runner pwd file list" ls ls -alht "custom_components/midea_ac_lan/" || exit 1 - echo 'show manifest.json for debug' + echo "show manifest.json for debug" cat "custom_components/midea_ac_lan/manifest.json" || exit 1 dst_dir="/github/workspace/artifacts" sudo mkdir -p "$dst_dir" || exit 1 sudo chown -R $(id -u):$(id -g) "$dst_dir" || exit 1 sudo chmod -R 755 "$dst_dir" || exit 1 cd "custom_components/midea_ac_lan/" || exit 1 - zip -r ../midea_ac_lan.zip ./* || exit 1 - cp ../midea_ac_lan.zip "$dst_dir/midea_ac_lan.zip" || exit 1 + zip -r "../midea_ac_lan.zip" "./*" || exit 1 + cp "../midea_ac_lan.zip" "$dst_dir/midea_ac_lan.zip" || exit 1 ls -alht "$dst_dir" || exit 1 - shell: bash - name: Upload Artifacts uses: actions/upload-artifact@v4 with: @@ -71,20 +70,20 @@ jobs: - name: Show Working Directory For Debug Purpose run: | - echo 'show runner hostname' + echo "show runner hostname" hostname - echo 'show runner user' + echo "show runner user" whoami - echo 'show runner disk usage' + echo "show runner disk usage" df -h - echo 'show runner pwd' + echo "show runner pwd" pwd - echo 'show runner kernel' + echo "show runner kernel" uname -a - echo 'show runner pwd file list' + echo "show runner pwd file list" ls ls -alht - echo 'show runner artifacts' + echo "show runner artifacts" ls -alht artifacts || exit 0 echo "github.ref: ${{github.ref}}" diff --git a/README.md b/README.md index 9e220823..b9643212 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@ [![Stable](https://img.shields.io/github/v/release/CyrielRct/midea_ac_lan)](https://github.com/CyrielRct/midea_ac_lan/releases/latest) [![Super-Linter](https://github.com///actions/workflows//badge.svg)](https://github.com/marketplace/actions/super-linter) -> :warning: **This is a fork of midea_ac_lan done by Georgezhao**: As the project is in a vegetative state we have done a fork and merged some pending fixes. -I'm trying to get in touch with the maintainer at the moment to find a solution, and at the same time we're looking for people available to maintain the project, which is actively used by the community. please contact me if you can help ! +> :warning: **This is a fork of midea_ac_lan done by Georgezhao** English | [简体中文](README_hans.md) @@ -76,9 +75,23 @@ And more. ## Installation -Search 'Midea AC LAN' in HACS and install, or copy all files in `custom_components/midea_ac_lan` from [Latest Release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest) to your `/custom_components/midea_ac_lan` in Home Assistant manually. +**Search `Midea AC LAN` in HACS not available now, it will be ready later** -Restart Home Assistant. +Please use manual install as below: + +Option 1: + +1. make sure you have installed HACS to Home Assistant [HACS install guide](https://hacs.xyz/docs/setup/download) +2. open HACS, click [Custom repositories], Repository input: `https://github.com/wuwentao/midea_ac_lan`, Category select [Integration] +3. **Restart Home Assistant**. + +Option 2: + +1. Download `midea_ac_lan.zip` from [Latest Release](https://github.com/wuwentao/midea_ac_lan/releases/latest) +2. copy `midea_ac_lan.zip` to `/custom_components/midea_ac_lan` in Home Assistant. +3. **Restart Home Assistant**. + +Once it done, open `[Settings]`, `[Device & services]`, `[Integrations]`, `[Midea AC Lan]`, do init config and add all your devices. ## Add device diff --git a/README_hans.md b/README_hans.md index 407c6ccf..7cca46cd 100644 --- a/README_hans.md +++ b/README_hans.md @@ -71,9 +71,23 @@ ## 安装 -在HACS中搜索'Midea AC LAN'并安装, 或者从[Latest release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest)下载最新的Release版本, 将其中的`custom_components/midea_ac_lan`放到你的Home Assistant的`custom_components/midea_ac_lan`中。 +**在HACS中搜索'Midea AC LAN'并安装的方式当前还不可用**,请耐心等待HACS审核和处理流程。 -重启Home Assistant +请先使用以下二种方式手工安装: + +方式1: + +1. 确保Home Assistant中已安装HACS [HACS install docs](https://hacs.xyz/docs/setup/download) +2. 打开HACS, 点击[Custom repositories], Repository 输入: `https://github.com/wuwentao/midea_ac_lan`, Category 选择 [Integration] +3. **重启Home Assistant**. + +方式2: + +1. Download midea_ac_lan.zip from [Latest Release](https://github.com/wuwentao/midea_ac_lan/releases/latest) +2. copy midea_ac_lan.zip to `/custom_components/midea_ac_lan` in Home Assistant. +3. **Restart Home Assistant**. + +Once it done, open [Settings], [Device & services], [Integrations], [Midea AC Lan], do init config and add all your devices. ## 添加设备 diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py index 6ae9dddf..46604d49 100644 --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -1,3 +1,6 @@ +""" +climate.py +""" import logging from homeassistant.components.climate import ( @@ -357,16 +360,15 @@ def fan_mode(self) -> str: fan_speed = self._device.get_attribute(ACAttributes.fan_speed) if fan_speed > 100: return FAN_AUTO.capitalize() - elif fan_speed > 80: + if fan_speed > 80: return FAN_FULL_SPEED.capitalize() - elif fan_speed > 60: + if fan_speed > 60: return FAN_HIGH.capitalize() - elif fan_speed > 40: + if fan_speed > 40: return FAN_MEDIUM.capitalize() - elif fan_speed > 20: + if fan_speed > 20: return FAN_LOW.capitalize() - else: - return FAN_SILENT.capitalize() + return FAN_SILENT.capitalize() @property def target_temperature_step(self): @@ -635,8 +637,7 @@ def hvac_mode(self) -> str: """ if self._device.get_attribute(self._power_attr): return self._modes[self._device.get_attribute(C3Attributes.mode)] - else: - return HVACMode.OFF + return HVACMode.OFF @property def target_temperature(self): diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py index 812f0135..1b3fac9b 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -3,6 +3,7 @@ """ import os +import logging import voluptuous as vol @@ -11,8 +12,6 @@ except ImportError: from homeassistant.util.json import save_json -import logging - import homeassistant.helpers.config_validation as cv from homeassistant import config_entries from homeassistant.const import ( @@ -177,11 +176,10 @@ async def async_step_user(self, user_input=None, error=None): if user_input is not None: if user_input["action"] == "discovery": return await self.async_step_discovery() - elif user_input["action"] == "manually": + if user_input["action"] == "manually": self.found_device = {} return await self.async_step_manually() - else: - return await self.async_step_list() + return await self.async_step_list() return self.async_show_form( step_id="user", data_schema=vol.Schema( @@ -212,8 +210,7 @@ async def async_step_login(self, user_input=None, error=None): } self._save_account(self.account) return await self.async_step_auto() - else: - return await self.async_step_login(error="login_failed") + return await self.async_step_login(error="login_failed") return self.async_show_form( step_id="login", data_schema=vol.Schema( @@ -268,8 +265,7 @@ async def async_step_discovery(self, user_input=None, error=None): ) if len(self.available_device) > 0: return await self.async_step_auto() - else: - return await self.async_step_discovery(error="no_devices") + return await self.async_step_discovery(error="no_devices") return self.async_show_form( step_id="discovery", data_schema=vol.Schema( @@ -303,73 +299,71 @@ async def async_step_auto(self, user_input=None, error=None): f"Loaded configuration for device {device_id} from storage" ) return await self.async_step_manually() - else: + if CONF_ACCOUNT not in self.account.keys(): + self.account = self._load_account() if CONF_ACCOUNT not in self.account.keys(): - self.account = self._load_account() - if CONF_ACCOUNT not in self.account.keys(): - return await self.async_step_login() - if self.session is None: - self.session = async_create_clientsession(self.hass) - if self.cloud is None: + return await self.async_step_login() + if self.session is None: + self.session = async_create_clientsession(self.hass) + if self.cloud is None: + self.cloud = get_midea_cloud( + self.account[CONF_SERVER], + self.session, + self.account[CONF_ACCOUNT], + self.account[CONF_PASSWORD], + ) + if not await self.cloud.login(): + return await self.async_step_login() + self.found_device = { + CONF_DEVICE_ID: device_id, + CONF_TYPE: device.get(CONF_TYPE), + CONF_PROTOCOL: device.get(CONF_PROTOCOL), + CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), + CONF_PORT: device.get(CONF_PORT), + CONF_MODEL: device.get(CONF_MODEL), + } + if device_info := await self.cloud.get_device_info(device_id): + self.found_device[CONF_NAME] = device_info.get("name") + self.found_device[CONF_SUBTYPE] = device_info.get("model_number") + if device.get(CONF_PROTOCOL) == 3: + if self.account[CONF_SERVER] == "美的美居": + _LOGGER.debug( + "Try to get the Token and the Key use the preset MSmartHome account" + ) self.cloud = get_midea_cloud( - self.account[CONF_SERVER], + "MSmartHome", self.session, - self.account[CONF_ACCOUNT], - self.account[CONF_PASSWORD], + bytes.fromhex( + format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[1]), "X") + ).decode("ASCII"), + bytes.fromhex( + format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[2]), "X") + ).decode("ASCII"), ) - if not await self.cloud.login(): - return await self.async_step_login() - self.found_device = { - CONF_DEVICE_ID: device_id, - CONF_TYPE: device.get(CONF_TYPE), - CONF_PROTOCOL: device.get(CONF_PROTOCOL), - CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), - CONF_PORT: device.get(CONF_PORT), - CONF_MODEL: device.get(CONF_MODEL), - } - if device_info := await self.cloud.get_device_info(device_id): - self.found_device[CONF_NAME] = device_info.get("name") - self.found_device[CONF_SUBTYPE] = device_info.get("model_number") - if device.get(CONF_PROTOCOL) == 3: - if self.account[CONF_SERVER] == "美的美居": - _LOGGER.debug( - "Try to get the Token and the Key use the preset MSmartHome account" - ) - self.cloud = get_midea_cloud( - "MSmartHome", - self.session, - bytes.fromhex( - format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[1]), "X") - ).decode("ASCII"), - bytes.fromhex( - format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[2]), "X") - ).decode("ASCII"), - ) - if not await self.cloud.login(): - return await self.async_step_auto(error="preset_account") - keys = await self.cloud.get_keys(user_input[CONF_DEVICE]) - for method, key in keys.items(): - dm = MiedaDevice( - name="", - device_id=device_id, - device_type=device.get(CONF_TYPE), - ip_address=device.get(CONF_IP_ADDRESS), - port=device.get(CONF_PORT), - token=key["token"], - key=key["key"], - protocol=3, - model=device.get(CONF_MODEL), - subtype=0, - attributes={}, - ) - if dm.connect(refresh_status=False): - dm.close_socket() - self.found_device[CONF_TOKEN] = key["token"] - self.found_device[CONF_KEY] = key["key"] - return await self.async_step_manually() - return await self.async_step_auto(error="connect_error") - else: - return await self.async_step_manually() + if not await self.cloud.login(): + return await self.async_step_auto(error="preset_account") + keys = await self.cloud.get_keys(user_input[CONF_DEVICE]) + for method, key in keys.items(): + dm = MiedaDevice( + name="", + device_id=device_id, + device_type=device.get(CONF_TYPE), + ip_address=device.get(CONF_IP_ADDRESS), + port=device.get(CONF_PORT), + token=key["token"], + key=key["key"], + protocol=3, + model=device.get(CONF_MODEL), + subtype=0, + attributes={}, + ) + if dm.connect(refresh_status=False): + dm.close_socket() + self.found_device[CONF_TOKEN] = key["token"] + self.found_device[CONF_KEY] = key["key"] + return await self.async_step_manually() + return await self.async_step_auto(error="connect_error") + return await self.async_step_manually() return self.async_show_form( step_id="auto", @@ -438,8 +432,7 @@ async def async_step_manually(self, user_input=None, error=None): return self.async_create_entry( title=f"{user_input[CONF_NAME]}", data=data ) - else: - return await self.async_step_manually(error="config_incorrect") + return await self.async_step_manually(error="config_incorrect") return self.async_show_form( step_id="manually", data_schema=vol.Schema( diff --git a/custom_components/midea_ac_lan/humidifier.py b/custom_components/midea_ac_lan/humidifier.py index 1027b585..8a4d126e 100644 --- a/custom_components/midea_ac_lan/humidifier.py +++ b/custom_components/midea_ac_lan/humidifier.py @@ -1,3 +1,6 @@ +""" +humidifier.py +""" import logging from homeassistant.components.humidifier import ( diff --git a/custom_components/midea_ac_lan/midea/core/discover.py b/custom_components/midea_ac_lan/midea/core/discover.py index 0deb1790..c4235532 100644 --- a/custom_components/midea_ac_lan/midea/core/discover.py +++ b/custom_components/midea_ac_lan/midea/core/discover.py @@ -1,3 +1,6 @@ +""" +discover.py +""" import logging import socket from ipaddress import IPv4Network @@ -153,6 +156,9 @@ def discover(discover_type=None, ip_address=None): + """ + discover + """ if discover_type is None: discover_type = [] security = LocalSecurity() @@ -242,17 +248,22 @@ def discover(discover_type=None, ip_address=None): def get_id_from_response(response): + """ + get_id_from_response + """ if response[64:-16][:6].hex() == "3c3f786d6c20": xml = response[64:-16] root = ET.fromstring(xml.decode(encoding="utf-8", errors="replace")) child = root.find("smartDevice") m = child.attrib return int.from_bytes(bytearray.fromhex(m["devId"]), "little") - else: - return 0 + return 0 def bytes2port(paramArrayOfbyte): + """ + bytes2port + """ if paramArrayOfbyte is None: return 0 b, i = 0, 0 @@ -267,6 +278,9 @@ def bytes2port(paramArrayOfbyte): def get_device_info(device_ip, device_port: int): + """ + get_device_info + """ response = bytearray(0) try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: @@ -289,6 +303,9 @@ def get_device_info(device_ip, device_port: int): def enum_all_broadcast(): + """ + enum_all_broadcast + """ nets = [] adapters = ifaddr.get_adapters() for adapter in adapters: From c4d78141162598e498d6efed359d8f7cae0b80c0 Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Wed, 15 May 2024 15:08:06 +0800 Subject: [PATCH 09/14] update with superlinter error --- .github/ISSUE_TEMPLATE/1-issue_zh-cn.yml | 2 +- .github/ISSUE_TEMPLATE/2-issue_en.yml | 2 +- .../ISSUE_TEMPLATE/3-enhancement_zh-cn.yml | 2 +- .github/ISSUE_TEMPLATE/4-enhancement_en.yml | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/linters/.eslintrc.yml | 7 +++++ .github/linters/.python-lint | 2 +- .github/pull_request_template.md | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/linter.yml | 4 ++- .github/workflows/release.yml | 7 +++-- .github/workflows/validate.yml | 2 +- README_hans.md | 14 +++++----- custom_components/midea_ac_lan/climate.py | 28 +++++++++++++++++-- custom_components/midea_ac_lan/config_flow.py | 15 +++++----- custom_components/midea_ac_lan/humidifier.py | 1 + .../midea/backports/{enum.py => myenum.py} | 0 .../midea_ac_lan/midea/core/device.py | 2 +- .../midea_ac_lan/midea/core/discover.py | 1 + .../midea_ac_lan/midea/devices/a1/device.py | 2 +- .../midea_ac_lan/midea/devices/ac/device.py | 2 +- .../midea_ac_lan/midea/devices/b0/device.py | 2 +- .../midea_ac_lan/midea/devices/b1/device.py | 2 +- .../midea_ac_lan/midea/devices/b3/device.py | 2 +- .../midea_ac_lan/midea/devices/b4/device.py | 2 +- .../midea_ac_lan/midea/devices/b6/device.py | 2 +- .../midea_ac_lan/midea/devices/bf/device.py | 2 +- .../midea_ac_lan/midea/devices/c2/device.py | 2 +- .../midea_ac_lan/midea/devices/c3/device.py | 2 +- .../midea_ac_lan/midea/devices/ca/device.py | 2 +- .../midea_ac_lan/midea/devices/cc/device.py | 2 +- .../midea_ac_lan/midea/devices/cd/device.py | 2 +- .../midea_ac_lan/midea/devices/ce/device.py | 2 +- .../midea_ac_lan/midea/devices/cf/device.py | 2 +- .../midea_ac_lan/midea/devices/da/device.py | 2 +- .../midea_ac_lan/midea/devices/db/device.py | 2 +- .../midea_ac_lan/midea/devices/dc/device.py | 2 +- .../midea_ac_lan/midea/devices/e1/device.py | 2 +- .../midea_ac_lan/midea/devices/e2/device.py | 2 +- .../midea_ac_lan/midea/devices/e3/device.py | 2 +- .../midea_ac_lan/midea/devices/e6/device.py | 2 +- .../midea_ac_lan/midea/devices/e8/device.py | 2 +- .../midea_ac_lan/midea/devices/ea/device.py | 2 +- .../midea_ac_lan/midea/devices/ec/device.py | 2 +- .../midea_ac_lan/midea/devices/ed/device.py | 2 +- .../midea_ac_lan/midea/devices/fa/device.py | 2 +- .../midea_ac_lan/midea/devices/fb/device.py | 2 +- .../midea_ac_lan/midea/devices/fc/device.py | 2 +- .../midea_ac_lan/midea/devices/fd/device.py | 2 +- .../midea_ac_lan/midea/devices/x13/device.py | 2 +- .../midea_ac_lan/midea/devices/x26/device.py | 2 +- .../midea_ac_lan/midea/devices/x34/device.py | 2 +- .../midea_ac_lan/midea/devices/x40/device.py | 2 +- scripts/setup.sh | 6 ++-- 54 files changed, 103 insertions(+), 68 deletions(-) create mode 100755 .github/linters/.eslintrc.yml rename custom_components/midea_ac_lan/midea/backports/{enum.py => myenum.py} (100%) diff --git a/.github/ISSUE_TEMPLATE/1-issue_zh-cn.yml b/.github/ISSUE_TEMPLATE/1-issue_zh-cn.yml index bef73140..dd9f4458 100644 --- a/.github/ISSUE_TEMPLATE/1-issue_zh-cn.yml +++ b/.github/ISSUE_TEMPLATE/1-issue_zh-cn.yml @@ -35,4 +35,4 @@ body: - type: textarea attributes: label: The logs - description: 打开Midea AC LAN的调试日志,并将发生错误时的日志发送或上传到这里 \ No newline at end of file + description: 打开Midea AC LAN的调试日志,并将发生错误时的日志发送或上传到这里 diff --git a/.github/ISSUE_TEMPLATE/2-issue_en.yml b/.github/ISSUE_TEMPLATE/2-issue_en.yml index e90ecfd7..cd6643a7 100644 --- a/.github/ISSUE_TEMPLATE/2-issue_en.yml +++ b/.github/ISSUE_TEMPLATE/2-issue_en.yml @@ -37,4 +37,4 @@ body: attributes: label: The logs description: Should enable the debug log of Midea AC LAN, and post or \ - upload the log when the error occurs to here \ No newline at end of file + upload the log when the error occurs to here diff --git a/.github/ISSUE_TEMPLATE/3-enhancement_zh-cn.yml b/.github/ISSUE_TEMPLATE/3-enhancement_zh-cn.yml index a1c2fc27..5b4d0a24 100644 --- a/.github/ISSUE_TEMPLATE/3-enhancement_zh-cn.yml +++ b/.github/ISSUE_TEMPLATE/3-enhancement_zh-cn.yml @@ -18,4 +18,4 @@ body: validations: required: true attributes: - label: 新功能的描述 \ No newline at end of file + label: 新功能的描述 diff --git a/.github/ISSUE_TEMPLATE/4-enhancement_en.yml b/.github/ISSUE_TEMPLATE/4-enhancement_en.yml index b30f84cb..03f27acf 100644 --- a/.github/ISSUE_TEMPLATE/4-enhancement_en.yml +++ b/.github/ISSUE_TEMPLATE/4-enhancement_en.yml @@ -18,4 +18,4 @@ body: validations: required: true attributes: - label: The description of new feature \ No newline at end of file + label: The description of new feature diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index c96a9ab2..341f6afa 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,2 +1,2 @@ --- -blank_issues_enabled: true \ No newline at end of file +blank_issues_enabled: true diff --git a/.github/linters/.eslintrc.yml b/.github/linters/.eslintrc.yml new file mode 100755 index 00000000..9f1876de --- /dev/null +++ b/.github/linters/.eslintrc.yml @@ -0,0 +1,7 @@ +--- +plugins: + - json +rules: + json/*: + - error + - allowComments: true diff --git a/.github/linters/.python-lint b/.github/linters/.python-lint index a348dd17..4fae9dfb 100644 --- a/.github/linters/.python-lint +++ b/.github/linters/.python-lint @@ -1,4 +1,4 @@ [FORMAT] max-line-length=500 [MESSAGES CONTROL] -disable=import-error, logging-fstring-interpolation, missing-class-docstring +disable=import-error, logging-fstring-interpolation, missing-class-docstring,missing-function-docstring diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 448d0a6d..3cc9bda0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ -# PR Brief Description +# PR Description ## Reason & Detail diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 225d7229..5b7bed34 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -9,7 +9,7 @@ on: # schedule: # - cron: "42 8 * * 6" -permissions: { } +permissions: {} jobs: analyze: diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 224a97bd..514633c3 100755 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -9,7 +9,7 @@ on: branches: - master -permissions: { } +permissions: {} jobs: build: @@ -37,3 +37,5 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_GITLEAKS: false VALIDATE_JSCPD: false + VALIDATE_PYTHON_PYLINT: false + VALIDATE_PYTHON_MYPY: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b2dc879..03255769 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: release: types: [released] -permissions: { } +permissions: {} defaults: run: @@ -24,7 +24,8 @@ jobs: clean: true - name: Zip release file run: | - apt-get update && apt-get install zip || exit 1 + apt-get update || exit 1 + apt-get install zip || exit 1 echo "show runner hostname" hostname echo "show runner user" @@ -40,7 +41,7 @@ jobs: cat "custom_components/midea_ac_lan/manifest.json" || exit 1 dst_dir="/github/workspace/artifacts" sudo mkdir -p "$dst_dir" || exit 1 - sudo chown -R $(id -u):$(id -g) "$dst_dir" || exit 1 + sudo chown -R "$(id -u):$(id -g)" "$dst_dir" || exit 1 sudo chmod -R 755 "$dst_dir" || exit 1 cd "custom_components/midea_ac_lan/" || exit 1 zip -r "../midea_ac_lan.zip" "./*" || exit 1 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index cbb7c6da..a2bd38b9 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -5,7 +5,7 @@ on: # schedule: # - cron: "0 0 * * *" -permissions: { } +permissions: {} jobs: validate: diff --git a/README_hans.md b/README_hans.md index 7cca46cd..e23b0a0e 100644 --- a/README_hans.md +++ b/README_hans.md @@ -71,23 +71,23 @@ ## 安装 -**在HACS中搜索'Midea AC LAN'并安装的方式当前还不可用**,请耐心等待HACS审核和处理流程。 +**在HACS中搜索`Midea AC LAN`并安装的方式当前还不可用**,请耐心等待HACS审核和处理流程。 请先使用以下二种方式手工安装: 方式1: -1. 确保Home Assistant中已安装HACS [HACS install docs](https://hacs.xyz/docs/setup/download) -2. 打开HACS, 点击[Custom repositories], Repository 输入: `https://github.com/wuwentao/midea_ac_lan`, Category 选择 [Integration] +1. 确保`Home Assistant`中已安装HACS [HACS install docs](https://hacs.xyz/docs/setup/download) +2. 打开`HACS`, 点击`[Custom repositories]`, `Repository` 输入: `https://github.com/wuwentao/midea_ac_lan`, `Category` 选择 `[Integration]` 3. **重启Home Assistant**. 方式2: -1. Download midea_ac_lan.zip from [Latest Release](https://github.com/wuwentao/midea_ac_lan/releases/latest) -2. copy midea_ac_lan.zip to `/custom_components/midea_ac_lan` in Home Assistant. -3. **Restart Home Assistant**. +1. 从[Latest Release](https://github.com/wuwentao/midea_ac_lan/releases/latest) 下载 `midea_ac_lan.zip` +2. 复制 `midea_ac_lan.zip` 到 `/custom_components/midea_ac_lan`. +3. **重启 Home Assistant**. -Once it done, open [Settings], [Device & services], [Integrations], [Midea AC Lan], do init config and add all your devices. +重启完成后, 打开 `[Settings]`, `[Device & services]`, `[Integrations]`, `[Midea AC Lan]`, 进行初始化设置并添加所有设备. ## 添加设备 diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py index 46604d49..5f779565 100644 --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -1,6 +1,7 @@ """ climate.py """ + import logging from homeassistant.components.climate import ( @@ -242,7 +243,14 @@ def set_temperature(self, **kwargs) -> None: """ if ATTR_TEMPERATURE not in kwargs: return - temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 + # get temperature_value + temperature_value = kwargs.get(ATTR_TEMPERATURE) + # check temperature_value is not None + if temperature_value is not None: + temperature = float(int((float(temperature_value) * 2) + 0.5)) / 2 + else: + # if temperature_value None,set default value to 0.0 + temperature = 0.0 hvac_mode = kwargs.get(ATTR_HVAC_MODE) if hvac_mode == HVACMode.OFF: self.turn_off() @@ -659,7 +667,14 @@ def set_temperature(self, **kwargs) -> None: """ if ATTR_TEMPERATURE not in kwargs: return - temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 + # get temperature_value + temperature_value = kwargs.get(ATTR_TEMPERATURE) + # check temperature_value is not None + if temperature_value is not None: + temperature = float(int((float(temperature_value) * 2) + 0.5)) / 2 + else: + # if temperature_value None,set default value to 0.0 + temperature = 0.0 hvac_mode = kwargs.get(ATTR_HVAC_MODE) if hvac_mode == HVACMode.OFF: self.turn_off() @@ -781,7 +796,14 @@ def set_temperature(self, **kwargs) -> None: """ if ATTR_TEMPERATURE not in kwargs: return - temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 + # get temperature_value + temperature_value = kwargs.get(ATTR_TEMPERATURE) + # check temperature_value is not None + if temperature_value is not None: + temperature = float(int((float(temperature_value) * 2) + 0.5)) / 2 + else: + # if temperature_value None,set default value to 0.0 + temperature = 0.0 hvac_mode = kwargs.get(ATTR_HVAC_MODE) if hvac_mode == HVACMode.OFF: self.turn_off() diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py index 1b3fac9b..ea35c73c 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -2,8 +2,9 @@ config_flow.py """ -import os import logging +import os +from typing import Any, Dict, List import voluptuous as vol @@ -78,12 +79,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ConfigFlow """ - available_device = [] - devices = {} - found_device = {} - supports = {} - unsorted = {} - account = {} + available_device: List[Any] = [] + devices: Dict[Any, Any] = {} + found_device: Dict[Any, Any] = {} + supports: Dict[Any, Any] = {} + unsorted: Dict[Any, Any] = {} + account: Dict[Any, Any] = {} cloud = None session = None for device_type, device_info in MIDEA_DEVICES.items(): diff --git a/custom_components/midea_ac_lan/humidifier.py b/custom_components/midea_ac_lan/humidifier.py index 8a4d126e..633a9473 100644 --- a/custom_components/midea_ac_lan/humidifier.py +++ b/custom_components/midea_ac_lan/humidifier.py @@ -1,6 +1,7 @@ """ humidifier.py """ + import logging from homeassistant.components.humidifier import ( diff --git a/custom_components/midea_ac_lan/midea/backports/enum.py b/custom_components/midea_ac_lan/midea/backports/myenum.py similarity index 100% rename from custom_components/midea_ac_lan/midea/backports/enum.py rename to custom_components/midea_ac_lan/midea/backports/myenum.py diff --git a/custom_components/midea_ac_lan/midea/core/device.py b/custom_components/midea_ac_lan/midea/core/device.py index dc2f2c07..3509f616 100644 --- a/custom_components/midea_ac_lan/midea/core/device.py +++ b/custom_components/midea_ac_lan/midea/core/device.py @@ -3,7 +3,7 @@ try: from enum import StrEnum except ImportError: - from ..backports.enum import StrEnum + from ..backports.myenum import StrEnum import logging import socket diff --git a/custom_components/midea_ac_lan/midea/core/discover.py b/custom_components/midea_ac_lan/midea/core/discover.py index c4235532..c8005f6c 100644 --- a/custom_components/midea_ac_lan/midea/core/discover.py +++ b/custom_components/midea_ac_lan/midea/core/discover.py @@ -1,6 +1,7 @@ """ discover.py """ + import logging import socket from ipaddress import IPv4Network diff --git a/custom_components/midea_ac_lan/midea/devices/a1/device.py b/custom_components/midea_ac_lan/midea/devices/a1/device.py index 856b479b..cbff7675 100644 --- a/custom_components/midea_ac_lan/midea/devices/a1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/a1/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/ac/device.py b/custom_components/midea_ac_lan/midea/devices/ac/device.py index 62e2e384..87ccb1c2 100644 --- a/custom_components/midea_ac_lan/midea/devices/ac/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ac/device.py @@ -16,7 +16,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/b0/device.py b/custom_components/midea_ac_lan/midea/devices/b0/device.py index 8ba25bc3..8de17bf5 100644 --- a/custom_components/midea_ac_lan/midea/devices/b0/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b0/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/b1/device.py b/custom_components/midea_ac_lan/midea/devices/b1/device.py index e9f45cc9..9b78b67c 100644 --- a/custom_components/midea_ac_lan/midea/devices/b1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b1/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/b3/device.py b/custom_components/midea_ac_lan/midea/devices/b3/device.py index f19e6071..8894dd19 100644 --- a/custom_components/midea_ac_lan/midea/devices/b3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b3/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/b4/device.py b/custom_components/midea_ac_lan/midea/devices/b4/device.py index 07a3068a..72824244 100644 --- a/custom_components/midea_ac_lan/midea/devices/b4/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b4/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/b6/device.py b/custom_components/midea_ac_lan/midea/devices/b6/device.py index 00147b08..88b50c12 100644 --- a/custom_components/midea_ac_lan/midea/devices/b6/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b6/device.py @@ -6,7 +6,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/bf/device.py b/custom_components/midea_ac_lan/midea/devices/bf/device.py index 7c9e3639..d8fe2070 100644 --- a/custom_components/midea_ac_lan/midea/devices/bf/device.py +++ b/custom_components/midea_ac_lan/midea/devices/bf/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/c2/device.py b/custom_components/midea_ac_lan/midea/devices/c2/device.py index ef11240c..3e1a0444 100644 --- a/custom_components/midea_ac_lan/midea/devices/c2/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c2/device.py @@ -6,7 +6,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/c3/device.py b/custom_components/midea_ac_lan/midea/devices/c3/device.py index 150532b0..c3c252cd 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/device.py @@ -11,7 +11,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/ca/device.py b/custom_components/midea_ac_lan/midea/devices/ca/device.py index 2fd98d67..f169b4ff 100644 --- a/custom_components/midea_ac_lan/midea/devices/ca/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ca/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/cc/device.py b/custom_components/midea_ac_lan/midea/devices/cc/device.py index 803984cc..680a0ec9 100644 --- a/custom_components/midea_ac_lan/midea/devices/cc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cc/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/cd/device.py b/custom_components/midea_ac_lan/midea/devices/cd/device.py index 1fdfa998..a124f735 100644 --- a/custom_components/midea_ac_lan/midea/devices/cd/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cd/device.py @@ -6,7 +6,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/ce/device.py b/custom_components/midea_ac_lan/midea/devices/ce/device.py index dde6a1da..c8ac6ecf 100644 --- a/custom_components/midea_ac_lan/midea/devices/ce/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ce/device.py @@ -6,7 +6,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/cf/device.py b/custom_components/midea_ac_lan/midea/devices/cf/device.py index b9a291aa..40b5a9d5 100644 --- a/custom_components/midea_ac_lan/midea/devices/cf/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cf/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/da/device.py b/custom_components/midea_ac_lan/midea/devices/da/device.py index e67ed58a..f5506e40 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/device.py +++ b/custom_components/midea_ac_lan/midea/devices/da/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/db/device.py b/custom_components/midea_ac_lan/midea/devices/db/device.py index bdd69954..fe1c02e4 100644 --- a/custom_components/midea_ac_lan/midea/devices/db/device.py +++ b/custom_components/midea_ac_lan/midea/devices/db/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/dc/device.py b/custom_components/midea_ac_lan/midea/devices/dc/device.py index ad18390a..7e6d659f 100644 --- a/custom_components/midea_ac_lan/midea/devices/dc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/dc/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/e1/device.py b/custom_components/midea_ac_lan/midea/devices/e1/device.py index 62510255..43c72ffa 100644 --- a/custom_components/midea_ac_lan/midea/devices/e1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e1/device.py @@ -11,7 +11,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/e2/device.py b/custom_components/midea_ac_lan/midea/devices/e2/device.py index 9b919e7c..b261a44f 100644 --- a/custom_components/midea_ac_lan/midea/devices/e2/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e2/device.py @@ -12,7 +12,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/e3/device.py b/custom_components/midea_ac_lan/midea/devices/e3/device.py index 999a6276..250076d1 100644 --- a/custom_components/midea_ac_lan/midea/devices/e3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e3/device.py @@ -12,7 +12,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/e6/device.py b/custom_components/midea_ac_lan/midea/devices/e6/device.py index 5c2751c8..82368b40 100644 --- a/custom_components/midea_ac_lan/midea/devices/e6/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e6/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/e8/device.py b/custom_components/midea_ac_lan/midea/devices/e8/device.py index 3d98bf4e..813941a2 100644 --- a/custom_components/midea_ac_lan/midea/devices/e8/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e8/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/ea/device.py b/custom_components/midea_ac_lan/midea/devices/ea/device.py index 7e81a55c..722d12d8 100644 --- a/custom_components/midea_ac_lan/midea/devices/ea/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ea/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/ec/device.py b/custom_components/midea_ac_lan/midea/devices/ec/device.py index 554d8b88..a87e9955 100644 --- a/custom_components/midea_ac_lan/midea/devices/ec/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ec/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/ed/device.py b/custom_components/midea_ac_lan/midea/devices/ed/device.py index c758ddfb..a25872e5 100644 --- a/custom_components/midea_ac_lan/midea/devices/ed/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ed/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/fa/device.py b/custom_components/midea_ac_lan/midea/devices/fa/device.py index 9c9864de..38772c93 100644 --- a/custom_components/midea_ac_lan/midea/devices/fa/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fa/device.py @@ -6,7 +6,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/fb/device.py b/custom_components/midea_ac_lan/midea/devices/fb/device.py index b54565d6..c5ae1887 100644 --- a/custom_components/midea_ac_lan/midea/devices/fb/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fb/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/fc/device.py b/custom_components/midea_ac_lan/midea/devices/fc/device.py index f536e0d5..fad4f23d 100644 --- a/custom_components/midea_ac_lan/midea/devices/fc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fc/device.py @@ -6,7 +6,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/fd/device.py b/custom_components/midea_ac_lan/midea/devices/fd/device.py index f310119a..82dfc063 100644 --- a/custom_components/midea_ac_lan/midea/devices/fd/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fd/device.py @@ -5,7 +5,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/x13/device.py b/custom_components/midea_ac_lan/midea/devices/x13/device.py index ca8ad4b0..cc019d20 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/device.py @@ -6,7 +6,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/x26/device.py b/custom_components/midea_ac_lan/midea/devices/x26/device.py index 1a44c5e6..b01c67b5 100644 --- a/custom_components/midea_ac_lan/midea/devices/x26/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x26/device.py @@ -6,7 +6,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/x34/device.py b/custom_components/midea_ac_lan/midea/devices/x34/device.py index 6e6d8a03..76e8f303 100644 --- a/custom_components/midea_ac_lan/midea/devices/x34/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x34/device.py @@ -11,7 +11,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/custom_components/midea_ac_lan/midea/devices/x40/device.py b/custom_components/midea_ac_lan/midea/devices/x40/device.py index d4280383..6115e00c 100644 --- a/custom_components/midea_ac_lan/midea/devices/x40/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x40/device.py @@ -6,7 +6,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice diff --git a/scripts/setup.sh b/scripts/setup.sh index 692cc8b7..43421316 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -8,6 +8,6 @@ python3 -m pip install --requirement requirements.txt # Create config dir if not present if [[ ! -d "${PWD}/config" ]]; then - mkdir -p "${PWD}/config" - hass --config "${PWD}/config" --script ensure_config -fi \ No newline at end of file + mkdir -p "${PWD}/config" + hass --config "${PWD}/config" --script ensure_config +fi From ed2112438e902679a2faf13f6da5ebc758b4d323 Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Fri, 17 May 2024 18:08:11 +0800 Subject: [PATCH 10/14] revert master source code and recheck with linter --- .github/workflows/codeql.yml | 17 +- custom_components/midea_ac_lan/__init__.py | 121 +-- .../midea_ac_lan/binary_sensor.py | 31 +- custom_components/midea_ac_lan/climate.py | 422 ++------- custom_components/midea_ac_lan/config_flow.py | 606 +++++-------- custom_components/midea_ac_lan/const.py | 14 +- custom_components/midea_ac_lan/fan.py | 202 +---- custom_components/midea_ac_lan/humidifier.py | 31 +- custom_components/midea_ac_lan/light.py | 34 +- custom_components/midea_ac_lan/lock.py | 20 +- .../midea_ac_lan/midea/backports/myenum.py | 3 +- .../midea_ac_lan/midea/core/cloud.py | 396 ++++----- .../midea_ac_lan/midea/core/crc8.py | 288 +----- .../midea_ac_lan/midea/core/device.py | 102 +-- .../midea_ac_lan/midea/core/discover.py | 228 +---- .../midea_ac_lan/midea/core/message.py | 77 +- .../midea_ac_lan/midea/core/packet_builder.py | 82 +- .../midea_ac_lan/midea/core/security.py | 56 +- .../midea_ac_lan/midea/devices/__init__.py | 10 +- .../midea_ac_lan/midea/devices/a1/device.py | 86 +- .../midea_ac_lan/midea/devices/a1/message.py | 105 +-- .../midea_ac_lan/midea/devices/ac/device.py | 125 ++- .../midea_ac_lan/midea/devices/ac/message.py | 393 +++------ .../midea_ac_lan/midea/devices/b0/device.py | 45 +- .../midea_ac_lan/midea/devices/b0/message.py | 26 +- .../midea_ac_lan/midea/devices/b1/device.py | 45 +- .../midea_ac_lan/midea/devices/b1/message.py | 23 +- .../midea_ac_lan/midea/devices/b3/device.py | 42 +- .../midea_ac_lan/midea/devices/b3/message.py | 94 +- .../midea_ac_lan/midea/devices/b4/device.py | 45 +- .../midea_ac_lan/midea/devices/b4/message.py | 29 +- .../midea_ac_lan/midea/devices/b6/device.py | 72 +- .../midea_ac_lan/midea/devices/b6/message.py | 37 +- .../midea_ac_lan/midea/devices/bf/device.py | 45 +- .../midea_ac_lan/midea/devices/bf/message.py | 33 +- .../midea_ac_lan/midea/devices/c2/device.py | 56 +- .../midea_ac_lan/midea/devices/c2/message.py | 57 +- .../midea_ac_lan/midea/devices/c3/device.py | 112 +-- .../midea_ac_lan/midea/devices/c3/message.py | 106 ++- .../midea_ac_lan/midea/devices/ca/device.py | 35 +- .../midea_ac_lan/midea/devices/ca/message.py | 35 +- .../midea_ac_lan/midea/devices/cc/device.py | 95 +- .../midea_ac_lan/midea/devices/cc/message.py | 75 +- .../midea_ac_lan/midea/devices/cd/device.py | 52 +- .../midea_ac_lan/midea/devices/cd/message.py | 39 +- .../midea_ac_lan/midea/devices/ce/device.py | 46 +- .../midea_ac_lan/midea/devices/ce/message.py | 39 +- .../midea_ac_lan/midea/devices/cf/device.py | 36 +- .../midea_ac_lan/midea/devices/cf/message.py | 35 +- .../midea_ac_lan/midea/devices/da/device.py | 86 +- .../midea_ac_lan/midea/devices/da/message.py | 51 +- .../midea_ac_lan/midea/devices/db/device.py | 50 +- .../midea_ac_lan/midea/devices/db/message.py | 66 +- .../midea_ac_lan/midea/devices/dc/device.py | 49 +- .../midea_ac_lan/midea/devices/dc/message.py | 35 +- .../midea_ac_lan/midea/devices/e1/device.py | 80 +- .../midea_ac_lan/midea/devices/e1/message.py | 45 +- .../midea_ac_lan/midea/devices/e2/device.py | 54 +- .../midea_ac_lan/midea/devices/e2/message.py | 64 +- .../midea_ac_lan/midea/devices/e3/device.py | 58 +- .../midea_ac_lan/midea/devices/e3/message.py | 98 +-- .../midea_ac_lan/midea/devices/e6/device.py | 46 +- .../midea_ac_lan/midea/devices/e6/message.py | 25 +- .../midea_ac_lan/midea/devices/e8/device.py | 45 +- .../midea_ac_lan/midea/devices/e8/message.py | 20 +- .../midea_ac_lan/midea/devices/ea/device.py | 160 +--- .../midea_ac_lan/midea/devices/ea/message.py | 34 +- .../midea_ac_lan/midea/devices/ec/device.py | 180 +--- .../midea_ac_lan/midea/devices/ec/message.py | 31 +- .../midea_ac_lan/midea/devices/ed/device.py | 48 +- .../midea_ac_lan/midea/devices/ed/message.py | 44 +- .../midea_ac_lan/midea/devices/fa/device.py | 155 ++-- .../midea_ac_lan/midea/devices/fa/message.py | 128 +-- .../midea_ac_lan/midea/devices/fb/device.py | 48 +- .../midea_ac_lan/midea/devices/fb/message.py | 85 +- .../midea_ac_lan/midea/devices/fc/device.py | 109 +-- .../midea_ac_lan/midea/devices/fc/message.py | 86 +- .../midea_ac_lan/midea/devices/fd/device.py | 100 +-- .../midea_ac_lan/midea/devices/fd/message.py | 96 +- .../midea_ac_lan/midea/devices/x13/device.py | 61 +- .../midea_ac_lan/midea/devices/x13/message.py | 27 +- .../midea_ac_lan/midea/devices/x26/device.py | 67 +- .../midea_ac_lan/midea/devices/x26/message.py | 115 ++- .../midea_ac_lan/midea/devices/x34/device.py | 80 +- .../midea_ac_lan/midea/devices/x34/message.py | 45 +- .../midea_ac_lan/midea/devices/x40/device.py | 59 +- .../midea_ac_lan/midea/devices/x40/message.py | 111 ++- .../midea_ac_lan/midea_devices.py | 820 ++++++++++-------- .../midea_ac_lan/midea_entity.py | 20 +- custom_components/midea_ac_lan/number.py | 59 +- custom_components/midea_ac_lan/select.py | 20 +- custom_components/midea_ac_lan/sensor.py | 20 +- custom_components/midea_ac_lan/services.yaml | 3 +- custom_components/midea_ac_lan/switch.py | 20 +- .../midea_ac_lan/water_heater.py | 72 +- 95 files changed, 3287 insertions(+), 5291 deletions(-) mode change 100755 => 100644 custom_components/midea_ac_lan/__init__.py mode change 100755 => 100644 custom_components/midea_ac_lan/binary_sensor.py mode change 100755 => 100644 custom_components/midea_ac_lan/climate.py mode change 100755 => 100644 custom_components/midea_ac_lan/config_flow.py mode change 100755 => 100644 custom_components/midea_ac_lan/const.py mode change 100755 => 100644 custom_components/midea_ac_lan/fan.py mode change 100755 => 100644 custom_components/midea_ac_lan/humidifier.py mode change 100755 => 100644 custom_components/midea_ac_lan/lock.py mode change 100755 => 100644 custom_components/midea_ac_lan/midea/backports/myenum.py mode change 100755 => 100644 custom_components/midea_ac_lan/midea/core/cloud.py mode change 100755 => 100644 custom_components/midea_ac_lan/midea_devices.py mode change 100755 => 100644 custom_components/midea_ac_lan/midea_entity.py mode change 100755 => 100644 custom_components/midea_ac_lan/number.py mode change 100755 => 100644 custom_components/midea_ac_lan/select.py mode change 100755 => 100644 custom_components/midea_ac_lan/sensor.py mode change 100755 => 100644 custom_components/midea_ac_lan/services.yaml mode change 100755 => 100644 custom_components/midea_ac_lan/switch.py mode change 100755 => 100644 custom_components/midea_ac_lan/water_heater.py diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5b7bed34..98eb3686 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,16 +1,13 @@ ---- name: "CodeQL" on: push: - branches: ["master"] + branches: [ "master" ] pull_request: - branches: ["master"] + branches: [ "master" ] # schedule: # - cron: "42 8 * * 6" -permissions: {} - jobs: analyze: name: Analyze @@ -23,22 +20,22 @@ jobs: strategy: fail-fast: false matrix: - language: [python] + language: [ python ] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v2 with: category: "/language:${{ matrix.language }}" diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py old mode 100755 new mode 100644 index e5cfbf22..1125f120 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -1,54 +1,53 @@ -""" -__init__.py -""" - import logging - -import homeassistant.helpers.config_validation as cv import voluptuous as vol -from homeassistant.const import ( - CONF_CUSTOMIZE, - CONF_DEVICE_ID, - CONF_IP_ADDRESS, - CONF_NAME, - CONF_PORT, - CONF_PROTOCOL, - CONF_TOKEN, - CONF_TYPE, -) -from homeassistant.core import HomeAssistant - +import homeassistant.helpers.config_validation as cv from .const import ( - ALL_PLATFORM, + DOMAIN, CONF_ACCOUNT, CONF_KEY, CONF_MODEL, - CONF_REFRESH_INTERVAL, CONF_SUBTYPE, + CONF_REFRESH_INTERVAL, DEVICES, - DOMAIN, + EXTRA_SENSOR, EXTRA_SWITCH, + EXTRA_CONTROL, + ALL_PLATFORM, ) -from .midea.devices import async_device_selector from .midea_devices import MIDEA_DEVICES +from homeassistant.core import HomeAssistant +from homeassistant.const import ( + CONF_NAME, + CONF_TOKEN, + CONF_IP_ADDRESS, + CONF_PORT, + CONF_PROTOCOL, + CONF_DEVICE_ID, + CONF_TYPE, + CONF_CUSTOMIZE, +) +from .midea.devices import async_device_selector + _LOGGER = logging.getLogger(__name__) async def update_listener(hass, config_entry): - """ - update_listener - """ for platform in ALL_PLATFORM: await hass.config_entries.async_forward_entry_unload(config_entry, platform) for platform in ALL_PLATFORM: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, platform) - ) + hass.async_create_task(hass.config_entries.async_forward_entry_setup( + config_entry, platform)) device_id = config_entry.data.get(CONF_DEVICE_ID) - customize = config_entry.options.get(CONF_CUSTOMIZE, "") - ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) - refresh_interval = config_entry.options.get(CONF_REFRESH_INTERVAL, None) + customize = config_entry.options.get( + CONF_CUSTOMIZE, "" + ) + ip_address = config_entry.options.get( + CONF_IP_ADDRESS, None + ) + refresh_interval = config_entry.options.get( + CONF_REFRESH_INTERVAL, None + ) dev = hass.data[DOMAIN][DEVICES].get(device_id) if dev: dev.set_customize(customize) @@ -58,28 +57,15 @@ async def update_listener(hass, config_entry): dev.set_refresh_interval(refresh_interval) -async def async_setup(hass: HomeAssistant, config: dict): - """ - async_setup - """ - if config.get(DOMAIN) is None: - # We get her if the integration is set up using config flow - return True - +async def async_setup(hass: HomeAssistant, hass_config: dict): hass.data.setdefault(DOMAIN, {}) attributes = [] for device_entities in MIDEA_DEVICES.values(): for attribute_name, attribute in device_entities.get("entities").items(): - if ( - attribute.get("type") in EXTRA_SWITCH - and attribute_name.value not in attributes - ): + if attribute.get("type") in EXTRA_SWITCH and attribute_name.value not in attributes: attributes.append(attribute_name.value) def service_set_attribute(service): - """ - service_set_attribute - """ device_id = service.data.get("device_id") attr = service.data.get("attribute") value = service.data.get("value") @@ -88,34 +74,20 @@ def service_set_attribute(service): if attr == "fan_speed" and value == "auto": value = 102 item = MIDEA_DEVICES.get(dev.device_type).get("entities").get(attr) - if ( - item - and (item.get("type") in EXTRA_SWITCH) - or ( - dev.device_type == 0xAC - and attr == "fan_speed" - and value in range(0, 103) - ) - ): + if (item and (item.get("type") in EXTRA_SWITCH) or + (dev.device_type == 0xAC and attr == "fan_speed" and value in range(0, 103))): dev.set_attribute(attr=attr, value=value) else: - _LOGGER.error( - f"Appliance [{device_id}] has no attribute {attr} or value is invalid" - ) + _LOGGER.error(f"Appliance [{device_id}] has no attribute {attr} or value is invalid") def service_send_command(service): - """ - service_send_command - """ device_id = service.data.get("device_id") cmd_type = service.data.get("cmd_type") cmd_body = service.data.get("cmd_body") try: cmd_body = bytearray.fromhex(cmd_body) except ValueError: - _LOGGER.error( - f"Appliance [{device_id}] invalid cmd_body, a hexadecimal string required" - ) + _LOGGER.error(f"Appliance [{device_id}] invalid cmd_body, a hexadecimal string required") return dev = hass.data[DOMAIN][DEVICES].get(device_id) if dev: @@ -129,9 +101,9 @@ def service_send_command(service): { vol.Required("device_id"): vol.Coerce(int), vol.Required("attribute"): vol.In(attributes), - vol.Required("value"): vol.Any(int, cv.boolean, str), + vol.Required("value"): vol.Any(int, cv.boolean, str) } - ), + ) ) hass.services.async_register( @@ -142,17 +114,14 @@ def service_send_command(service): { vol.Required("device_id"): vol.Coerce(int), vol.Required("cmd_type"): vol.In([2, 3]), - vol.Required("cmd_body"): str, + vol.Required("cmd_body"): str } - ), + ) ) return True async def async_setup_entry(hass: HomeAssistant, config_entry): - """ - async_setup_entry - """ device_type = config_entry.data.get(CONF_TYPE) if device_type == CONF_ACCOUNT: return True @@ -161,7 +130,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry): if name is None: name = f"{device_id}" if device_type is None: - device_type = 0xAC + device_type = 0xac token = config_entry.data.get(CONF_TOKEN) key = config_entry.data.get(CONF_KEY) ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) @@ -200,18 +169,14 @@ async def async_setup_entry(hass: HomeAssistant, config_entry): hass.data[DOMAIN][DEVICES] = {} hass.data[DOMAIN][DEVICES][device_id] = device for platform in ALL_PLATFORM: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, platform) - ) + hass.async_create_task(hass.config_entries.async_forward_entry_setup( + config_entry, platform)) config_entry.add_update_listener(update_listener) return True return False async def async_unload_entry(hass: HomeAssistant, config_entry): - """ - async_unload_entry - """ device_type = config_entry.data.get(CONF_TYPE) if device_type == CONF_ACCOUNT: return True diff --git a/custom_components/midea_ac_lan/binary_sensor.py b/custom_components/midea_ac_lan/binary_sensor.py old mode 100755 new mode 100644 index 5bfcfca1..860ed591 --- a/custom_components/midea_ac_lan/binary_sensor.py +++ b/custom_components/midea_ac_lan/binary_sensor.py @@ -1,22 +1,19 @@ -""" -binary_sensor.py -""" - from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES +from homeassistant.const import Platform, CONF_DEVICE_ID, CONF_SENSORS +from .const import ( + DOMAIN, + DEVICES +) from .midea_entity import MideaEntity +from .midea_devices import MIDEA_DEVICES async def async_setup_entry(hass, config_entry, async_add_entities): - """ - async_setup_entry - """ device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_sensors = config_entry.options.get(CONF_SENSORS, []) + extra_sensors = config_entry.options.get( + CONF_SENSORS, [] + ) binary_sensors = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.BINARY_SENSOR and entity_key in extra_sensors: @@ -26,20 +23,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class MideaSensor(MideaEntity, BinarySensorEntity): - """ - MideaSensor - """ - @property def device_class(self): - """ - device_class - """ return self._config.get("device_class") @property def is_on(self): - """ - is_on - """ return self._device.get_attribute(self._entity_key) diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py old mode 100755 new mode 100644 index 5f779565..640e67bc --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -1,15 +1,12 @@ -""" -climate.py -""" - -import logging - from homeassistant.components.climate import ( ATTR_HVAC_MODE, + ClimateEntity, + ClimateEntityFeature, FAN_AUTO, FAN_HIGH, FAN_LOW, FAN_MEDIUM, + HVACMode, PRESET_AWAY, PRESET_BOOST, PRESET_COMFORT, @@ -21,23 +18,23 @@ SWING_OFF, SWING_ON, SWING_VERTICAL, - ClimateEntity, - ClimateEntityFeature, - HVACMode, ) from homeassistant.const import ( - ATTR_TEMPERATURE, - CONF_DEVICE_ID, - CONF_SWITCHES, MAJOR_VERSION, MINOR_VERSION, - PRECISION_HALVES, - PRECISION_WHOLE, Platform, UnitOfTemperature, + PRECISION_WHOLE, + PRECISION_HALVES, + ATTR_TEMPERATURE, + CONF_DEVICE_ID, + CONF_SWITCHES, ) -from .const import DEVICES, DOMAIN +from .const import ( + DOMAIN, + DEVICES, +) from .midea.devices.ac.device import DeviceAttributes as ACAttributes from .midea.devices.c3.device import DeviceAttributes as C3Attributes from .midea.devices.cc.device import DeviceAttributes as CCAttributes @@ -46,6 +43,7 @@ from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +import logging _LOGGER = logging.getLogger(__name__) @@ -57,17 +55,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): - """ - async_setup_entry - """ device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) + extra_switches = config_entry.options.get( + CONF_SWITCHES, [] + ) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.CLIMATE and ( - config.get("default") or entity_key in extra_switches - ): + if config["type"] == Platform.CLIMATE and (config.get("default") or entity_key in extra_switches): if device.device_type == 0xAC: devs.append(MideaACClimate(device, entity_key)) elif device.device_type == 0xCC: @@ -82,31 +77,20 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class MideaClimate(MideaEntity, ClimateEntity): - """ - MideaClimate - """ # https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded - _enable_turn_on_off_backwards_compatibility: bool = ( - False # maybe remove after 2025.1 - ) + _enable_turn_on_off_backwards_compatibility: bool = False # maybe remove after 2025.1 def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) @property def supported_features(self): - """ - supported_features - """ features = ( - ClimateEntityFeature.TARGET_TEMPERATURE - | ClimateEntityFeature.FAN_MODE - | ClimateEntityFeature.PRESET_MODE - | ClimateEntityFeature.SWING_MODE + ClimateEntityFeature.TARGET_TEMPERATURE | + ClimateEntityFeature.FAN_MODE | + ClimateEntityFeature.PRESET_MODE | + ClimateEntityFeature.SWING_MODE ) if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON @@ -114,65 +98,38 @@ def supported_features(self): @property def min_temp(self): - """ - min_temp - """ return TEMPERATURE_MIN @property def max_temp(self): - """ - max_temp - """ return TEMPERATURE_MAX @property def temperature_unit(self): - """ - temperature_unit - """ return UnitOfTemperature.CELSIUS @property def target_temperature_low(self): - """ - target_temperature_low - """ return TEMPERATURE_MIN @property def target_temperature_high(self): - """ - target_temperature_high - """ return TEMPERATURE_MAX @property def hvac_modes(self): - """ - hvac_modes - """ return self._modes @property def swing_modes(self): - """ - swing_modes - """ return self._swing_modes @property def is_on(self) -> bool: - """ - is_on - """ return self.hvac_mode != HVACMode.OFF @property def hvac_mode(self) -> str: - """ - hvac_mode - """ if self._device.get_attribute("power"): return self._modes[self._device.get_attribute("mode")] else: @@ -180,30 +137,18 @@ def hvac_mode(self) -> str: @property def target_temperature(self): - """ - target_temperature - """ return self._device.get_attribute("target_temperature") @property def current_temperature(self): - """ - current_temperature - """ return self._device.get_attribute("indoor_temperature") @property def preset_modes(self): - """ - preset_modes - """ return self._preset_modes @property def preset_mode(self): - """ - preset_mode - """ if self._device.get_attribute("comfort_mode"): mode = PRESET_COMFORT elif self._device.get_attribute("eco_mode"): @@ -220,37 +165,18 @@ def preset_mode(self): @property def extra_state_attributes(self) -> dict: - """ - extra_state_attributes - """ return self._device.attributes def turn_on(self): - """ - turn_on - """ self._device.set_attribute(attr="power", value=True) def turn_off(self): - """ - turn_off - """ self._device.set_attribute(attr="power", value=False) def set_temperature(self, **kwargs) -> None: - """ - set_temperature - """ if ATTR_TEMPERATURE not in kwargs: return - # get temperature_value - temperature_value = kwargs.get(ATTR_TEMPERATURE) - # check temperature_value is not None - if temperature_value is not None: - temperature = float(int((float(temperature_value) * 2) + 0.5)) / 2 - else: - # if temperature_value None,set default value to 0.0 - temperature = 0.0 + temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 hvac_mode = kwargs.get(ATTR_HVAC_MODE) if hvac_mode == HVACMode.OFF: self.turn_off() @@ -258,15 +184,11 @@ def set_temperature(self, **kwargs) -> None: try: mode = self._modes.index(hvac_mode.lower()) if hvac_mode else None self._device.set_target_temperature( - target_temperature=temperature, mode=mode - ) + target_temperature=temperature, mode=mode) except ValueError as e: _LOGGER.error(f"set_temperature {e}, kwargs = {kwargs}") def set_hvac_mode(self, hvac_mode: str) -> None: - """ - set_hvac_mode - """ hvac_mode = hvac_mode.lower() if hvac_mode == HVACMode.OFF: self.turn_off() @@ -274,9 +196,6 @@ def set_hvac_mode(self, hvac_mode: str) -> None: self._device.set_attribute(attr="mode", value=self._modes.index(hvac_mode)) def set_preset_mode(self, preset_mode: str) -> None: - """ - set_preset_mode - """ old_mode = self.preset_mode preset_mode = preset_mode.lower() if preset_mode == PRESET_AWAY: @@ -301,215 +220,121 @@ def set_preset_mode(self, preset_mode: str) -> None: self._device.set_attribute(attr="boost_mode", value=False) def update_state(self, status): - """ - update_state - """ try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) + _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") class MideaACClimate(MideaClimate): - """ - MideaACClimate - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) - self._modes = [ - HVACMode.OFF, - HVACMode.AUTO, - HVACMode.COOL, - HVACMode.DRY, - HVACMode.HEAT, - HVACMode.FAN_ONLY, - ] + self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.DRY, HVACMode.HEAT, HVACMode.FAN_ONLY] self._fan_speeds = { FAN_SILENT.capitalize(): 20, FAN_LOW.capitalize(): 40, FAN_MEDIUM.capitalize(): 60, FAN_HIGH.capitalize(): 80, FAN_FULL_SPEED.capitalize(): 100, - FAN_AUTO.capitalize(): 102, + FAN_AUTO.capitalize(): 102 } self._swing_modes = [ SWING_OFF.capitalize(), SWING_VERTICAL.capitalize(), SWING_HORIZONTAL.capitalize(), - SWING_BOTH.capitalize(), - ] - self._preset_modes = [ - PRESET_NONE, - PRESET_COMFORT, - PRESET_ECO, - PRESET_BOOST, - PRESET_SLEEP, - PRESET_AWAY, + SWING_BOTH.capitalize() ] + self._preset_modes = [PRESET_NONE, PRESET_COMFORT, PRESET_ECO, PRESET_BOOST, PRESET_SLEEP, PRESET_AWAY] @property def fan_modes(self): - """ - fan_modes - """ return list(self._fan_speeds.keys()) @property def fan_mode(self) -> str: - """ - fan_mode - """ fan_speed = self._device.get_attribute(ACAttributes.fan_speed) if fan_speed > 100: return FAN_AUTO.capitalize() - if fan_speed > 80: + elif fan_speed > 80: return FAN_FULL_SPEED.capitalize() - if fan_speed > 60: + elif fan_speed > 60: return FAN_HIGH.capitalize() - if fan_speed > 40: + elif fan_speed > 40: return FAN_MEDIUM.capitalize() - if fan_speed > 20: + elif fan_speed > 20: return FAN_LOW.capitalize() - return FAN_SILENT.capitalize() + else: + return FAN_SILENT.capitalize() @property def target_temperature_step(self): - """ - target_temperature_step - """ - return ( - PRECISION_WHOLE if self._device.temperature_step == 1 else PRECISION_HALVES - ) + return PRECISION_WHOLE if self._device.temperature_step == 1 else PRECISION_HALVES @property def swing_mode(self): - """ - swing_mode - """ - swing_mode = ( - 1 if self._device.get_attribute(ACAttributes.swing_vertical) else 0 - ) + (2 if self._device.get_attribute(ACAttributes.swing_horizontal) else 0) + swing_mode = (1 if self._device.get_attribute(ACAttributes.swing_vertical) else 0) + \ + (2 if self._device.get_attribute(ACAttributes.swing_horizontal) else 0) return self._swing_modes[swing_mode] @property def outdoor_temperature(self): - """ - outdoor_temperature - """ return self._device.get_attribute(ACAttributes.outdoor_temperature) def set_fan_mode(self, fan_mode: str) -> None: - """ - set_fan_mode - """ fan_speed = self._fan_speeds.get(fan_mode.capitalize()) if fan_speed: self._device.set_attribute(attr=ACAttributes.fan_speed, value=fan_speed) def set_swing_mode(self, swing_mode: str) -> None: - """ - set_swing_mode - """ swing = self._swing_modes.index(swing_mode.capitalize()) swing_vertical = swing & 1 > 0 swing_horizontal = swing & 2 > 0 - self._device.set_swing( - swing_vertical=swing_vertical, swing_horizontal=swing_horizontal - ) + self._device.set_swing(swing_vertical=swing_vertical, swing_horizontal=swing_horizontal) class MideaCCClimate(MideaClimate): - """ - MideaCCClimate - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) - self._modes = [ - HVACMode.OFF, - HVACMode.FAN_ONLY, - HVACMode.DRY, - HVACMode.HEAT, - HVACMode.COOL, - HVACMode.AUTO, + self._modes = [HVACMode.OFF, HVACMode.FAN_ONLY, HVACMode.DRY, HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO] + self._swing_modes = [ + SWING_OFF.capitalize(), + SWING_ON.capitalize() ] - self._swing_modes = [SWING_OFF.capitalize(), SWING_ON.capitalize()] self._preset_modes = [PRESET_NONE, PRESET_SLEEP, PRESET_ECO] @property def fan_modes(self): - """ - fan_modes - """ return self._device.fan_modes @property def fan_mode(self) -> str: - """ - fan_mode - """ return self._device.get_attribute(CCAttributes.fan_speed) @property def target_temperature_step(self): - """ - target_temperature_step - """ return self._device.get_attribute(CCAttributes.temperature_precision) @property def swing_mode(self): - """ - swing_mode - """ - return ( - SWING_ON.capitalize() - if self._device.get_attribute(CCAttributes.swing) - else SWING_OFF.capitalize() - ) + return SWING_ON.capitalize() if self._device.get_attribute(CCAttributes.swing) else SWING_OFF.capitalize() def set_fan_mode(self, fan_mode: str) -> None: - """ - set_fan_mode - """ self._device.set_attribute(attr=CCAttributes.fan_speed, value=fan_mode) def set_swing_mode(self, swing_mode: str) -> None: - """ - set_swing_mode - """ self._device.set_attribute( attr=CCAttributes.swing, - value=swing_mode.capitalize() == SWING_ON.capitalize(), + value=swing_mode.capitalize() == SWING_ON.capitalize() ) class MideaCFClimate(MideaClimate): - """ - MideaCFClimate - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] @property def supported_features(self): - """ - supported_features - """ features = ClimateEntityFeature.TARGET_TEMPERATURE if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON @@ -517,61 +342,36 @@ def supported_features(self): @property def target_temperature_step(self): - """ - target_temperature_step - """ return PRECISION_WHOLE @property def min_temp(self): - """ - min_temp - """ return self._device.get_attribute(CFAttributes.min_temperature) @property def max_temp(self): - """ - max_temp - """ return self._device.get_attribute(CFAttributes.max_temperature) @property def target_temperature_low(self): - """ - target_temperature_low - """ return self._device.get_attribute(CFAttributes.min_temperature) @property def target_temperature_high(self): - """ - target_temperature_high - """ return self._device.get_attribute(CFAttributes.max_temperature) @property def current_temperature(self): - """ - current_temperature - """ return self._device.get_attribute(CFAttributes.current_temperature) class MideaC3Climate(MideaClimate): - """ - MideaC3Climate - """ - _powers = [ C3Attributes.zone1_power, C3Attributes.zone2_power, ] def __init__(self, device, entity_key, zone): - """ - __init__ - """ super().__init__(device, entity_key) self._zone = zone self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] @@ -579,9 +379,6 @@ def __init__(self, device, entity_key, zone): @property def supported_features(self): - """ - supported_features - """ features = ClimateEntityFeature.TARGET_TEMPERATURE if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON @@ -589,92 +386,50 @@ def supported_features(self): @property def target_temperature_step(self): - """ - target_temperature_step - """ - return ( - PRECISION_WHOLE - if self._device.get_attribute(C3Attributes.zone_temp_type)[self._zone] - else PRECISION_HALVES - ) + return PRECISION_WHOLE if \ + self._device.get_attribute(C3Attributes.zone_temp_type)[self._zone] else PRECISION_HALVES @property def min_temp(self): - """ - min_temp - """ return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] @property def max_temp(self): - """ - max_temp - """ return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] @property def target_temperature_low(self): - """ - target_temperature_low - """ return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] @property def target_temperature_high(self): - """ - target_temperature_high - """ return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] def turn_on(self): - """ - turn_on - """ self._device.set_attribute(attr=self._power_attr, value=True) def turn_off(self): - """ - turn_off - """ self._device.set_attribute(attr=self._power_attr, value=False) @property def hvac_mode(self) -> str: - """ - hvac_mode - """ if self._device.get_attribute(self._power_attr): return self._modes[self._device.get_attribute(C3Attributes.mode)] - return HVACMode.OFF + else: + return HVACMode.OFF @property def target_temperature(self): - """ - target_temperature - """ return self._device.get_attribute(C3Attributes.target_temperature)[self._zone] @property def current_temperature(self): - """ - current_temperature - """ return None def set_temperature(self, **kwargs) -> None: - """ - set_temperature - """ if ATTR_TEMPERATURE not in kwargs: return - # get temperature_value - temperature_value = kwargs.get(ATTR_TEMPERATURE) - # check temperature_value is not None - if temperature_value is not None: - temperature = float(int((float(temperature_value) * 2) + 0.5)) / 2 - else: - # if temperature_value None,set default value to 0.0 - temperature = 0.0 + temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 hvac_mode = kwargs.get(ATTR_HVAC_MODE) if hvac_mode == HVACMode.OFF: self.turn_off() @@ -682,15 +437,11 @@ def set_temperature(self, **kwargs) -> None: try: mode = self._modes.index(hvac_mode.lower()) if hvac_mode else None self._device.set_target_temperature( - zone=self._zone, target_temperature=temperature, mode=mode - ) + zone=self._zone, target_temperature=temperature, mode=mode) except ValueError as e: _LOGGER.error(f"set_temperature {e}, kwargs = {kwargs}") def set_hvac_mode(self, hvac_mode: str) -> None: - """ - set_hvac_mode - """ hvac_mode = hvac_mode.lower() if hvac_mode == HVACMode.OFF: self.turn_off() @@ -699,123 +450,65 @@ def set_hvac_mode(self, hvac_mode: str) -> None: class MideaFBClimate(MideaClimate): - """ - MideaFBClimate - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) self._modes = [HVACMode.OFF, HVACMode.HEAT] self._preset_modes = self._device.modes @property def supported_features(self): - """ - supported_features - """ - features = ( - ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE - ) + features = ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON return features @property def target_temperature_step(self): - """ - target_temperature_step - """ return PRECISION_WHOLE @property def preset_modes(self): - """ - preset_modes - """ return self._preset_modes @property def preset_mode(self): - """ - preset_mode - """ return self._device.get_attribute(attr=FBAttributes.mode) @property def min_temp(self): - """ - min_temp - """ return 5 @property def max_temp(self): - """ - max_temp - """ return 35 @property def target_temperature_low(self): - """ - target_temperature_low - """ return 5 @property def target_temperature_high(self): - """ - target_temperature_high - """ return 35 @property def hvac_mode(self) -> str: - """ - hvac_mode - """ - return ( - HVACMode.HEAT - if self._device.get_attribute(attr=FBAttributes.power) - else HVACMode.OFF - ) + return HVACMode.HEAT if self._device.get_attribute(attr=FBAttributes.power) else HVACMode.OFF @property def current_temperature(self): - """ - current_temperature - """ return self._device.get_attribute(FBAttributes.current_temperature) def set_temperature(self, **kwargs) -> None: - """ - set_temperature - """ if ATTR_TEMPERATURE not in kwargs: return - # get temperature_value - temperature_value = kwargs.get(ATTR_TEMPERATURE) - # check temperature_value is not None - if temperature_value is not None: - temperature = float(int((float(temperature_value) * 2) + 0.5)) / 2 - else: - # if temperature_value None,set default value to 0.0 - temperature = 0.0 + temperature = float(int((float(kwargs.get(ATTR_TEMPERATURE)) * 2) + 0.5)) / 2 hvac_mode = kwargs.get(ATTR_HVAC_MODE) if hvac_mode == HVACMode.OFF: self.turn_off() else: - self._device.set_attribute( - attr=FBAttributes.target_temperature, value=temperature - ) + self._device.set_attribute(attr=FBAttributes.target_temperature, value=temperature) def set_hvac_mode(self, hvac_mode: str) -> None: - """ - set_hvac_mode - """ hvac_mode = hvac_mode.lower() if hvac_mode == HVACMode.OFF: self.turn_off() @@ -823,7 +516,4 @@ def set_hvac_mode(self, hvac_mode: str) -> None: self.turn_on() def set_preset_mode(self, preset_mode: str) -> None: - """ - set_preset_mode - """ - self._device.set_attribute(attr=FBAttributes.mode, value=preset_mode) + self._device.set_attribute(attr=FBAttributes.mode,value=preset_mode) diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py old mode 100755 new mode 100644 index ea35c73c..16433b0a --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -1,61 +1,48 @@ -""" -config_flow.py -""" - -import logging -import os -from typing import Any, Dict, List - import voluptuous as vol - +import os try: from homeassistant.helpers.json import save_json except ImportError: from homeassistant.util.json import save_json - -import homeassistant.helpers.config_validation as cv +from homeassistant.util.json import load_json +import logging +from .const import ( + DOMAIN, + EXTRA_SENSOR, + EXTRA_CONTROL, + CONF_ACCOUNT, + CONF_SERVER, + CONF_KEY, + CONF_MODEL, + CONF_SUBTYPE, + CONF_REFRESH_INTERVAL +) from homeassistant import config_entries +from homeassistant.core import callback from homeassistant.const import ( - CONF_CUSTOMIZE, + CONF_NAME, CONF_DEVICE, + CONF_TOKEN, CONF_DEVICE_ID, + CONF_TYPE, CONF_IP_ADDRESS, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, CONF_PROTOCOL, - CONF_SENSORS, + CONF_PORT, CONF_SWITCHES, - CONF_TOKEN, - CONF_TYPE, + CONF_SENSORS, + CONF_CUSTOMIZE, + CONF_PASSWORD, ) -from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.aiohttp_client import async_create_clientsession -from homeassistant.util.json import load_json - -from .const import ( - CONF_ACCOUNT, - CONF_KEY, - CONF_MODEL, - CONF_REFRESH_INTERVAL, - CONF_SERVER, - CONF_SUBTYPE, - DOMAIN, - EXTRA_CONTROL, - EXTRA_SENSOR, -) +from .midea.core.discover import discover from .midea.core.cloud import get_midea_cloud from .midea.core.device import MiedaDevice -from .midea.core.discover import discover from .midea_devices import MIDEA_DEVICES _LOGGER = logging.getLogger(__name__) -ADD_WAY = { - "discovery": "Discover automatically", - "manually": "Configure manually", - "list": "List all appliances only", -} +ADD_WAY = {"discovery": "Discover automatically", "manually": "Configure manually", "list": "List all appliances only"} PROTOCOLS = {1: "V1", 2: "V2", 3: "V3"} STORAGE_PATH = f".storage/{DOMAIN}" @@ -70,21 +57,17 @@ PRESET_ACCOUNT = [ 39182118275972017797890111985649342047468653967530949796945843010512, 29406100301096535908214728322278519471982973450672552249652548883645, - 39182118275972017797890111985649342050088014265865102175083010656997, + 39182118275972017797890111985649342050088014265865102175083010656997 ] class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """ - ConfigFlow - """ - - available_device: List[Any] = [] - devices: Dict[Any, Any] = {} - found_device: Dict[Any, Any] = {} - supports: Dict[Any, Any] = {} - unsorted: Dict[Any, Any] = {} - account: Dict[Any, Any] = {} + available_device = [] + devices = {} + found_device = {} + supports = {} + unsorted = {} + account = {} cloud = None session = None for device_type, device_info in MIDEA_DEVICES.items(): @@ -95,104 +78,65 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): supports[item[0]] = item[1] def _save_device_config(self, data: dict): - """ - _save_device_config - """ os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True) - record_file = self.hass.config.path( - f"{STORAGE_PATH}/{data[CONF_DEVICE_ID]}.json" - ) + record_file = self.hass.config.path(f"{STORAGE_PATH}/{data[CONF_DEVICE_ID]}.json") save_json(record_file, data) def _load_device_config(self, device_id): - """ - _load_device_config - """ record_file = self.hass.config.path(f"{STORAGE_PATH}/{device_id}.json") json_data = load_json(record_file, default={}) return json_data def _save_account(self, account: dict): - """ - _save_account - """ os.makedirs(self.hass.config.path(STORAGE_PATH), exist_ok=True) record_file = self.hass.config.path(f"{STORAGE_PATH}/account.json") - account[CONF_PASSWORD] = format( - ( - int(account[CONF_ACCOUNT].encode("utf-8").hex(), 16) - ^ int(account[CONF_PASSWORD].encode("utf-8").hex(), 16) - ), - "x", - ) + account[CONF_PASSWORD] = format((int(account[CONF_ACCOUNT].encode("utf-8").hex(), 16) ^ + int(account[CONF_PASSWORD].encode("utf-8").hex(), 16)), 'x') save_json(record_file, account) def _load_account(self): - """ - _load_account - """ record_file = self.hass.config.path(f"{STORAGE_PATH}/account.json") json_data = load_json(record_file, default={}) if CONF_ACCOUNT in json_data.keys(): - json_data[CONF_PASSWORD] = bytes.fromhex( - format( - ( - int(json_data[CONF_PASSWORD], 16) - ^ int(json_data[CONF_ACCOUNT].encode("utf-8").hex(), 16) - ), - "X", - ) - ).decode("UTF-8") + json_data[CONF_PASSWORD] = bytes.fromhex(format(( + int(json_data[CONF_PASSWORD], 16) ^ + int(json_data[CONF_ACCOUNT].encode("utf-8").hex(), 16)), 'X') + ).decode('UTF-8') return json_data @staticmethod def _check_storage_device(device: dict, storage_device: dict): - """ - _check_storage_device - """ if storage_device.get(CONF_SUBTYPE) is None: return False - if device.get(CONF_PROTOCOL) == 3 and ( - storage_device.get(CONF_TOKEN) is None - or storage_device.get(CONF_KEY) is None - ): + if (device.get(CONF_PROTOCOL) == 3 and + (storage_device.get(CONF_TOKEN) is None or storage_device.get(CONF_KEY) is None)): return False return True def _already_configured(self, device_id, ip_address): - """ - _already_configured - """ for entry in self._async_current_entries(): - if device_id == entry.data.get( - CONF_DEVICE_ID - ) or ip_address == entry.data.get(CONF_IP_ADDRESS): + if device_id == entry.data.get(CONF_DEVICE_ID) or ip_address == entry.data.get(CONF_IP_ADDRESS): return True return False async def async_step_user(self, user_input=None, error=None): - """ - async_step_user - """ if user_input is not None: if user_input["action"] == "discovery": return await self.async_step_discovery() - if user_input["action"] == "manually": + elif user_input["action"] == "manually": self.found_device = {} return await self.async_step_manually() - return await self.async_step_list() + else: + return await self.async_step_list() return self.async_show_form( step_id="user", - data_schema=vol.Schema( - {vol.Required("action", default="discovery"): vol.In(ADD_WAY)} - ), - errors={"base": error} if error else None, + data_schema=vol.Schema({ + vol.Required("action", default="discovery"): vol.In(ADD_WAY) + }), + errors={"base": error} if error else None ) async def async_step_login(self, user_input=None, error=None): - """ - async_step_login - """ if user_input is not None: if self.session is None: self.session = async_create_clientsession(self.hass) @@ -201,57 +145,46 @@ async def async_step_login(self, user_input=None, error=None): session=self.session, cloud_name=SERVERS[user_input[CONF_SERVER]], account=user_input[CONF_ACCOUNT], - password=user_input[CONF_PASSWORD], + password=user_input[CONF_PASSWORD] ) if await self.cloud.login(): self.account = { CONF_ACCOUNT: user_input[CONF_ACCOUNT], CONF_PASSWORD: user_input[CONF_PASSWORD], - CONF_SERVER: SERVERS[user_input[CONF_SERVER]], + CONF_SERVER: SERVERS[user_input[CONF_SERVER]] } self._save_account(self.account) return await self.async_step_auto() - return await self.async_step_login(error="login_failed") + else: + return await self.async_step_login(error="login_failed") return self.async_show_form( step_id="login", - data_schema=vol.Schema( - { - vol.Required(CONF_ACCOUNT): str, - vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_SERVER, default=1): vol.In(SERVERS), - } - ), - errors={"base": error} if error else None, + data_schema=vol.Schema({ + vol.Required(CONF_ACCOUNT): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_SERVER, default=1): vol.In(SERVERS) + }), + errors={"base": error} if error else None ) async def async_step_list(self, user_input=None, error=None): - """ - async_step_list - """ all_devices = discover() if len(all_devices) > 0: - table = ( - "Appliance code|Type|IP address|SN|Supported\n:--:|:--:|:--:|:--:|:--:" - ) + table = "Appliance code|Type|IP address|SN|Supported\n:--:|:--:|:--:|:--:|:--:" for device_id, device in all_devices.items(): supported = device.get(CONF_TYPE) in self.supports.keys() - table += ( - f"\n{device_id}|{'%02X' % device.get(CONF_TYPE)}|{device.get(CONF_IP_ADDRESS)}|" - f"{device.get('sn')}|" - f"{'YES' if supported else 'NO'}" - ) + table += f"\n{device_id}|{'%02X' % device.get(CONF_TYPE)}|{device.get(CONF_IP_ADDRESS)}|" \ + f"{device.get('sn')}|" \ + f"{'YES' if supported else 'NO'}" else: table = "Not found" return self.async_show_form( step_id="list", description_placeholders={"table": table}, - errors={"base": error} if error else None, + errors={"base": error} if error else None ) async def async_step_discovery(self, user_input=None, error=None): - """ - async_step_discovery - """ if user_input is not None: if user_input[CONF_IP_ADDRESS].lower() == "auto": ip_address = None @@ -261,24 +194,21 @@ async def async_step_discovery(self, user_input=None, error=None): self.available_device = {} for device_id, device in self.devices.items(): if not self._already_configured(device_id, device.get(CONF_IP_ADDRESS)): - self.available_device[device_id] = ( + self.available_device[device_id] = \ f"{device_id} ({self.supports.get(device.get(CONF_TYPE))})" - ) if len(self.available_device) > 0: return await self.async_step_auto() - return await self.async_step_discovery(error="no_devices") + else: + return await self.async_step_discovery(error="no_devices") return self.async_show_form( step_id="discovery", - data_schema=vol.Schema( - {vol.Required(CONF_IP_ADDRESS, default="auto"): str} - ), - errors={"base": error} if error else None, + data_schema=vol.Schema({ + vol.Required(CONF_IP_ADDRESS, default="auto"): str + }), + errors={"base": error} if error else None ) async def async_step_auto(self, user_input=None, error=None): - """ - async_step_auto - """ if user_input is not None: device_id = user_input[CONF_DEVICE] device = self.devices.get(device_id) @@ -294,94 +224,78 @@ async def async_step_auto(self, user_input=None, error=None): CONF_NAME: storage_device.get(CONF_NAME), CONF_SUBTYPE: storage_device.get(CONF_SUBTYPE), CONF_TOKEN: storage_device.get(CONF_TOKEN), - CONF_KEY: storage_device.get(CONF_KEY), + CONF_KEY: storage_device.get(CONF_KEY) } - _LOGGER.debug( - f"Loaded configuration for device {device_id} from storage" - ) + _LOGGER.debug(f"Loaded configuration for device {device_id} from storage") return await self.async_step_manually() - if CONF_ACCOUNT not in self.account.keys(): - self.account = self._load_account() + else: if CONF_ACCOUNT not in self.account.keys(): - return await self.async_step_login() - if self.session is None: - self.session = async_create_clientsession(self.hass) - if self.cloud is None: - self.cloud = get_midea_cloud( - self.account[CONF_SERVER], - self.session, - self.account[CONF_ACCOUNT], - self.account[CONF_PASSWORD], - ) - if not await self.cloud.login(): - return await self.async_step_login() - self.found_device = { - CONF_DEVICE_ID: device_id, - CONF_TYPE: device.get(CONF_TYPE), - CONF_PROTOCOL: device.get(CONF_PROTOCOL), - CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), - CONF_PORT: device.get(CONF_PORT), - CONF_MODEL: device.get(CONF_MODEL), - } - if device_info := await self.cloud.get_device_info(device_id): - self.found_device[CONF_NAME] = device_info.get("name") - self.found_device[CONF_SUBTYPE] = device_info.get("model_number") - if device.get(CONF_PROTOCOL) == 3: - if self.account[CONF_SERVER] == "美的美居": - _LOGGER.debug( - "Try to get the Token and the Key use the preset MSmartHome account" - ) + self.account = self._load_account() + if CONF_ACCOUNT not in self.account.keys(): + return await self.async_step_login() + if self.session is None: + self.session = async_create_clientsession(self.hass) + if self.cloud is None: self.cloud = get_midea_cloud( - "MSmartHome", - self.session, - bytes.fromhex( - format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[1]), "X") - ).decode("ASCII"), - bytes.fromhex( - format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[2]), "X") - ).decode("ASCII"), - ) - if not await self.cloud.login(): - return await self.async_step_auto(error="preset_account") - keys = await self.cloud.get_keys(user_input[CONF_DEVICE]) - for method, key in keys.items(): - dm = MiedaDevice( - name="", - device_id=device_id, - device_type=device.get(CONF_TYPE), - ip_address=device.get(CONF_IP_ADDRESS), - port=device.get(CONF_PORT), - token=key["token"], - key=key["key"], - protocol=3, - model=device.get(CONF_MODEL), - subtype=0, - attributes={}, - ) - if dm.connect(refresh_status=False): - dm.close_socket() - self.found_device[CONF_TOKEN] = key["token"] - self.found_device[CONF_KEY] = key["key"] - return await self.async_step_manually() - return await self.async_step_auto(error="connect_error") - return await self.async_step_manually() + self.account[CONF_SERVER], self.session, self.account[CONF_ACCOUNT], + self.account[CONF_PASSWORD]) + if not await self.cloud.login(): + return await self.async_step_login() + self.found_device = { + CONF_DEVICE_ID: device_id, + CONF_TYPE: device.get(CONF_TYPE), + CONF_PROTOCOL: device.get(CONF_PROTOCOL), + CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), + CONF_PORT: device.get(CONF_PORT), + CONF_MODEL: device.get(CONF_MODEL), + } + if device_info := await self.cloud.get_device_info(device_id): + self.found_device[CONF_NAME] = device_info.get("name") + self.found_device[CONF_SUBTYPE] = device_info.get("model_number") + if device.get(CONF_PROTOCOL) == 3: + if self.account[CONF_SERVER] == "美的美居": + _LOGGER.debug(f"Try to get the Token and the Key use the preset MSmartHome account") + self.cloud = get_midea_cloud( + "MSmartHome", + self.session, + bytes.fromhex(format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[1]), 'X')).decode('ASCII'), + bytes.fromhex(format((PRESET_ACCOUNT[0] ^ PRESET_ACCOUNT[2]), 'X')).decode('ASCII')) + if not await self.cloud.login(): + return await self.async_step_auto(error="preset_account") + keys = await self.cloud.get_keys(user_input[CONF_DEVICE]) + for method, key in keys.items(): + dm = MiedaDevice( + name="", + device_id=device_id, + device_type=device.get(CONF_TYPE), + ip_address=device.get(CONF_IP_ADDRESS), + port=device.get(CONF_PORT), + token=key["token"], + key=key["key"], + protocol=3, + model=device.get(CONF_MODEL), + subtype=0, + attributes={} + ) + if dm.connect(refresh_status=False): + dm.close_socket() + self.found_device[CONF_TOKEN] = key["token"] + self.found_device[CONF_KEY] = key["key"] + return await self.async_step_manually() + return await self.async_step_auto(error="connect_error") + else: + return await self.async_step_manually() return self.async_show_form( step_id="auto", - data_schema=vol.Schema( - { - vol.Required( - CONF_DEVICE, default=list(self.available_device.keys())[0] - ): vol.In(self.available_device), - } - ), - errors={"base": error} if error else None, + data_schema=vol.Schema({ + vol.Required(CONF_DEVICE, default=list(self.available_device.keys())[0]): + vol.In(self.available_device), + }), + errors={"base": error} if error else None ) async def async_step_manually(self, user_input=None, error=None): - """ - async_step_manually - """ if user_input is not None: self.found_device = { CONF_DEVICE_ID: user_input[CONF_DEVICE_ID], @@ -391,16 +305,14 @@ async def async_step_manually(self, user_input=None, error=None): CONF_PORT: user_input[CONF_PORT], CONF_MODEL: user_input[CONF_MODEL], CONF_TOKEN: user_input[CONF_TOKEN], - CONF_KEY: user_input[CONF_KEY], + CONF_KEY: user_input[CONF_KEY] } try: bytearray.fromhex(user_input[CONF_TOKEN]) bytearray.fromhex(user_input[CONF_KEY]) except ValueError: return await self.async_step_manually(error="invalid_token") - if user_input[CONF_PROTOCOL] == 3 and ( - len(user_input[CONF_TOKEN]) == 0 or len(user_input[CONF_KEY]) == 0 - ): + if user_input[CONF_PROTOCOL] == 3 and (len(user_input[CONF_TOKEN]) == 0 or len(user_input[CONF_KEY]) == 0): return await self.async_step_manually(error="invalid_token") dm = MiedaDevice( name="", @@ -413,7 +325,7 @@ async def async_step_manually(self, user_input=None, error=None): protocol=user_input[CONF_PROTOCOL], model=user_input[CONF_MODEL], subtype=0, - attributes={}, + attributes={} ) if dm.connect(refresh_status=False): dm.close_socket() @@ -428,113 +340,75 @@ async def async_step_manually(self, user_input=None, error=None): CONF_SUBTYPE: user_input[CONF_SUBTYPE], CONF_TOKEN: user_input[CONF_TOKEN], CONF_KEY: user_input[CONF_KEY], - } + } self._save_device_config(data) return self.async_create_entry( - title=f"{user_input[CONF_NAME]}", data=data + title=f"{user_input[CONF_NAME]}", + data=data ) - return await self.async_step_manually(error="config_incorrect") + else: + return await self.async_step_manually(error="config_incorrect") return self.async_show_form( step_id="manually", - data_schema=vol.Schema( - { - vol.Required( - CONF_NAME, - default=( - self.found_device.get(CONF_NAME) - if self.found_device.get(CONF_NAME) - else self.supports.get(self.found_device.get(CONF_TYPE)) - ), - ): str, - vol.Required( - CONF_DEVICE_ID, default=self.found_device.get(CONF_DEVICE_ID) - ): int, - vol.Required( - CONF_TYPE, - default=( - self.found_device.get(CONF_TYPE) - if self.found_device.get(CONF_TYPE) - else 0xAC - ), - ): vol.In(self.supports), - vol.Required( - CONF_IP_ADDRESS, default=self.found_device.get(CONF_IP_ADDRESS) - ): str, - vol.Required( - CONF_PORT, - default=( - self.found_device.get(CONF_PORT) - if self.found_device.get(CONF_PORT) - else 6444 - ), - ): int, - vol.Required( - CONF_PROTOCOL, - default=( - self.found_device.get(CONF_PROTOCOL) - if self.found_device.get(CONF_PROTOCOL) - else 3 - ), - ): vol.In(PROTOCOLS), - vol.Required( - CONF_MODEL, - default=( - self.found_device.get(CONF_MODEL) - if self.found_device.get(CONF_MODEL) - else "Unknown" - ), - ): str, - vol.Required( - CONF_SUBTYPE, - default=( - self.found_device.get(CONF_SUBTYPE) - if self.found_device.get(CONF_SUBTYPE) - else 0 - ), - ): int, - vol.Optional( - CONF_TOKEN, - default=( - self.found_device.get(CONF_TOKEN) - if self.found_device.get(CONF_TOKEN) - else "" - ), - ): str, - vol.Optional( - CONF_KEY, - default=( - self.found_device.get(CONF_KEY) - if self.found_device.get(CONF_KEY) - else "" - ), - ): str, - } - ), - errors={"base": error} if error else None, + data_schema=vol.Schema({ + vol.Required( + CONF_NAME, + default=(self.found_device.get(CONF_NAME) + if self.found_device.get(CONF_NAME) + else self.supports.get(self.found_device.get(CONF_TYPE))) + ): str, + vol.Required( + CONF_DEVICE_ID, + default=self.found_device.get(CONF_DEVICE_ID) + ): int, + vol.Required( + CONF_TYPE, + default=self.found_device.get(CONF_TYPE) if self.found_device.get(CONF_TYPE) else 0xac + ): vol.In(self.supports), + vol.Required( + CONF_IP_ADDRESS, + default=self.found_device.get(CONF_IP_ADDRESS) + ): str, + vol.Required( + CONF_PORT, + default=self.found_device.get(CONF_PORT) if self.found_device.get(CONF_PORT) else 6444 + ): int, + vol.Required( + CONF_PROTOCOL, + default=self.found_device.get(CONF_PROTOCOL) if self.found_device.get(CONF_PROTOCOL) else 3 + ): vol.In(PROTOCOLS), + vol.Required( + CONF_MODEL, + default=self.found_device.get(CONF_MODEL) if self.found_device.get(CONF_MODEL) else "Unknown" + ): str, + vol.Required( + CONF_SUBTYPE, + default=self.found_device.get(CONF_SUBTYPE) if self.found_device.get(CONF_SUBTYPE) else 0 + ): int, + vol.Optional( + CONF_TOKEN, + default=self.found_device.get(CONF_TOKEN) if self.found_device.get(CONF_TOKEN) else "" + ): str, + vol.Optional( + CONF_KEY, + default=self.found_device.get(CONF_KEY) if self.found_device.get(CONF_KEY) else "" + ): str, + }), + errors={"base": error} if error else None ) @staticmethod @callback def async_get_options_flow(config_entry): - """ - async_get_options_flow - """ return OptionsFlowHandler(config_entry) class OptionsFlowHandler(config_entries.OptionsFlow): - """ - OptionsFlowHandler - """ - def __init__(self, config_entry: config_entries.ConfigEntry): - """ - __init__ - """ self._config_entry = config_entry self._device_type = config_entry.data.get(CONF_TYPE) if self._device_type is None: - self._device_type = 0xAC + self._device_type = 0xac if CONF_SENSORS in self._config_entry.options: for key in self._config_entry.options[CONF_SENSORS]: if key not in MIDEA_DEVICES[self._device_type]["entities"]: @@ -545,70 +419,72 @@ def __init__(self, config_entry: config_entries.ConfigEntry): self._config_entry.options[CONF_SWITCHES].remove(key) async def async_step_init(self, user_input=None): - """ - async_step_init - """ if self._device_type == CONF_ACCOUNT: return self.async_abort(reason="account_option") if user_input is not None: return self.async_create_entry(title="", data=user_input) sensors = {} switches = {} - for attribute, attribute_config in ( - MIDEA_DEVICES.get(self._device_type).get("entities").items() - ): - attribute_name = ( - attribute if isinstance(attribute, str) else attribute.value - ) + for attribute, attribute_config in MIDEA_DEVICES.get(self._device_type).get("entities").items(): + attribute_name = attribute if type(attribute) is str else attribute.value if attribute_config.get("type") in EXTRA_SENSOR: sensors[attribute_name] = attribute_config.get("name") - elif attribute_config.get( - "type" - ) in EXTRA_CONTROL and not attribute_config.get("default"): + elif attribute_config.get("type") in EXTRA_CONTROL and not attribute_config.get("default"): switches[attribute_name] = attribute_config.get("name") - ip_address = self._config_entry.options.get(CONF_IP_ADDRESS, None) - if ip_address is None: - ip_address = self._config_entry.data.get(CONF_IP_ADDRESS, None) - refresh_interval = self._config_entry.options.get(CONF_REFRESH_INTERVAL, 30) - extra_sensors = list( - set(sensors.keys()) & set(self._config_entry.options.get(CONF_SENSORS, [])) + ip_address = self._config_entry.options.get( + CONF_IP_ADDRESS, None ) - extra_switches = list( - set(switches.keys()) - & set(self._config_entry.options.get(CONF_SWITCHES, [])) + if ip_address is None: + ip_address = self._config_entry.data.get( + CONF_IP_ADDRESS, None + ) + refresh_interval = self._config_entry.options.get( + CONF_REFRESH_INTERVAL, 30 ) - customize = self._config_entry.options.get(CONF_CUSTOMIZE, "") - data_schema = vol.Schema( - { - vol.Required(CONF_IP_ADDRESS, default=ip_address): str, - vol.Required(CONF_REFRESH_INTERVAL, default=refresh_interval): int, - } + extra_sensors = list(set(sensors.keys()) & set(self._config_entry.options.get( + CONF_SENSORS, [] + ))) + extra_switches = list(set(switches.keys()) & set(self._config_entry.options.get( + CONF_SWITCHES, [] + ))) + customize = self._config_entry.options.get( + CONF_CUSTOMIZE, "" ) + data_schema = vol.Schema({ + vol.Required( + CONF_IP_ADDRESS, + default=ip_address + ): str, + vol.Required( + CONF_REFRESH_INTERVAL, + default=refresh_interval + ): int + }) if len(sensors) > 0: - data_schema = data_schema.extend( - { - vol.Required( - CONF_SENSORS, - default=extra_sensors, - ): cv.multi_select(sensors) - } - ) + data_schema = data_schema.extend({ + vol.Required( + CONF_SENSORS, + default=extra_sensors, + ): + cv.multi_select(sensors) + }) if len(switches) > 0: - data_schema = data_schema.extend( - { - vol.Required( - CONF_SWITCHES, - default=extra_switches, - ): cv.multi_select(switches) - } - ) - data_schema = data_schema.extend( - { - vol.Optional( - CONF_CUSTOMIZE, - default=customize, - ): str - } - ) + data_schema = data_schema.extend({ + vol.Required( + CONF_SWITCHES, + default=extra_switches, + ): + cv.multi_select(switches) + }) + data_schema = data_schema.extend({ + vol.Optional( + CONF_CUSTOMIZE, + default=customize, + ): + str + }) - return self.async_show_form(step_id="init", data_schema=data_schema) + return self.async_show_form( + step_id="init", + data_schema=data_schema + ) diff --git a/custom_components/midea_ac_lan/const.py b/custom_components/midea_ac_lan/const.py old mode 100755 new mode 100644 index 85f76b50..dc5439db --- a/custom_components/midea_ac_lan/const.py +++ b/custom_components/midea_ac_lan/const.py @@ -1,9 +1,4 @@ -""" -const.py -""" - from homeassistant.const import Platform - DOMAIN = "midea_ac_lan" COMPONENT = "component" DEVICES = "devices" @@ -15,11 +10,6 @@ CONF_REFRESH_INTERVAL = "refresh_interval" EXTRA_SENSOR = [Platform.SENSOR, Platform.BINARY_SENSOR] EXTRA_SWITCH = [Platform.SWITCH, Platform.LOCK, Platform.SELECT, Platform.NUMBER] -EXTRA_CONTROL = [ - Platform.CLIMATE, - Platform.WATER_HEATER, - Platform.FAN, - Platform.HUMIDIFIER, - Platform.LIGHT, -] + EXTRA_SWITCH +EXTRA_CONTROL = [Platform.CLIMATE, Platform.WATER_HEATER, Platform.FAN, Platform.HUMIDIFIER, Platform.LIGHT] + \ + EXTRA_SWITCH ALL_PLATFORM = EXTRA_SENSOR + EXTRA_CONTROL diff --git a/custom_components/midea_ac_lan/fan.py b/custom_components/midea_ac_lan/fan.py old mode 100755 new mode 100644 index c14d7a00..bc49c8f4 --- a/custom_components/midea_ac_lan/fan.py +++ b/custom_components/midea_ac_lan/fan.py @@ -1,41 +1,36 @@ -""" -fan.py -""" - -import logging from typing import Any from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.const import ( + Platform, CONF_DEVICE_ID, CONF_SWITCHES, - STATE_OFF, STATE_ON, - Platform, + STATE_OFF +) +from .const import ( + DOMAIN, + DEVICES, ) - -from .const import DEVICES, DOMAIN from .midea.devices.ac.device import DeviceAttributes as ACAttributes from .midea.devices.ce.device import DeviceAttributes as CEAttributes from .midea.devices.x40.device import DeviceAttributes as X40Attributes from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +import logging _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): - """ - async_setup_entry - """ device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) + extra_switches = config_entry.options.get( + CONF_SWITCHES, [] + ) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.FAN and ( - config.get("default") or entity_key in extra_switches - ): + if config["type"] == Platform.FAN and (config.get("default") or entity_key in extra_switches): if device.device_type == 0xFA: devs.append(MideaFAFan(device, entity_key)) elif device.device_type == 0xB6: @@ -50,14 +45,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class MideaFan(MideaEntity, FanEntity): - """ - MideaFan - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) def turn_on( @@ -66,9 +54,6 @@ def turn_on( preset_mode: str | None = None, **kwargs: Any, ) -> None: - """ - turn_on - """ if percentage: fan_speed = int(percentage / self.percentage_step + 0.5) else: @@ -77,286 +62,141 @@ def turn_on( @property def preset_modes(self): - """ - preset_modes - """ - return ( - self._device.preset_modes if hasattr(self._device, "preset_modes") else None - ) + return self._device.preset_modes if hasattr(self._device, "preset_modes") else None @property def is_on(self) -> bool: - """ - is_on - """ return self._device.get_attribute("power") @property def oscillating(self): - """ - oscillating - """ return self._device.get_attribute("oscillate") @property def preset_mode(self): - """ - preset_mode - """ return self._device.get_attribute("mode") @property def fan_speed(self): - """ - fan_speed - """ return self._device.get_attribute("fan_speed") def turn_off(self): - """ - turn_off - """ self._device.set_attribute(attr="power", value=False) def toggle(self): - """ - toggle - """ toggle = not self.is_on self._device.set_attribute(attr="power", value=toggle) def oscillate(self, oscillating: bool): - """ - oscillate - """ self._device.set_attribute(attr="oscillate", value=oscillating) def set_preset_mode(self, preset_mode: str): - """ - set_preset_mode - """ self._device.set_attribute(attr="mode", value=preset_mode.capitalize()) @property def percentage(self): - """ - percentage - """ return round(self.fan_speed * self.percentage_step) def set_percentage(self, percentage: int): - """ - set_percentage - """ fan_speed = round(percentage / self.percentage_step) self._device.set_attribute(attr="fan_speed", value=fan_speed) async def async_set_percentage(self, percentage: int): - """ - async_set_percentage - """ if percentage == 0: await self.async_turn_off() else: await self.hass.async_add_executor_job(self.set_percentage, percentage) def update_state(self, status): - """ - update_state - """ try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) + _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") class MideaFAFan(MideaFan): - """ - MideaFAFan - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED - | FanEntityFeature.OSCILLATE - | FanEntityFeature.PRESET_MODE - ) + self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE | FanEntityFeature.PRESET_MODE self._attr_speed_count = self._device.speed_count class MideaB6Fan(MideaFan): - """ - MideaB6Fan - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE - ) + self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE self._attr_speed_count = self._device.speed_count class MideaACFreshAirFan(MideaFan): - """ - MideaACFreshAirFan - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE - ) + self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE self._attr_speed_count = 100 @property def preset_modes(self): - """ - preset_modes - """ return self._device.fresh_air_fan_speeds @property def state(self): - """ - state - """ - return ( - STATE_ON - if self._device.get_attribute(ACAttributes.fresh_air_power) - else STATE_OFF - ) + return STATE_ON if self._device.get_attribute(ACAttributes.fresh_air_power) else STATE_OFF @property def is_on(self) -> bool: - """ - is_on - """ return self.state == STATE_ON @property def fan_speed(self): - """ - fan_speed - """ return self._device.get_attribute(ACAttributes.fresh_air_fan_speed) def turn_on(self, percentage, preset_mode, **kwargs): - """ - turn_on - """ self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=True) def turn_off(self): - """ - turn_off - """ self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=False) def toggle(self): - """ - toggle - """ toggle = not self.is_on self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=toggle) def set_percentage(self, percentage: int): - """ - set_percentage - """ fan_speed = int(percentage / self.percentage_step + 0.5) - self._device.set_attribute( - attr=ACAttributes.fresh_air_fan_speed, value=fan_speed - ) + self._device.set_attribute(attr=ACAttributes.fresh_air_fan_speed, value=fan_speed) def set_preset_mode(self, preset_mode: str): - """ - set_preset_mode - """ self._device.set_attribute(attr=ACAttributes.fresh_air_mode, value=preset_mode) @property def preset_mode(self): - """ - preset_mode - """ return self._device.get_attribute(attr=ACAttributes.fresh_air_mode) class MideaCEFan(MideaFan): - """ - MideaCEFan - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE - ) + self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE self._attr_speed_count = self._device.speed_count def turn_on(self, percentage, preset_mode, **kwargs): - """ - turn_on - """ self._device.set_attribute(attr=CEAttributes.power, value=True) async def async_set_percentage(self, percentage: int): - """ - async_set_percentage - """ await self.hass.async_add_executor_job(self.set_percentage, percentage) class Midea40Fan(MideaFan): - """ - Midea40Fan - """ - def __init__(self, device, entity_key): - """ - __init__ - """ super().__init__(device, entity_key) - self._attr_supported_features = ( - FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE - ) + self._attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE self._attr_speed_count = 2 @property def state(self): - """ - state - """ - return ( - STATE_ON - if self._device.get_attribute(attr=X40Attributes.fan_speed) > 0 - else STATE_OFF - ) + return STATE_ON if self._device.get_attribute(attr=X40Attributes.fan_speed) > 0 else STATE_OFF def turn_on(self, percentage, preset_mode, **kwargs): - """ - turn_on - """ self._device.set_attribute(attr=X40Attributes.fan_speed, value=1) def turn_off(self): - """ - turn_off - """ self._device.set_attribute(attr=X40Attributes.fan_speed, value=0) diff --git a/custom_components/midea_ac_lan/humidifier.py b/custom_components/midea_ac_lan/humidifier.py old mode 100755 new mode 100644 index 633a9473..35ec5a0a --- a/custom_components/midea_ac_lan/humidifier.py +++ b/custom_components/midea_ac_lan/humidifier.py @@ -1,32 +1,33 @@ -""" -humidifier.py -""" - -import logging - from homeassistant.components.humidifier import ( HumidifierDeviceClass, HumidifierEntity, HumidifierEntityFeature, ) -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform - -from .const import DEVICES, DOMAIN +from homeassistant.const import ( + Platform, + CONF_DEVICE_ID, + CONF_SWITCHES, +) +from .const import ( + DOMAIN, + DEVICES, +) from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +import logging _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) + extra_switches = config_entry.options.get( + CONF_SWITCHES, [] + ) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.HUMIDIFIER and ( - config.get("default") or entity_key in extra_switches - ): + if config["type"] == Platform.HUMIDIFIER and (config.get("default") or entity_key in extra_switches): if device.device_type == 0xA1: devs.append(MideaA1Humidifier(device, entity_key)) if device.device_type == 0xFD: @@ -78,9 +79,7 @@ def update_state(self, status): try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) + _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") class MideaA1Humidifier(MideaHumidifier): diff --git a/custom_components/midea_ac_lan/light.py b/custom_components/midea_ac_lan/light.py index 91747b87..5834574d 100644 --- a/custom_components/midea_ac_lan/light.py +++ b/custom_components/midea_ac_lan/light.py @@ -5,19 +5,25 @@ ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, + LightEntity, + LightEntityFeature, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, - LightEntity, - LightEntityFeature, ) -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform - -from .const import DEVICES, DOMAIN +from homeassistant.const import ( + Platform, + CONF_DEVICE_ID, + CONF_SWITCHES, +) +from .const import ( + DOMAIN, + DEVICES +) from .midea.devices.x13.device import DeviceAttributes as X13Attributes -from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +from .midea_devices import MIDEA_DEVICES _LOGGER = logging.getLogger(__name__) @@ -25,12 +31,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) + extra_switches = config_entry.options.get( + CONF_SWITCHES, [] + ) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.LIGHT and ( - config.get("default") or entity_key in extra_switches - ): + if config["type"] == Platform.LIGHT and (config.get("default") or entity_key in extra_switches): devs.append(MideaLight(device, entity_key)) async_add_entities(devs) @@ -104,9 +110,7 @@ def turn_on(self, **kwargs: Any): if key == ATTR_BRIGHTNESS: self._device.set_attribute(attr=X13Attributes.brightness, value=value) if key == ATTR_COLOR_TEMP: - self._device.set_attribute( - attr=X13Attributes.color_temperature, value=round(1000000 / value) - ) + self._device.set_attribute(attr=X13Attributes.color_temperature, value=round(1000000 / value)) if key == ATTR_EFFECT: self._device.set_attribute(attr=X13Attributes.effect, value=value) @@ -117,6 +121,4 @@ def update_state(self, status): try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) + _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") diff --git a/custom_components/midea_ac_lan/lock.py b/custom_components/midea_ac_lan/lock.py old mode 100755 new mode 100644 index f62eeb54..df43e084 --- a/custom_components/midea_ac_lan/lock.py +++ b/custom_components/midea_ac_lan/lock.py @@ -1,15 +1,23 @@ -from homeassistant.components.lock import LockEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +from .midea_devices import MIDEA_DEVICES +from homeassistant.components.lock import LockEntity +from homeassistant.const import ( + Platform, + CONF_DEVICE_ID, + CONF_SWITCHES +) +from .const import ( + DOMAIN, + DEVICES, +) async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) + extra_switches = config_entry.options.get( + CONF_SWITCHES, [] + ) locks = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.LOCK and entity_key in extra_switches: diff --git a/custom_components/midea_ac_lan/midea/backports/myenum.py b/custom_components/midea_ac_lan/midea/backports/myenum.py old mode 100755 new mode 100644 index c4de8762..c256cb8d --- a/custom_components/midea_ac_lan/midea/backports/myenum.py +++ b/custom_components/midea_ac_lan/midea/backports/myenum.py @@ -1,5 +1,4 @@ """Enum backports from standard lib.""" - from __future__ import annotations from enum import Enum @@ -32,4 +31,4 @@ def _generate_next_value_( We may revisit this when it's very clear that Python 3.11's `StrEnum.auto()` behavior will no longer change. """ - raise TypeError("auto() is not supported by this implementation") + raise TypeError("auto() is not supported by this implementation") \ No newline at end of file diff --git a/custom_components/midea_ac_lan/midea/core/cloud.py b/custom_components/midea_ac_lan/midea/core/cloud.py old mode 100755 new mode 100644 index f4eb2eb0..12e396c4 --- a/custom_components/midea_ac_lan/midea/core/cloud.py +++ b/custom_components/midea_ac_lan/midea/core/cloud.py @@ -1,19 +1,12 @@ -import base64 -import datetime -import json import logging import time -from secrets import token_hex +import datetime +import json +import base64 from threading import Lock - from aiohttp import ClientSession - -from .security import ( - CloudSecurity, - MeijuCloudSecurity, - MideaAirSecurity, - MSmartCloudSecurity, -) +from secrets import token_hex +from .security import CloudSecurity, MeijuCloudSecurity, MSmartCloudSecurity, MideaAirSecurity _LOGGER = logging.getLogger(__name__) @@ -23,22 +16,16 @@ "app_id": "900", "app_key": "46579c15", "login_key": "ad0ee21d48a64bf49f4fb583ab76e799", - "iot_key": bytes.fromhex( - format(9795516279659324117647275084689641883661667, "x") - ).decode(), - "hmac_key": bytes.fromhex( - format(117390035944627627450677220413733956185864939010425, "x") - ).decode(), + "iot_key": bytes.fromhex(format(9795516279659324117647275084689641883661667, 'x')).decode(), + "hmac_key": bytes.fromhex(format(117390035944627627450677220413733956185864939010425, 'x')).decode(), "api_url": "https://mp-prod.smartmidea.net/mas/v5/app/proxy?alias=", }, "MSmartHome": { "class_name": "MSmartHomeCloud", "app_id": "1010", "app_key": "ac21b9f9cbfe4ca5a88562ef25e2b768", - "iot_key": bytes.fromhex(format(7882822598523843940, "x")).decode(), - "hmac_key": bytes.fromhex( - format(117390035944627627450677220413733956185864939010425, "x") - ).decode(), + "iot_key": bytes.fromhex(format(7882822598523843940, 'x')).decode(), + "hmac_key": bytes.fromhex(format(117390035944627627450677220413733956185864939010425, 'x')).decode(), "api_url": "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=", }, "Midea Air": { @@ -58,28 +45,28 @@ "app_id": "1005", "app_key": "434a209a5ce141c3b726de067835d7f0", "api_url": "https://mapp.appsmb.com", - }, + } } default_keys = { 99: { "token": "ee755a84a115703768bcc7c6c13d3d629aa416f1e2fd798beb9f78cbb1381d09" - "1cc245d7b063aad2a900e5b498fbd936c811f5d504b2e656d4f33b3bbc6d1da3", - "key": "ed37bd31558a4b039aaf4e7a7a59aa7a75fd9101682045f69baf45d28380ae5c", + "1cc245d7b063aad2a900e5b498fbd936c811f5d504b2e656d4f33b3bbc6d1da3", + "key": "ed37bd31558a4b039aaf4e7a7a59aa7a75fd9101682045f69baf45d28380ae5c" } } class MideaCloud: def __init__( - self, - session: ClientSession, - security: CloudSecurity, - app_id: str, - app_key: str, - account: str, - password: str, - api_url: str, + self, + session: ClientSession, + security: CloudSecurity, + app_id: str, + app_key: str, + account: str, + password: str, + api_url: str ): self._device_id = CloudSecurity.get_deviceid(account) self._session = session @@ -100,36 +87,38 @@ def _make_general_data(self): async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: header = header or {} if not data.get("reqId"): - data.update({"reqId": token_hex(16)}) + data.update({ + "reqId": token_hex(16) + }) if not data.get("stamp"): - data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) + data.update({ + "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S") + }) random = str(int(time.time())) url = self._api_url + endpoint dump_data = json.dumps(data) sign = self._security.sign("", dump_data, random) - header.update( - { - "content-type": "application/json; charset=utf-8", - "secretVersion": "1", - "sign": sign, - "random": random, - } - ) + header.update({ + "content-type": "application/json; charset=utf-8", + "secretVersion": "1", + "sign": sign, + "random": random, + }) if self._uid is not None: - header.update({"uid": self._uid}) + header.update({ + "uid": self._uid + }) if self._access_token is not None: - header.update({"accessToken": self._access_token}) + header.update({ + "accessToken": self._access_token + }) response: dict = {"code": -1} for i in range(0, 3): try: with self._api_lock: - r = await self._session.request( - "POST", url, headers=header, data=dump_data, timeout=10 - ) + r = await self._session.request("POST", url, headers=header, data=dump_data, timeout=10) raw = await r.read() - _LOGGER.debug( - f"Midea cloud API url: {url}, data: {data}, response: {raw}" - ) + _LOGGER.debug(f"Midea cloud API url: {url}, data: {data}, response: {raw}") response = json.loads(raw) break except Exception as e: @@ -140,9 +129,12 @@ async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | N async def _get_login_id(self) -> str | None: data = self._make_general_data() - data.update({"loginAccount": f"{self._account}"}) + data.update({ + "loginAccount": f"{self._account}" + }) if response := await self._api_request( - endpoint="/v1/user/login/id/get", data=data + endpoint="/v1/user/login/id/get", + data=data ): return response.get("loginId") return None @@ -155,16 +147,19 @@ async def get_keys(self, appliance_id: int): for method in [1, 2]: udp_id = self._security.get_udp_id(appliance_id, method) data = self._make_general_data() - data.update({"udpid": udp_id}) + data.update({ + "udpid": udp_id + }) response = await self._api_request( - endpoint="/v1/iot/secure/getToken", data=data + endpoint="/v1/iot/secure/getToken", + data=data ) if response and "tokenlist" in response: for token in response["tokenlist"]: if token["udpId"] == udp_id: result[method] = { "token": token["token"].lower(), - "key": token["key"].lower(), + "key": token["key"].lower() } result.update(default_keys) return result @@ -182,23 +177,22 @@ async def get_device_info(self, device_id: int): return None async def download_lua( - self, - path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", + self, path: str, + device_type: int, + sn: str, + model_number: str | None, + manufacturer_code: str = "0000", ): raise NotImplementedError() class MeijuCloud(MideaCloud): def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, + self, + cloud_name: str, + session: ClientSession, + account: str, + password: str, ): super().__init__( session=session, @@ -211,7 +205,7 @@ def __init__( app_key=clouds[cloud_name]["app_key"], account=account, password=password, - api_url=clouds[cloud_name]["api_url"], + api_url=clouds[cloud_name]["api_url"] ) async def login(self) -> bool: @@ -222,31 +216,30 @@ async def login(self) -> bool: "iotData": { "clientType": 1, "deviceId": self._device_id, - "iampwd": self._security.encrypt_iam_password( - self._login_id, self._password - ), + "iampwd": self._security.encrypt_iam_password(self._login_id, self._password), "iotAppId": self._app_id, "loginAccount": self._account, - "password": self._security.encrypt_password( - self._login_id, self._password - ), + "password": self._security.encrypt_password(self._login_id, self._password), "reqId": token_hex(16), - "stamp": stamp, + "stamp": stamp }, "data": { "appKey": self._app_key, "deviceId": self._device_id, - "platform": 2, + "platform": 2 }, "timestamp": stamp, - "stamp": stamp, + "stamp": stamp } if response := await self._api_request( - endpoint="/mj/user/login", data=data + endpoint="/mj/user/login", + data=data ): self._access_token = response["mdata"]["accessToken"] self._security.set_aes_keys( - self._security.aes_decrypt_with_fixed_key(response["key"]), None + self._security.aes_decrypt_with_fixed_key( + response["key"] + ), None ) return True @@ -254,18 +247,24 @@ async def login(self) -> bool: async def list_home(self): if response := await self._api_request( - endpoint="/v1/homegroup/list/get", data={} + endpoint="/v1/homegroup/list/get", + data={} ): homes = {} for home in response["homeList"]: - homes.update({int(home["homegroupId"]): home["name"]}) + homes.update({ + int(home["homegroupId"]): home["name"] + }) return homes return None async def list_appliances(self, home_id) -> dict | None: - data = {"homegroupId": home_id} + data = { + "homegroupId": home_id + } if response := await self._api_request( - endpoint="/v1/appliance/home/list/get", data=data + endpoint="/v1/appliance/home/list/get", + data=data ): appliances = {} for home in response.get("homeList") or []: @@ -278,37 +277,28 @@ async def list_appliances(self, home_id) -> dict | None: device_info = { "name": appliance.get("name"), "type": int(appliance.get("type"), 16), - "sn": ( - self._security.aes_decrypt(appliance.get("sn")) - if appliance.get("sn") - else "" - ), + "sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "", "sn8": appliance.get("sn8", "00000000"), "model_number": model_number, - "manufacturer_code": appliance.get( - "enterpriseCode", "0000" - ), + "manufacturer_code":appliance.get("enterpriseCode", "0000"), "model": appliance.get("productModel"), "online": appliance.get("onlineStatus") == "1", } - if ( - device_info.get("sn8") is None - or len(device_info.get("sn8")) == 0 - ): + if device_info.get("sn8") is None or len(device_info.get("sn8")) == 0: device_info["sn8"] = "00000000" - if ( - device_info.get("model") is None - or len(device_info.get("model")) == 0 - ): + if device_info.get("model") is None or len(device_info.get("model")) == 0: device_info["model"] = device_info["sn8"] appliances[int(appliance["applianceCode"])] = device_info return appliances return None async def get_device_info(self, device_id: int): - data = {"applianceCode": device_id} + data = { + "applianceCode": device_id + } if response := await self._api_request( - endpoint="/v1/appliance/info/get", data=data + endpoint="/v1/appliance/info/get", + data=data ): try: model_number = int(response.get("modelNumber", 0)) @@ -317,11 +307,7 @@ async def get_device_info(self, device_id: int): device_info = { "name": response.get("name"), "type": int(response.get("type"), 16), - "sn": ( - self._security.aes_decrypt(response.get("sn")) - if response.get("sn") - else "" - ), + "sn": self._security.aes_decrypt(response.get("sn")) if response.get("sn") else "", "sn8": response.get("sn8", "00000000"), "model_number": model_number, "manufacturer_code": response.get("enterpriseCode", "0000"), @@ -336,32 +322,30 @@ async def get_device_info(self, device_id: int): return None async def download_lua( - self, - path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", + self, path: str, + device_type: int, + sn: str, + model_number: str | None, + manufacturer_code: str = "0000", ): data = { "applianceSn": sn, "applianceType": "0x%02X" % device_type, "applianceMFCode": manufacturer_code, - "version": "0", - "iotAppId": self._app_id, + 'version': "0", + "iotAppId": self._app_id } fnm = None if response := await self._api_request( - endpoint="/v1/appliance/protocol/lua/luaGet", data=data + endpoint="/v1/appliance/protocol/lua/luaGet", + data=data ): res = await self._session.get(response["url"]) if res.status == 200: lua = await res.text() if lua: - stream = ( - 'local bit = require "bit"\n' - + self._security.aes_decrypt_with_fixed_key(lua) - ) + stream = ('local bit = require "bit"\n' + + self._security.aes_decrypt_with_fixed_key(lua)) stream = stream.replace("\r\n", "\n") fnm = f"{path}/{response['fileName']}" with open(fnm, "w") as fp: @@ -371,11 +355,11 @@ async def download_lua( class MSmartHomeCloud(MideaCloud): def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, + self, + cloud_name: str, + session: ClientSession, + account: str, + password: str, ): super().__init__( session=session, @@ -388,7 +372,7 @@ def __init__( app_key=clouds[cloud_name]["app_key"], account=account, password=password, - api_url=clouds[cloud_name]["api_url"], + api_url=clouds[cloud_name]["api_url"] ) self._auth_base = base64.b64encode( f"{self._app_key}:{clouds['MSmartHome']['iot_key']}".encode("ascii") @@ -405,22 +389,27 @@ def _make_general_data(self): "reqId": token_hex(16), "uid": self._uid, "clientType": "1", - "appId": self._app_id, + "appId": self._app_id } async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: header = header or {} - header.update( - {"x-recipe-app": self._app_id, "authorization": f"Basic {self._auth_base}"} - ) + header.update({ + "x-recipe-app": self._app_id, + "authorization": f"Basic {self._auth_base}" + }) return await super()._api_request(endpoint, data, header) async def _re_route(self): data = self._make_general_data() - data.update({"userType": "0", "userName": f"{self._account}"}) + data.update({ + "userType": "0", + "userName": f"{self._account}" + }) if response := await self._api_request( - endpoint="/v1/multicloud/platform/user/route", data=data + endpoint="/v1/multicloud/platform/user/route", + data=data ): if api_url := response.get("masUrl"): self._api_url = api_url @@ -432,42 +421,36 @@ async def login(self) -> bool: iot_data = self._make_general_data() iot_data.pop("uid") stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - iot_data.update( - { - "iampwd": self._security.encrypt_iam_password( - self._login_id, self._password - ), - "loginAccount": self._account, - "password": self._security.encrypt_password( - self._login_id, self._password - ), - "stamp": stamp, - } - ) + iot_data.update({ + "iampwd": self._security.encrypt_iam_password(self._login_id, self._password), + "loginAccount": self._account, + "password": self._security.encrypt_password(self._login_id, self._password), + "stamp": stamp + }) data = { "iotData": iot_data, "data": { "appKey": self._app_key, "deviceId": self._device_id, - "platform": "2", + "platform": "2" }, - "stamp": stamp, + "stamp": stamp } if response := await self._api_request( - endpoint="/mj/user/login", data=data + endpoint="/mj/user/login", + data=data ): self._uid = response["uid"] self._access_token = response["mdata"]["accessToken"] - self._security.set_aes_keys( - response["accessToken"], response["randomData"] - ) + self._security.set_aes_keys(response["accessToken"], response["randomData"]) return True return False async def list_appliances(self, home_id) -> dict | None: data = self._make_general_data() if response := await self._api_request( - endpoint="/v1/appliance/user/list/get", data=data + endpoint="/v1/appliance/user/list/get", + data=data ): appliances = {} for appliance in response["list"]: @@ -478,28 +461,21 @@ async def list_appliances(self, home_id) -> dict | None: device_info = { "name": appliance.get("name"), "type": int(appliance.get("type"), 16), - "sn": ( - self._security.aes_decrypt(appliance.get("sn")) - if appliance.get("sn") - else "" - ), + "sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "", "sn8": "", "model_number": model_number, - "manufacturer_code": appliance.get("enterpriseCode", "0000"), + "manufacturer_code":appliance.get("enterpriseCode", "0000"), "model": "", "online": appliance.get("onlineStatus") == "1", } - device_info["sn8"] = ( - device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" - ) + device_info["sn8"] = device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" device_info["model"] = device_info.get("sn8") appliances[int(appliance["id"])] = device_info return appliances return None async def download_lua( - self, - path: str, + self, path: str, device_type: int, sn: str, model_number: str | None, @@ -514,24 +490,21 @@ async def download_lua( "applianceMFCode": manufacturer_code, "applianceType": "0x%02X" % device_type, "modelNumber": model_number, - "applianceSn": self._security.aes_encrypt_with_fixed_key( - sn.encode("ascii") - ).hex(), + "applianceSn": self._security.aes_encrypt_with_fixed_key(sn.encode("ascii")).hex(), "version": "0", - "encryptedType ": "2", + "encryptedType ": "2" } fnm = None if response := await self._api_request( - endpoint="/v2/luaEncryption/luaGet", data=data + endpoint="/v2/luaEncryption/luaGet", + data=data ): res = await self._session.get(response["url"]) if res.status == 200: lua = await res.text() if lua: - stream = ( - 'local bit = require "bit"\n' - + self._security.aes_decrypt_with_fixed_key(lua) - ) + stream = ('local bit = require "bit"\n' + + self._security.aes_decrypt_with_fixed_key(lua)) stream = stream.replace("\r\n", "\n") fnm = f"{path}/{response['fileName']}" with open(fnm, "w") as fp: @@ -541,20 +514,22 @@ async def download_lua( class MideaAirCloud(MideaCloud): def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, + self, + cloud_name: str, + session: ClientSession, + account: str, + password: str, ): super().__init__( session=session, - security=MideaAirSecurity(login_key=clouds[cloud_name]["app_key"]), + security=MideaAirSecurity( + login_key=clouds[cloud_name]["app_key"] + ), app_id=clouds[cloud_name]["app_id"], app_key=clouds[cloud_name]["app_key"], account=account, password=password, - api_url=clouds[cloud_name]["api_url"], + api_url=clouds[cloud_name]["api_url"] ) self._session_id = None @@ -566,37 +541,45 @@ def _make_general_data(self): "deviceId": self._device_id, "reqId": token_hex(16), "clientType": "1", - "appId": self._app_id, + "appId": self._app_id } if self._session_id is not None: - data.update({"sessionId": self._session_id}) + data.update({ + "sessionId": self._session_id + }) return data async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: header = header or {} if not data.get("reqId"): - data.update({"reqId": token_hex(16)}) + data.update({ + "reqId": token_hex(16) + }) if not data.get("stamp"): - data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) + data.update({ + "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S") + }) url = self._api_url + endpoint sign = self._security.sign(url, data, "") - data.update({"sign": sign}) + data.update({ + "sign": sign + }) if self._uid is not None: - header.update({"uid": self._uid}) + header.update({ + "uid": self._uid + }) if self._access_token is not None: - header.update({"accessToken": self._access_token}) + header.update({ + "accessToken": self._access_token + }) response: dict = {"code": -1} for i in range(0, 3): try: with self._api_lock: - r = await self._session.request( - "POST", url, headers=header, data=data, timeout=10 - ) + r = await self._session.request("POST", url, headers=header, data=data, timeout=10) raw = await r.read() - _LOGGER.debug( - f"Midea cloud API url: {url}, data: {data}, response: {raw}" - ) + _LOGGER.debug(f"Midea cloud API url: {url}, data: {data}, response: {raw}") response = json.loads(raw) break except Exception as e: @@ -609,16 +592,13 @@ async def login(self) -> bool: if login_id := await self._get_login_id(): self._login_id = login_id data = self._make_general_data() - data.update( - { - "loginAccount": self._account, - "password": self._security.encrypt_password( - self._login_id, self._password - ), - } - ) + data.update({ + "loginAccount": self._account, + "password": self._security.encrypt_password(self._login_id, self._password), + }) if response := await self._api_request( - endpoint="/v1/user/login", data=data + endpoint="/v1/user/login", + data=data ): self._access_token = response["accessToken"] self._uid = response["userId"] @@ -629,7 +609,8 @@ async def login(self) -> bool: async def list_appliances(self, home_id) -> dict | None: data = self._make_general_data() if response := await self._api_request( - endpoint="/v1/appliance/user/list/get", data=data + endpoint="/v1/appliance/user/list/get", + data=data ): appliances = {} for appliance in response["list"]: @@ -643,25 +624,24 @@ async def list_appliances(self, home_id) -> dict | None: "sn": appliance.get("sn"), "sn8": "", "model_number": model_number, - "manufacturer_code": appliance.get("enterpriseCode", "0000"), + "manufacturer_code":appliance.get("enterpriseCode", "0000"), "model": "", "online": appliance.get("onlineStatus") == "1", } - device_info["sn8"] = ( - device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" - ) + device_info["sn8"] = device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" device_info["model"] = device_info.get("sn8") appliances[int(appliance["id"])] = device_info return appliances return None -def get_midea_cloud( - cloud_name: str, session: ClientSession, account: str, password: str -) -> MideaCloud | None: +def get_midea_cloud(cloud_name: str, session: ClientSession, account: str, password: str) -> MideaCloud | None: cloud = None if cloud_name in clouds.keys(): cloud = globals()[clouds[cloud_name]["class_name"]]( - cloud_name=cloud_name, session=session, account=account, password=password + cloud_name=cloud_name, + session=session, + account=account, + password=password ) return cloud diff --git a/custom_components/midea_ac_lan/midea/core/crc8.py b/custom_components/midea_ac_lan/midea/core/crc8.py index 8ce11bcf..6d87d33c 100644 --- a/custom_components/midea_ac_lan/midea/core/crc8.py +++ b/custom_components/midea_ac_lan/midea/core/crc8.py @@ -1,260 +1,36 @@ crc8_854_table = [ - 0x00, - 0x5E, - 0xBC, - 0xE2, - 0x61, - 0x3F, - 0xDD, - 0x83, - 0xC2, - 0x9C, - 0x7E, - 0x20, - 0xA3, - 0xFD, - 0x1F, - 0x41, - 0x9D, - 0xC3, - 0x21, - 0x7F, - 0xFC, - 0xA2, - 0x40, - 0x1E, - 0x5F, - 0x01, - 0xE3, - 0xBD, - 0x3E, - 0x60, - 0x82, - 0xDC, - 0x23, - 0x7D, - 0x9F, - 0xC1, - 0x42, - 0x1C, - 0xFE, - 0xA0, - 0xE1, - 0xBF, - 0x5D, - 0x03, - 0x80, - 0xDE, - 0x3C, - 0x62, - 0xBE, - 0xE0, - 0x02, - 0x5C, - 0xDF, - 0x81, - 0x63, - 0x3D, - 0x7C, - 0x22, - 0xC0, - 0x9E, - 0x1D, - 0x43, - 0xA1, - 0xFF, - 0x46, - 0x18, - 0xFA, - 0xA4, - 0x27, - 0x79, - 0x9B, - 0xC5, - 0x84, - 0xDA, - 0x38, - 0x66, - 0xE5, - 0xBB, - 0x59, - 0x07, - 0xDB, - 0x85, - 0x67, - 0x39, - 0xBA, - 0xE4, - 0x06, - 0x58, - 0x19, - 0x47, - 0xA5, - 0xFB, - 0x78, - 0x26, - 0xC4, - 0x9A, - 0x65, - 0x3B, - 0xD9, - 0x87, - 0x04, - 0x5A, - 0xB8, - 0xE6, - 0xA7, - 0xF9, - 0x1B, - 0x45, - 0xC6, - 0x98, - 0x7A, - 0x24, - 0xF8, - 0xA6, - 0x44, - 0x1A, - 0x99, - 0xC7, - 0x25, - 0x7B, - 0x3A, - 0x64, - 0x86, - 0xD8, - 0x5B, - 0x05, - 0xE7, - 0xB9, - 0x8C, - 0xD2, - 0x30, - 0x6E, - 0xED, - 0xB3, - 0x51, - 0x0F, - 0x4E, - 0x10, - 0xF2, - 0xAC, - 0x2F, - 0x71, - 0x93, - 0xCD, - 0x11, - 0x4F, - 0xAD, - 0xF3, - 0x70, - 0x2E, - 0xCC, - 0x92, - 0xD3, - 0x8D, - 0x6F, - 0x31, - 0xB2, - 0xEC, - 0x0E, - 0x50, - 0xAF, - 0xF1, - 0x13, - 0x4D, - 0xCE, - 0x90, - 0x72, - 0x2C, - 0x6D, - 0x33, - 0xD1, - 0x8F, - 0x0C, - 0x52, - 0xB0, - 0xEE, - 0x32, - 0x6C, - 0x8E, - 0xD0, - 0x53, - 0x0D, - 0xEF, - 0xB1, - 0xF0, - 0xAE, - 0x4C, - 0x12, - 0x91, - 0xCF, - 0x2D, - 0x73, - 0xCA, - 0x94, - 0x76, - 0x28, - 0xAB, - 0xF5, - 0x17, - 0x49, - 0x08, - 0x56, - 0xB4, - 0xEA, - 0x69, - 0x37, - 0xD5, - 0x8B, - 0x57, - 0x09, - 0xEB, - 0xB5, - 0x36, - 0x68, - 0x8A, - 0xD4, - 0x95, - 0xCB, - 0x29, - 0x77, - 0xF4, - 0xAA, - 0x48, - 0x16, - 0xE9, - 0xB7, - 0x55, - 0x0B, - 0x88, - 0xD6, - 0x34, - 0x6A, - 0x2B, - 0x75, - 0x97, - 0xC9, - 0x4A, - 0x14, - 0xF6, - 0xA8, - 0x74, - 0x2A, - 0xC8, - 0x96, - 0x15, - 0x4B, - 0xA9, - 0xF7, - 0xB6, - 0xE8, - 0x0A, - 0x54, - 0xD7, - 0x89, - 0x6B, - 0x35, + 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, + 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, + 0x9D, 0xC3, 0x21, 0x7F, 0xFC, 0xA2, 0x40, 0x1E, + 0x5F, 0x01, 0xE3, 0xBD, 0x3E, 0x60, 0x82, 0xDC, + 0x23, 0x7D, 0x9F, 0xC1, 0x42, 0x1C, 0xFE, 0xA0, + 0xE1, 0xBF, 0x5D, 0x03, 0x80, 0xDE, 0x3C, 0x62, + 0xBE, 0xE0, 0x02, 0x5C, 0xDF, 0x81, 0x63, 0x3D, + 0x7C, 0x22, 0xC0, 0x9E, 0x1D, 0x43, 0xA1, 0xFF, + 0x46, 0x18, 0xFA, 0xA4, 0x27, 0x79, 0x9B, 0xC5, + 0x84, 0xDA, 0x38, 0x66, 0xE5, 0xBB, 0x59, 0x07, + 0xDB, 0x85, 0x67, 0x39, 0xBA, 0xE4, 0x06, 0x58, + 0x19, 0x47, 0xA5, 0xFB, 0x78, 0x26, 0xC4, 0x9A, + 0x65, 0x3B, 0xD9, 0x87, 0x04, 0x5A, 0xB8, 0xE6, + 0xA7, 0xF9, 0x1B, 0x45, 0xC6, 0x98, 0x7A, 0x24, + 0xF8, 0xA6, 0x44, 0x1A, 0x99, 0xC7, 0x25, 0x7B, + 0x3A, 0x64, 0x86, 0xD8, 0x5B, 0x05, 0xE7, 0xB9, + 0x8C, 0xD2, 0x30, 0x6E, 0xED, 0xB3, 0x51, 0x0F, + 0x4E, 0x10, 0xF2, 0xAC, 0x2F, 0x71, 0x93, 0xCD, + 0x11, 0x4F, 0xAD, 0xF3, 0x70, 0x2E, 0xCC, 0x92, + 0xD3, 0x8D, 0x6F, 0x31, 0xB2, 0xEC, 0x0E, 0x50, + 0xAF, 0xF1, 0x13, 0x4D, 0xCE, 0x90, 0x72, 0x2C, + 0x6D, 0x33, 0xD1, 0x8F, 0x0C, 0x52, 0xB0, 0xEE, + 0x32, 0x6C, 0x8E, 0xD0, 0x53, 0x0D, 0xEF, 0xB1, + 0xF0, 0xAE, 0x4C, 0x12, 0x91, 0xCF, 0x2D, 0x73, + 0xCA, 0x94, 0x76, 0x28, 0xAB, 0xF5, 0x17, 0x49, + 0x08, 0x56, 0xB4, 0xEA, 0x69, 0x37, 0xD5, 0x8B, + 0x57, 0x09, 0xEB, 0xB5, 0x36, 0x68, 0x8A, 0xD4, + 0x95, 0xCB, 0x29, 0x77, 0xF4, 0xAA, 0x48, 0x16, + 0xE9, 0xB7, 0x55, 0x0B, 0x88, 0xD6, 0x34, 0x6A, + 0x2B, 0x75, 0x97, 0xC9, 0x4A, 0x14, 0xF6, 0xA8, + 0x74, 0x2A, 0xC8, 0x96, 0x15, 0x4B, 0xA9, 0xF7, + 0xB6, 0xE8, 0x0A, 0x54, 0xD7, 0x89, 0x6B, 0x35 ] diff --git a/custom_components/midea_ac_lan/midea/core/device.py b/custom_components/midea_ac_lan/midea/core/device.py index 3509f616..1eaa24f4 100644 --- a/custom_components/midea_ac_lan/midea/core/device.py +++ b/custom_components/midea_ac_lan/midea/core/device.py @@ -1,27 +1,20 @@ import threading - try: from enum import StrEnum except ImportError: from ..backports.myenum import StrEnum - -import logging -import socket -import time from enum import IntEnum - +from .security import LocalSecurity, MSGTYPE_HANDSHAKE_REQUEST, MSGTYPE_ENCRYPTED_REQUEST +from .packet_builder import PacketBuilder from .message import ( - MessageApplianceResponse, - MessageQueryAppliance, - MessageQuestCustom, MessageType, + MessageQuestCustom, + MessageQueryAppliance, + MessageApplianceResponse ) -from .packet_builder import PacketBuilder -from .security import ( - MSGTYPE_ENCRYPTED_REQUEST, - MSGTYPE_HANDSHAKE_REQUEST, - LocalSecurity, -) +import socket +import logging +import time _LOGGER = logging.getLogger(__name__) @@ -49,20 +42,18 @@ class ParseMessageResult(IntEnum): class MiedaDevice(threading.Thread): - def __init__( - self, - name: str, - device_id: int, - device_type: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - attributes: dict, - ): + def __init__(self, + name: str, + device_id: int, + device_type: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + attributes: dict): threading.Thread.__init__(self) self._attributes = attributes if attributes else {} self._socket = None @@ -131,9 +122,7 @@ def connect(self, refresh_status=True): try: self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.settimeout(10) - _LOGGER.debug( - f"[{self._device_id}] Connecting to {self._ip_address}:{self._port}" - ) + _LOGGER.debug(f"[{self._device_id}] Connecting to {self._ip_address}:{self._port}") self._socket.connect((self._ip_address, self._port)) _LOGGER.debug(f"[{self._device_id}] Connected") if self._protocol == 3: @@ -154,21 +143,20 @@ def connect(self, refresh_status=True): except RefreshFailed: _LOGGER.debug(f"[{self._device_id}] Refresh status is timed out") except Exception as e: - _LOGGER.error( - f"[{self._device_id}] Unknown error: {e.__traceback__.tb_frame.f_globals['__file__']}, " - f"{e.__traceback__.tb_lineno}, {repr(e)}" - ) + _LOGGER.error(f"[{self._device_id}] Unknown error: {e.__traceback__.tb_frame.f_globals['__file__']}, " + f"{e.__traceback__.tb_lineno}, {repr(e)}") self.enable_device(False) return False def authenticate(self): - request = self._security.encode_8370(self._token, MSGTYPE_HANDSHAKE_REQUEST) + request = self._security.encode_8370( + self._token, MSGTYPE_HANDSHAKE_REQUEST) _LOGGER.debug(f"[{self._device_id}] Handshaking") self._socket.send(request) response = self._socket.recv(512) if len(response) < 20: raise AuthException() - response = response[8:72] + response = response[8: 72] self._security.tcp_key(response, self._key) def send_message(self, data): @@ -181,9 +169,7 @@ def send_message_v2(self, data): if self._socket is not None: self._socket.send(data) else: - _LOGGER.debug( - f"[{self._device_id}] Send failure, device disconnected, data: {data.hex()}" - ) + _LOGGER.debug(f"[{self._device_id}] Send failure, device disconnected, data: {data.hex()}") def send_message_v3(self, data, msg_type=MSGTYPE_ENCRYPTED_REQUEST): data = self._security.encode_8370(data, msg_type) @@ -219,10 +205,8 @@ def refresh_status(self, wait_response=False): except socket.timeout: error_count += 1 self._unsupported_protocol.append(cmd.__class__.__name__) - _LOGGER.debug( - f"[{self._device_id}] Does not supports " - f"the protocol {cmd.__class__.__name__}, ignored" - ) + _LOGGER.debug(f"[{self._device_id}] Does not supports " + f"the protocol {cmd.__class__.__name__}, ignored") except ResponseException: error_count += 1 else: @@ -236,9 +220,7 @@ def pre_process_message(self, msg): self._appliance_query = False _LOGGER.debug(f"[{self.device_id}] Received: {message}") self._protocol_version = message.protocol_version - _LOGGER.debug( - f"[{self._device_id}] Device protocol version: {self._protocol_version}" - ) + _LOGGER.debug(f"[{self._device_id}] Device protocol version: {self._protocol_version}") return False return True @@ -270,13 +252,9 @@ def parse_message(self, msg): if len(status) > 0: self.update_all(status) else: - _LOGGER.debug( - f"[{self._device_id}] Unidentified protocol" - ) + _LOGGER.debug(f"[{self._device_id}] Unidentified protocol") except Exception as e: - _LOGGER.error( - f"[{self._device_id}] Error in process message, msg = {e}" - ) + _LOGGER.error(f"[{self._device_id}] Error in process message, msg = {decrypted.hex()}") else: _LOGGER.warning( f"[{self._device_id}] Illegal payload, " @@ -300,16 +278,12 @@ def process_message(self, msg): raise NotImplementedError def send_command(self, cmd_type, cmd_body: bytearray): - cmd = MessageQuestCustom( - self._device_type, self._protocol_version, cmd_type, cmd_body - ) + cmd = MessageQuestCustom(self._device_type, self._protocol_version, cmd_type, cmd_body) try: self.build_send(cmd) except socket.error as e: - _LOGGER.debug( - f"[{self._device_id}] Interface send_command failure, {repr(e)}, " - f"cmd_type: {cmd_type}, cmd_body: {cmd_body.hex()}" - ) + _LOGGER.debug(f"[{self._device_id}] Interface send_command failure, {repr(e)}, " + f"cmd_type: {cmd_type}, cmd_body: {cmd_body.hex()}") def send_heartbeat(self): msg = PacketBuilder(self._device_id, bytearray([0x00])).finalize(msg_type=0) @@ -399,10 +373,8 @@ def run(self): self.close_socket() break except Exception as e: - _LOGGER.error( - f"[{self._device_id}] Unknown error :{e.__traceback__.tb_frame.f_globals['__file__']}, " - f"{e.__traceback__.tb_lineno}, {repr(e)}" - ) + _LOGGER.error(f"[{self._device_id}] Unknown error :{e.__traceback__.tb_frame.f_globals['__file__']}, " + f"{e.__traceback__.tb_lineno}, {repr(e)}") self.close_socket() break diff --git a/custom_components/midea_ac_lan/midea/core/discover.py b/custom_components/midea_ac_lan/midea/core/discover.py index c8005f6c..a4be3e26 100644 --- a/custom_components/midea_ac_lan/midea/core/discover.py +++ b/custom_components/midea_ac_lan/midea/core/discover.py @@ -1,15 +1,8 @@ -""" -discover.py -""" - import logging import socket -from ipaddress import IPv4Network - import ifaddr - +from ipaddress import IPv4Network from .security import LocalSecurity - try: import xml.etree.cElementTree as ET except ImportError: @@ -17,149 +10,30 @@ _LOGGER = logging.getLogger(__name__) -BROADCAST_MSG = bytearray( - [ - 0x5A, - 0x5A, - 0x01, - 0x11, - 0x48, - 0x00, - 0x92, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x7F, - 0x75, - 0xBD, - 0x6B, - 0x3E, - 0x4F, - 0x8B, - 0x76, - 0x2E, - 0x84, - 0x9C, - 0x6E, - 0x57, - 0x8D, - 0x65, - 0x90, - 0x03, - 0x6E, - 0x9D, - 0x43, - 0x42, - 0xA5, - 0x0F, - 0x1F, - 0x56, - 0x9E, - 0xB8, - 0xEC, - 0x91, - 0x8E, - 0x92, - 0xE5, - ] -) - -DEVICE_INFO_MSG = bytearray( - [ - 0x5A, - 0x5A, - 0x15, - 0x00, - 0x00, - 0x38, - 0x00, - 0x04, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x27, - 0x33, - 0x05, - 0x13, - 0x06, - 0x14, - 0x14, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x03, - 0xE8, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0xCA, - 0x8D, - 0x9B, - 0xF9, - 0xA0, - 0x30, - 0x1A, - 0xE3, - 0xB7, - 0xE4, - 0x2D, - 0x53, - 0x49, - 0x47, - 0x62, - 0xBE, - ] -) +BROADCAST_MSG = bytearray([ + 0x5a, 0x5a, 0x01, 0x11, 0x48, 0x00, 0x92, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7f, 0x75, 0xbd, 0x6b, 0x3e, 0x4f, 0x8b, 0x76, + 0x2e, 0x84, 0x9c, 0x6e, 0x57, 0x8d, 0x65, 0x90, + 0x03, 0x6e, 0x9d, 0x43, 0x42, 0xa5, 0x0f, 0x1f, + 0x56, 0x9e, 0xb8, 0xec, 0x91, 0x8e, 0x92, 0xe5 +]) + +DEVICE_INFO_MSG = bytearray([ + 0x5a, 0x5a, 0x15, 0x00, 0x00, 0x38, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x33, 0x05, + 0x13, 0x06, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xca, 0x8d, 0x9b, 0xf9, 0xa0, 0x30, 0x1a, 0xe3, + 0xb7, 0xe4, 0x2d, 0x53, 0x49, 0x47, 0x62, 0xbe +]) def discover(discover_type=None, ip_address=None): - """ - discover - """ if discover_type is None: discover_type = [] security = LocalSecurity() @@ -183,9 +57,7 @@ def discover(discover_type=None, ip_address=None): data, addr = sock.recvfrom(512) ip = addr[0] _LOGGER.debug(f"Received response from {addr}: {data.hex()}") - if len(data) >= 104 and ( - data[:2].hex() == "5a5a" or data[8:10].hex() == "5a5a" - ): + if len(data) >= 104 and (data[:2].hex() == "5a5a" or data[8:10].hex() == "5a5a"): if data[:2].hex() == "5a5a": protocol = 2 elif data[:2].hex() == "8370": @@ -194,29 +66,25 @@ def discover(discover_type=None, ip_address=None): data = data[8:-16] else: continue - device_id = int.from_bytes( - bytearray.fromhex(data[20:26].hex()), "little" - ) + device_id = int.from_bytes(bytearray.fromhex(data[20:26].hex()), "little") if device_id in found_devices: continue encrypt_data = data[40:-16] reply = security.aes_decrypt(encrypt_data) _LOGGER.debug(f"Declassified reply: {reply.hex()}") - ssid = reply[41 : 41 + reply[40]].decode("utf-8") + ssid = reply[41:41 + reply[40]].decode("utf-8") device_type = ssid.split("_")[1] port = bytes2port(reply[4:8]) model = reply[17:25].decode("utf-8") sn = reply[8:40].decode("utf-8") elif data[:6].hex() == "3c3f786d6c20": protocol = 1 - root = ET.fromstring(data.decode(encoding="utf-8", errors="replace")) + root = ET.fromstring(data.decode( + encoding="utf-8", errors="replace")) child = root.find("body/device") m = child.attrib - port, sn, device_type = ( - int(m["port"]), - m["apc_sn"], - str(hex(int(m["apc_type"])))[2:], - ) + port, sn, device_type = int(m["port"]), m["apc_sn"], str( + hex(int(m["apc_type"])))[2:] response = get_device_info(ip, int(port)) device_id = get_id_from_response(response) if len(sn) == 32: @@ -234,7 +102,7 @@ def discover(discover_type=None, ip_address=None): "port": port, "model": model, "sn": sn, - "protocol": protocol, + "protocol": protocol } if len(discover_type) == 0 or device.get("type") in discover_type: found_devices[device_id] = device @@ -249,22 +117,17 @@ def discover(discover_type=None, ip_address=None): def get_id_from_response(response): - """ - get_id_from_response - """ if response[64:-16][:6].hex() == "3c3f786d6c20": xml = response[64:-16] root = ET.fromstring(xml.decode(encoding="utf-8", errors="replace")) child = root.find("smartDevice") m = child.attrib return int.from_bytes(bytearray.fromhex(m["devId"]), "little") - return 0 + else: + return 0 def bytes2port(paramArrayOfbyte): - """ - bytes2port - """ if paramArrayOfbyte is None: return 0 b, i = 0, 0 @@ -279,47 +142,32 @@ def bytes2port(paramArrayOfbyte): def get_device_info(device_ip, device_port: int): - """ - get_device_info - """ response = bytearray(0) try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.settimeout(8) device_address = (device_ip, device_port) sock.connect(device_address) - _LOGGER.debug( - f"Sending to {device_ip}:{device_port} {DEVICE_INFO_MSG.hex()}" - ) + _LOGGER.debug(f"Sending to {device_ip}:{device_port} {DEVICE_INFO_MSG.hex()}") sock.sendall(DEVICE_INFO_MSG) response = sock.recv(512) except socket.timeout: - _LOGGER.warning( - f"Connect the device {device_ip}:{device_port} timed out for 8s. " - f"Don't care about a small amount of this. if many maybe not support." - ) + _LOGGER.warning(f"Connect the device {device_ip}:{device_port} timed out for 8s. " + f"Don't care about a small amount of this. if many maybe not support." + ) except socket.error: _LOGGER.warning(f"Can't connect to Device {device_ip}:{device_port}") return response def enum_all_broadcast(): - """ - enum_all_broadcast - """ nets = [] adapters = ifaddr.get_adapters() for adapter in adapters: for ip in adapter.ips: if ip.is_IPv4 and ip.network_prefix < 32: - local_network = IPv4Network( - f"{ip.ip}/{ip.network_prefix}", strict=False - ) - if ( - local_network.is_private - and not local_network.is_loopback - and not local_network.is_link_local - ): + local_network = IPv4Network(f"{ip.ip}/{ip.network_prefix}", strict=False) + if local_network.is_private and not local_network.is_loopback and not local_network.is_link_local: addr = str(local_network.broadcast_address) if addr not in nets: nets.append(addr) diff --git a/custom_components/midea_ac_lan/midea/core/message.py b/custom_components/midea_ac_lan/midea/core/message.py index 91e485c6..4c744a45 100644 --- a/custom_components/midea_ac_lan/midea/core/message.py +++ b/custom_components/midea_ac_lan/midea/core/message.py @@ -18,12 +18,12 @@ class MessageCheckSumError(Exception): class MessageType(IntEnum): - set = (0x02,) - query = (0x03,) - notify1 = (0x04,) - notify2 = (0x05,) - exception = (0x06,) - exception2 = (0x0A,) + set = 0x02, + query = 0x03, + notify1 = 0x04, + notify2 = 0x05, + exception = 0x06, + exception2 = 0x0A, query_appliance = 0xA0 @@ -38,7 +38,7 @@ def __init__(self): @staticmethod def checksum(data): - return (~sum(data) + 1) & 0xFF + return (~ sum(data) + 1) & 0xff @property def header(self): @@ -85,9 +85,7 @@ def __str__(self) -> str: "header": self.header.hex(), "body": self.body.hex(), "message type": "%02x" % self._message_type, - "body type": ( - ("%02x" % self._body_type) if self._body_type is not None else "None" - ), + "body type": ("%02x" % self._body_type) if self._body_type is not None else "None" } return str(output) @@ -103,29 +101,26 @@ def __init__(self, device_type, protocol_version, message_type, body_type): @property def header(self): length = self.HEADER_LENGTH + len(self.body) - return bytearray( - [ - # flag - 0xAA, - # length - length, - # device type - self.device_type, - # frame checksum - 0x00, # self._device_type ^ length, - # unused - 0x00, - 0x00, - # frame ID - 0x00, - # frame protocol version - 0x00, - # device protocol version - self.protocol_version, - # frame type - self.message_type, - ] - ) + return bytearray([ + # flag + 0xAA, + # length + length, + # device type + self.device_type, + # frame checksum + 0x00, # self._device_type ^ length, + # unused + 0x00, 0x00, + # frame ID + 0x00, + # frame protocol version + 0x00, + # device protocol version + self.protocol_version, + # frame type + self.message_type + ]) @property def _body(self): @@ -152,8 +147,7 @@ def __init__(self, device_type, protocol_version, cmd_type, cmd_body): device_type=device_type, protocol_version=protocol_version, message_type=cmd_type, - body_type=None, - ) + body_type=None) self._cmd_body = cmd_body @property @@ -171,8 +165,7 @@ def __init__(self, device_type): device_type=device_type, protocol_version=0, message_type=MessageType.query_appliance, - body_type=None, - ) + body_type=None) @property def _body(self): @@ -203,7 +196,7 @@ def read_byte(body, byte, default_value=0): class NewProtocolMessageBody(MessageBody): def __init__(self, body, bt): super().__init__(body) - if bt == 0xB5: + if bt == 0xb5: self._pack_len = 4 else: self._pack_len = 5 @@ -227,9 +220,9 @@ def parse(self): pos += 1 length = self.data[pos + 2] if length > 0: - value = self.data[pos + 3 : pos + 3 + length] + value = self.data[pos + 3: pos + 3 + length] result[param] = value - pos += 3 + length + pos += (3 + length) except IndexError: # Some device used non-standard new-protocol(美的乐享三代中央空调?) _LOGGER.debug(f"Non-standard new-protocol {self.data.hex()}") @@ -241,11 +234,11 @@ def __init__(self, message): super().__init__() if message is None or len(message) < self.HEADER_LENGTH + 1: raise MessageLenError - self._header = message[: self.HEADER_LENGTH] + self._header = message[:self.HEADER_LENGTH] self.protocol_version = self._header[-2] self.message_type = self._header[-1] self.device_type = self._header[2] - body = message[self.HEADER_LENGTH : -1] + body = message[self.HEADER_LENGTH: -1] self._body = MessageBody(body) self.body_type = self._body.body_type diff --git a/custom_components/midea_ac_lan/midea/core/packet_builder.py b/custom_components/midea_ac_lan/midea/core/packet_builder.py index 04780fee..6fed16be 100644 --- a/custom_components/midea_ac_lan/midea/core/packet_builder.py +++ b/custom_components/midea_ac_lan/midea/core/packet_builder.py @@ -1,6 +1,5 @@ -import datetime - from .security import LocalSecurity +import datetime class PacketBuilder: @@ -9,58 +8,24 @@ def __init__(self, device_id: int, command): self.security = LocalSecurity() # aa20ac00000000000003418100ff03ff000200000000000000000000000006f274 # Init the packet with the header data. - self.packet = bytearray( - [ - # 2 bytes - StaicHeader - 0x5A, - 0x5A, - # 2 bytes - mMessageType - 0x01, - 0x11, - # 2 bytes - PacketLenght - 0x00, - 0x00, - # 2 bytes - 0x20, - 0x00, - # 4 bytes - MessageId - 0x00, - 0x00, - 0x00, - 0x00, - # 8 bytes - Date&Time - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - # 6 bytes - mDeviceID - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - # 12 bytes - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + self.packet = bytearray([ + # 2 bytes - StaicHeader + 0x5a, 0x5a, + # 2 bytes - mMessageType + 0x01, 0x11, + # 2 bytes - PacketLenght + 0x00, 0x00, + # 2 bytes + 0x20, 0x00, + # 4 bytes - MessageId + 0x00, 0x00, 0x00, 0x00, + # 8 bytes - Date&Time + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + # 6 bytes - mDeviceID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + # 12 bytes + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]) self.packet[12:20] = self.packet_time() self.packet[20:28] = device_id.to_bytes(8, "little") self.command = command @@ -68,7 +33,7 @@ def __init__(self, device_id: int, command): def finalize(self, msg_type=1): if msg_type != 1: self.packet[3] = 0x10 - self.packet[6] = 0x7B + self.packet[6] = 0x7b else: self.packet.extend(self.security.aes_encrypt(self.command)) # PacketLenght @@ -82,13 +47,14 @@ def encode32(self, data: bytearray): @staticmethod def checksum(data): - return (~sum(data) + 1) & 0xFF + return (~ sum(data) + 1) & 0xff @staticmethod def packet_time(): - t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:16] + t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[ + :16] b = bytearray() for i in range(0, len(t), 2): - d = int(t[i : i + 2]) + d = int(t[i:i+2]) b.insert(0, d) return b diff --git a/custom_components/midea_ac_lan/midea/core/security.py b/custom_components/midea_ac_lan/midea/core/security.py index 90166ea1..4f9ac85d 100644 --- a/custom_components/midea_ac_lan/midea/core/security.py +++ b/custom_components/midea_ac_lan/midea/core/security.py @@ -1,12 +1,12 @@ -import hmac -from hashlib import md5, sha256 -from typing import Any -from urllib.parse import unquote_plus, urlencode, urlparse - from Crypto.Cipher import AES -from Crypto.Random import get_random_bytes from Crypto.Util.Padding import pad, unpad from Crypto.Util.strxor import strxor +from Crypto.Random import get_random_bytes +from urllib.parse import unquote_plus, urlencode, urlparse +from hashlib import md5, sha256 +from typing import Any +import hmac + MSGTYPE_HANDSHAKE_REQUEST = 0x0 MSGTYPE_HANDSHAKE_RESPONSE = 0x1 @@ -21,8 +21,8 @@ def __init__(self, login_key, iot_key, hmac_key, fixed_key=None, fixed_iv=None): self._hmac_key = hmac_key self._aes_key = None self._aes_iv = None - self._fixed_key = format(fixed_key, "x").encode("ascii") if fixed_key else None - self._fixed_iv = format(fixed_iv, "x").encode("ascii") if fixed_iv else None + self._fixed_key = format(fixed_key, 'x').encode("ascii") if fixed_key else None + self._fixed_iv = format(fixed_iv, 'x').encode("ascii") if fixed_iv else None def sign(self, url: str, data: Any, random: str) -> str: msg = self._iot_key @@ -59,7 +59,7 @@ def get_udp_id(appliance_id, method=0): data = bytearray(sha256(bytes_id).digest()) for i in range(0, 16): data[i] ^= data[i + 16] - return data[0:16].hex() + return data[0: 16].hex() def set_aes_keys(self, key, iv): if isinstance(key, str): @@ -103,18 +103,15 @@ def aes_decrypt(self, data, key=None, iv=None): if isinstance(data, str): data = bytes.fromhex(data) if aes_iv is None: # ECB - return unpad( - AES.new(aes_key, AES.MODE_ECB).decrypt(data), len(aes_key) - ).decode() + return unpad(AES.new(aes_key, AES.MODE_ECB).decrypt(data), len(aes_key)).decode() else: # CBC - return unpad( - AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).decrypt(data), len(aes_key) - ).decode() + return unpad(AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).decrypt(data), len(aes_key)).decode() class MeijuCloudSecurity(CloudSecurity): def __init__(self, login_key, iot_key, hmac_key): - super().__init__(login_key, iot_key, hmac_key, 10864842703515613082) + super().__init__(login_key, iot_key, hmac_key, + 10864842703515613082) def encrypt_iam_password(self, login_id, data) -> str: md = md5() @@ -126,9 +123,9 @@ def encrypt_iam_password(self, login_id, data) -> str: class MSmartCloudSecurity(CloudSecurity): def __init__(self, login_key, iot_key, hmac_key): - super().__init__( - login_key, iot_key, hmac_key, 13101328926877700970, 16429062708050928556 - ) + super().__init__(login_key, iot_key, hmac_key, + 13101328926877700970, + 16429062708050928556) def encrypt_iam_password(self, login_id, data) -> str: md = md5() @@ -144,8 +141,8 @@ def set_aes_keys(self, encrypted_key, encrypted_iv): key_digest = sha256(self._login_key.encode("ascii")).hexdigest() tmp_key = key_digest[:16].encode("ascii") tmp_iv = key_digest[16:32].encode("ascii") - self._aes_key = self.aes_decrypt(encrypted_key, tmp_key, tmp_iv).encode("ascii") - self._aes_iv = self.aes_decrypt(encrypted_iv, tmp_key, tmp_iv).encode("ascii") + self._aes_key = self.aes_decrypt(encrypted_key, tmp_key, tmp_iv).encode('ascii') + self._aes_iv = self.aes_decrypt(encrypted_iv, tmp_key, tmp_iv).encode('ascii') class MideaAirSecurity(CloudSecurity): @@ -164,13 +161,10 @@ def __init__(self): self.blockSize = 16 self.iv = b"\0" * 16 self.aes_key = bytes.fromhex( - format(141661095494369103254425781617665632877, "x") + format(141661095494369103254425781617665632877, 'x') ) self.salt = bytes.fromhex( - format( - 233912452794221312800602098970898185176935770387238278451789080441632479840061417076563, - "x", - ) + format(233912452794221312800602098970898185176935770387238278451789080441632479840061417076563, 'x') ) self._tcp_key = None self._request_count = 0 @@ -178,10 +172,8 @@ def __init__(self): def aes_decrypt(self, raw): try: - return unpad( - AES.new(self.aes_key, AES.MODE_ECB).decrypt(bytearray(raw)), 16 - ) - except ValueError: + return unpad(AES.new(self.aes_key, AES.MODE_ECB).decrypt(bytearray(raw)), 16) + except ValueError as e: return bytearray(0) def aes_encrypt(self, raw): @@ -216,7 +208,7 @@ def encode_8370(self, data, msgtype): size, padding = len(data), 0 if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): if (size + 2) % 16 != 0: - padding = 16 - (size + 2 & 0xF) + padding = 16 - (size + 2 & 0xf) size += padding + 32 data += get_random_bytes(padding) header += size.to_bytes(2, "big") @@ -246,7 +238,7 @@ def decode_8370(self, data): if header[4] != 0x20: raise Exception("missing byte 4") padding = header[5] >> 4 - msgtype = header[5] & 0xF + msgtype = header[5] & 0xf data = data[6:] if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): sign = data[-32:] diff --git a/custom_components/midea_ac_lan/midea/devices/__init__.py b/custom_components/midea_ac_lan/midea/devices/__init__.py index 6a908f26..279e0a91 100644 --- a/custom_components/midea_ac_lan/midea/devices/__init__.py +++ b/custom_components/midea_ac_lan/midea/devices/__init__.py @@ -1,9 +1,7 @@ +from homeassistant.core import HomeAssistant from importlib import import_module from types import ModuleType -from homeassistant.core import HomeAssistant - - async def async_device_selector( hass: HomeAssistant, name: str, @@ -16,7 +14,7 @@ async def async_device_selector( protocol: int, model: str, subtype: int, - customize: str, + customize: str ): try: @@ -26,11 +24,9 @@ async def async_device_selector( device_path = f".{'%02x' % device_type}.device" modules: list[ModuleType] = [] - def _load_device_module() -> None: """Load all service modules.""" modules.append(import_module(device_path, __package__)) - await hass.async_add_import_executor_job(_load_device_module) device = modules[0].MideaAppliance( @@ -43,7 +39,7 @@ def _load_device_module() -> None: protocol=protocol, model=model, subtype=subtype, - customize=customize, + customize=customize ) except ModuleNotFoundError: device = None diff --git a/custom_components/midea_ac_lan/midea/devices/a1/device.py b/custom_components/midea_ac_lan/midea/devices/a1/device.py index cbff7675..4cc990a4 100644 --- a/custom_components/midea_ac_lan/midea/devices/a1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/a1/device.py @@ -1,12 +1,13 @@ import logging - -from .message import MessageA1Response, MessageQuery, MessageSet - +from .message import ( + MessageQuery, + MessageA1Response, + MessageSet +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -29,29 +30,28 @@ class DeviceAttributes(StrEnum): class MideaA1Device(MiedaDevice): - _modes = ["Manual", "Continuous", "Auto", "Clothes-Dry", "Shoes-Dry"] + _modes = [ + "Manual", "Continuous", "Auto", "Clothes-Dry", "Shoes-Dry" + ] _speeds = { - 1: "Lowest", - 40: "Low", - 60: "Medium", - 80: "High", - 102: "Auto", - 127: "Off", + 1: "Lowest", 40: "Low", 60: "Medium", 80: "High", 102: "Auto", 127: "Off" } - _water_level_sets = ["25", "50", "75", "100"] + _water_level_sets = [ + "25", "50", "75", "100" + ] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -77,9 +77,8 @@ def __init__( DeviceAttributes.water_level_set: 50, DeviceAttributes.tank_full: None, DeviceAttributes.current_humidity: None, - DeviceAttributes.current_temperature: None, - }, - ) + DeviceAttributes.current_temperature: None + }) @property def modes(self): @@ -94,7 +93,9 @@ def water_level_sets(self): return MideaA1Device._water_level_sets def build_query(self): - return [MessageQuery(self._protocol_version)] + return [ + MessageQuery(self._protocol_version) + ] def process_message(self, msg): message = MessageA1Response(msg) @@ -118,13 +119,9 @@ def process_message(self, msg): self._attributes[status] = str(value) else: self._attributes[status] = value - tank_full = self._attributes[DeviceAttributes.tank] >= int( - self._attributes[DeviceAttributes.water_level_set] - ) - if ( - self._attributes[DeviceAttributes.tank_full] is None - or self._attributes[DeviceAttributes.tank_full] != tank_full - ): + tank_full = (self._attributes[DeviceAttributes.tank] >= + int(self._attributes[DeviceAttributes.water_level_set])) + if self._attributes[DeviceAttributes.tank_full] is None or self._attributes[DeviceAttributes.tank_full] != tank_full: self._attributes[DeviceAttributes.tank_full] = tank_full new_status[str(DeviceAttributes.tank_full)] = tank_full new_status[str(status)] = self._attributes[status] @@ -136,26 +133,17 @@ def make_message_set(self): message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] message.child_lock = self._attributes[DeviceAttributes.child_lock] if self._attributes[DeviceAttributes.mode] in MideaA1Device._modes: - message.mode = ( - MideaA1Device._modes.index(self._attributes[DeviceAttributes.mode]) + 1 - ) + message.mode = MideaA1Device._modes.index(self._attributes[DeviceAttributes.mode]) + 1 else: message.mode = 1 - message.fan_speed = ( - 40 - if self._attributes[DeviceAttributes.fan_speed] is None - else list(MideaA1Device._speeds.keys())[ - list(MideaA1Device._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - ) + message.fan_speed = 40 if self._attributes[DeviceAttributes.fan_speed] is None else \ + list(MideaA1Device._speeds.keys())[list(MideaA1Device._speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + )] message.target_humidity = self._attributes[DeviceAttributes.target_humidity] message.swing = self._attributes[DeviceAttributes.swing] message.anion = self._attributes[DeviceAttributes.anion] - message.water_level_set = int( - self._attributes[DeviceAttributes.water_level_set] - ) + message.water_level_set = int(self._attributes[DeviceAttributes.water_level_set]) return message def set_attribute(self, attr, value): diff --git a/custom_components/midea_ac_lan/midea/devices/a1/message.py b/custom_components/midea_ac_lan/midea/devices/a1/message.py index f6a43b7a..a954f98b 100644 --- a/custom_components/midea_ac_lan/midea/devices/a1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/a1/message.py @@ -1,15 +1,13 @@ from enum import IntEnum - from ...core.crc8 import calculate from ...core.message import ( - MessageBody, + MessageType, MessageRequest, MessageResponse, - MessageType, - NewProtocolMessageBody, + MessageBody, + NewProtocolMessageBody ) - class NewProtocolTags(IntEnum): light = 0x005B @@ -45,34 +43,17 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41, - ) + body_type=0x41) @property def _body(self): - return bytearray( - [ - 0x81, - 0x00, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + 0x81, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + ]) class MessageNewProtocolQuery(MessageA1Base): @@ -80,12 +61,13 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0xB1, - ) + body_type=0xB1) @property def _body(self): - query_params = [NewProtocolTags.light] + query_params = [ + NewProtocolTags.light + ] _body = bytearray([len(query_params)]) for param in query_params: _body.extend([param & 0xFF, param >> 8]) @@ -97,8 +79,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x48, - ) + body_type=0x48) self.power = False self.prompt_tone = False self.mode = 1 @@ -128,30 +109,20 @@ def _body(self): swing = 0x08 if self.swing else 0x00 # byte 13 water_level_set water_level_set = self.water_level_set - return bytearray( - [ - power | prompt_tone | 0x02, - mode, - fan_speed, - 0x00, - 0x00, - 0x00, - target_humidity, - child_lock, - anion, - swing, - 0x00, - 0x00, - water_level_set, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + power | prompt_tone | 0x02, + mode, + fan_speed, + 0x00, 0x00, 0x00, + target_humidity, + child_lock, + anion, + swing, + 0x00, 0x00, + water_level_set, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + ]) class MessageNewProtocolSet(MessageA1Base): @@ -159,8 +130,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0xB0, - ) + body_type=0xB0) self.light = None @property @@ -172,9 +142,8 @@ def _body(self): payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.light, - value=bytearray([0x01 if self.light else 0x00]), - ) - ) + value=bytearray([0x01 if self.light else 0x00]) + )) payload[0] = pack_count return payload @@ -202,17 +171,13 @@ def __init__(self, body, bt): super().__init__(body, bt) params = self.parse() if NewProtocolTags.light in params: - self.light = params[NewProtocolTags.light][0] > 0 + self.light = (params[NewProtocolTags.light][0] > 0) class MessageA1Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: + if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1]: if self.body_type in [0xB0, 0xB1, 0xB5]: self.set_body(A1NewProtocolMessageBody(super().body, self.body_type)) else: diff --git a/custom_components/midea_ac_lan/midea/devices/ac/device.py b/custom_components/midea_ac_lan/midea/devices/ac/device.py index 87ccb1c2..977c6050 100644 --- a/custom_components/midea_ac_lan/midea/devices/ac/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ac/device.py @@ -1,23 +1,20 @@ -import json import logging - +import json from .message import ( + MessageQuery, + MessageToggleDisplay, + MessageNewProtocolQuery, MessageACResponse, MessageGeneralSet, - MessageNewProtocolQuery, MessageNewProtocolSet, MessagePowerQuery, - MessageQuery, MessageSubProtocolQuery, - MessageSubProtocolSet, - MessageToggleDisplay, + MessageSubProtocolSet ) - try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -61,27 +58,22 @@ class DeviceAttributes(StrEnum): class MideaACDevice(MiedaDevice): _fresh_air_fan_speeds = { - 0: "Off", - 20: "Silent", - 40: "Low", - 60: "Medium", - 80: "High", - 100: "Full", + 0: "Off", 20: "Silent", 40: "Low", 60: "Medium", 80: "High", 100: "Full" } _fresh_air_fan_speeds_rev = dict(reversed(_fresh_air_fan_speeds.items())) def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -128,8 +120,7 @@ def __init__( DeviceAttributes.fresh_air_mode: None, DeviceAttributes.fresh_air_1: None, DeviceAttributes.fresh_air_2: None, - }, - ) + }) self._fresh_air_version = None self._default_temperature_step = 0.5 self._temperature_step = None @@ -153,12 +144,12 @@ def build_query(self): return [ MessageSubProtocolQuery(self._protocol_version, 0x10), MessageSubProtocolQuery(self._protocol_version, 0x11), - MessageSubProtocolQuery(self._protocol_version, 0x30), + MessageSubProtocolQuery(self._protocol_version, 0x30) ] return [ MessageQuery(self._protocol_version), MessageNewProtocolQuery(self._protocol_version), - MessagePowerQuery(self._protocol_version), + MessagePowerQuery(self._protocol_version) ] def process_message(self, msg): @@ -188,13 +179,9 @@ def process_message(self, msg): self._attributes[DeviceAttributes.fresh_air_mode] = v else: self._attributes[DeviceAttributes.fresh_air_mode] = "Off" - new_status[DeviceAttributes.fresh_air_mode.value] = self._attributes[ - DeviceAttributes.fresh_air_mode - ] - if not self._attributes[DeviceAttributes.power] or ( - DeviceAttributes.swing_vertical in new_status - and self._attributes[DeviceAttributes.swing_vertical] - ): + new_status[DeviceAttributes.fresh_air_mode.value] = self._attributes[DeviceAttributes.fresh_air_mode] + if not self._attributes[DeviceAttributes.power] or \ + (DeviceAttributes.swing_vertical in new_status and self._attributes[DeviceAttributes.swing_vertical]): self._attributes[DeviceAttributes.indirect_wind] = False new_status[DeviceAttributes.indirect_wind.value] = False if not self._attributes[DeviceAttributes.power]: @@ -211,9 +198,7 @@ def make_message_set(self): message.power = self._attributes[DeviceAttributes.power] message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] + message.target_temperature = self._attributes[DeviceAttributes.target_temperature] message.fan_speed = self._attributes[DeviceAttributes.fan_speed] message.swing_vertical = self._attributes[DeviceAttributes.swing_vertical] message.swing_horizontal = self._attributes[DeviceAttributes.swing_horizontal] @@ -235,9 +220,7 @@ def make_subptotocol_message_set(self): message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] message.aux_heating = self._attributes[DeviceAttributes.aux_heating] message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] + message.target_temperature = self._attributes[DeviceAttributes.target_temperature] message.fan_speed = self._attributes[DeviceAttributes.fan_speed] message.boost_mode = self._attributes[DeviceAttributes.boost_mode] message.dry = self._attributes[DeviceAttributes.dry] @@ -257,15 +240,13 @@ def make_message_uniq_set(self): def set_attribute(self, attr, value): # if nat a sensor message = None - if attr not in [ - DeviceAttributes.indoor_temperature, - DeviceAttributes.outdoor_temperature, - DeviceAttributes.indoor_humidity, - DeviceAttributes.full_dust, - DeviceAttributes.total_energy_consumption, - DeviceAttributes.current_energy_consumption, - DeviceAttributes.realtime_power, - ]: + if attr not in [DeviceAttributes.indoor_temperature, + DeviceAttributes.outdoor_temperature, + DeviceAttributes.indoor_humidity, + DeviceAttributes.full_dust, + DeviceAttributes.total_energy_consumption, + DeviceAttributes.current_energy_consumption, + DeviceAttributes.realtime_power]: if attr == DeviceAttributes.prompt_tone: self._attributes[DeviceAttributes.prompt_tone] = value self.update_all({DeviceAttributes.prompt_tone.value: value}) @@ -273,9 +254,9 @@ def set_attribute(self, attr, value): message = MessageToggleDisplay(self._protocol_version) message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] elif attr in [ - DeviceAttributes.indirect_wind, - DeviceAttributes.breezeless, - DeviceAttributes.screen_display_alternate, + DeviceAttributes.indirect_wind, + DeviceAttributes.breezeless, + DeviceAttributes.screen_display_alternate ]: message = MessageNewProtocolSet(self._protocol_version) setattr(message, str(attr), value) @@ -286,42 +267,38 @@ def set_attribute(self, attr, value): setattr( message, str(self._fresh_air_version), - [value, self._attributes[DeviceAttributes.fresh_air_fan_speed]], + [value, self._attributes[DeviceAttributes.fresh_air_fan_speed]] ) elif attr == DeviceAttributes.fresh_air_mode: if value in MideaACDevice._fresh_air_fan_speeds.values(): speed = list(MideaACDevice._fresh_air_fan_speeds.keys())[ list(MideaACDevice._fresh_air_fan_speeds.values()).index(value) ] - fresh_air = ( - [True, speed] - if speed > 0 - else [ - False, - self._attributes[DeviceAttributes.fresh_air_fan_speed], - ] - ) + fresh_air = [True, speed] if speed > 0 else \ + [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]] message = MessageNewProtocolSet(self._protocol_version) - setattr(message, str(self._fresh_air_version), fresh_air) + setattr( + message, + str(self._fresh_air_version), + fresh_air + ) elif not value: message = MessageNewProtocolSet(self._protocol_version) setattr( message, str(self._fresh_air_version), - [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]], + [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]] ) elif attr == DeviceAttributes.fresh_air_fan_speed: if self._fresh_air_version is not None: message = MessageNewProtocolSet(self._protocol_version) - fresh_air = ( - [True, value] - if value > 0 - else [ - False, - self._attributes[DeviceAttributes.fresh_air_fan_speed], - ] + fresh_air = [True, value] if value > 0 else \ + [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]] + setattr( + message, + str(self._fresh_air_version), + fresh_air ) - setattr(message, str(self._fresh_air_version), fresh_air) elif attr in self._attributes.keys(): message = self.make_message_uniq_set() if attr in [ @@ -329,7 +306,7 @@ def set_attribute(self, attr, value): DeviceAttributes.sleep_mode, DeviceAttributes.frost_protect, DeviceAttributes.comfort_mode, - DeviceAttributes.eco_mode, + DeviceAttributes.eco_mode ]: message.boost_mode = False message.sleep_mode = False diff --git a/custom_components/midea_ac_lan/midea/devices/ac/message.py b/custom_components/midea_ac_lan/midea/devices/ac/message.py index 9c5816b3..9468f689 100644 --- a/custom_components/midea_ac_lan/midea/devices/ac/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ac/message.py @@ -1,13 +1,12 @@ from enum import IntEnum - -from ...core.crc8 import calculate from ...core.message import ( - MessageBody, + MessageType, MessageRequest, MessageResponse, - MessageType, - NewProtocolMessageBody, + MessageBody, + NewProtocolMessageBody ) +from ...core.crc8 import calculate BB_AC_MODES = [0, 3, 1, 2, 4, 5] @@ -19,7 +18,7 @@ class NewProtocolTags(IntEnum): prompt_tone = 0x001A indirect_wind = 0x0042 fresh_air_1 = 0x0233 - fresh_air_2 = 0x004B + fresh_air_2 = 0x004b class MessageACBase(MessageRequest): @@ -30,7 +29,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xAC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) MessageACBase._message_serial += 1 if MessageACBase._message_serial >= 254: @@ -53,34 +52,17 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41, - ) + body_type=0x41) @property def _body(self): - return bytearray( - [ - 0x81, - 0x00, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + 0x81, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + ]) class MessagePowerQuery(MessageACBase): @@ -88,12 +70,13 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41, - ) + body_type=0x41) @property def _body(self): - return bytearray([0x21, 0x01, 0x44, 0x00, 0x01]) + return bytearray([ + 0x21, 0x01, 0x44, 0x00, 0x01 + ]) @property def body(self): @@ -107,36 +90,20 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41, - ) + body_type=0x41) self.prompt_tone = False @property def _body(self): prompt_tone = 0x40 if self.prompt_tone else 0 - return bytearray( - [ - 0x02 | prompt_tone, - 0x00, - 0xFF, - 0x02, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + 0x02 | prompt_tone, + 0x00, 0xFF, 0x02, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + ]) class MessageNewProtocolQuery(MessageACBase): @@ -144,8 +111,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0xB1, - ) + body_type=0xB1) @property def _body(self): @@ -155,7 +121,7 @@ def _body(self): NewProtocolTags.indoor_humidity, NewProtocolTags.screen_display, NewProtocolTags.fresh_air_1, - NewProtocolTags.fresh_air_2, + NewProtocolTags.fresh_air_2 ] _body = bytearray([len(query_params)]) @@ -167,8 +133,9 @@ def _body(self): class MessageSubProtocol(MessageACBase): def __init__(self, protocol_version, message_type, subprotocol_query_type): super().__init__( - protocol_version=protocol_version, message_type=message_type, body_type=0xAA - ) + protocol_version=protocol_version, + message_type=message_type, + body_type=0xAA) self._subprotocol_query_type = subprotocol_query_type @property @@ -185,17 +152,10 @@ def body(self): @property def _body(self): _subprotocol_body = self._subprotocol_body - _body = bytearray( - [ - 6 - + 2 - + (len(_subprotocol_body) if _subprotocol_body is not None else 0), - 0x00, - 0xFF, - 0xFF, - self._subprotocol_query_type, - ] - ) + _body = bytearray([ + 6 + 2 + (len(_subprotocol_body) if _subprotocol_body is not None else 0), + 0x00, 0xFF, 0xFF, self._subprotocol_query_type + ]) if _subprotocol_body is not None: _body.extend(_subprotocol_body) return _body @@ -206,8 +166,7 @@ def __init__(self, protocol_version, subprotocol_query_type): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - subprotocol_query_type=subprotocol_query_type, - ) + subprotocol_query_type=subprotocol_query_type) class MessageSubProtocolSet(MessageSubProtocol): @@ -215,8 +174,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - subprotocol_query_type=0x20, - ) + subprotocol_query_type=0x20) self.power = False self.mode = 0 self.target_temperature = 20.0 @@ -248,47 +206,18 @@ def _subprotocol_body(self): prompt_tone = 0x01 if self.prompt_tone else 0 timer = 0x04 if (self.sn8_flag and self.timer) else 0 - return bytearray( - [ - 0x02 | boost_mode | power | dry, - aux_heating, - sleep_mode, - 0x00, - 0x00, - mode, - target_temperature, - fan_speed, - 0x32, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0x01, - 0x00, - 0x01, - water_model_temperature_set, - prompt_tone, - target_temperature, - 0x32, - 0x66, - 0x00, - eco | timer, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x08, - ] - ) + return bytearray([ + 0x02 | boost_mode | power | dry, aux_heating, sleep_mode, 0x00, + 0x00, mode, target_temperature, fan_speed, + 0x32, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x01, water_model_temperature_set, + prompt_tone, target_temperature, 0x32, 0x66, + 0x00, eco | timer, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x08 + ]) class MessageGeneralSet(MessageACBase): @@ -296,8 +225,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x40, - ) + body_type=0x40) self.power = False self.prompt_tone = True self.mode = 0 @@ -322,18 +250,15 @@ def _body(self): power = 0x01 if self.power else 0 prompt_tone = 0x40 if self.prompt_tone else 0 # Byte2, mode target_temperature - mode = (self.mode << 5) & 0xE0 - target_temperature = (int(self.target_temperature) & 0xF) | ( - 0x10 if int(round(self.target_temperature * 2)) % 2 != 0 else 0 - ) + mode = (self.mode << 5) & 0xe0 + target_temperature = (int(self.target_temperature) & 0xf) | \ + (0x10 if int(round(self.target_temperature * 2)) % 2 != 0 else 0) # Byte 3, fan_speed - fan_speed = self.fan_speed & 0x7F + fan_speed = self.fan_speed & 0x7f # Byte 7, swing_mode - swing_mode = ( - 0x30 - | (0x0C if self.swing_vertical else 0) - | (0x03 if self.swing_horizontal else 0) - ) + swing_mode = 0x30 | \ + (0x0c if self.swing_vertical else 0) | \ + (0x03 if self.swing_horizontal else 0) # Byte 8, turbo boost_mode = 0x20 if self.boost_mode else 0 # Byte 9 aux_heating eco_mode @@ -352,32 +277,22 @@ def _body(self): # Byte 22 comfort_mode comfort_mode = 0x01 if self.comfort_mode else 0 - return bytearray( - [ - power | prompt_tone, - mode | target_temperature, - fan_speed, - 0x00, - 0x00, - 0x00, - swing_mode, - boost_mode, - smart_eye | dry | aux_heating | eco_mode, - temp_fahrenheit | sleep_mode | boost_mode_1, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - natural_wind, - 0x00, - 0x00, - 0x00, - frost_protect, - comfort_mode, - ] - ) + return bytearray([ + power | prompt_tone, + mode | target_temperature, + fan_speed, + 0x00, 0x00, 0x00, + swing_mode, + boost_mode, + smart_eye | dry | aux_heating | eco_mode, + temp_fahrenheit | sleep_mode | boost_mode_1, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + natural_wind, + 0x00, 0x00, 0x00, + frost_protect, + comfort_mode + ]) class MessageNewProtocolSet(MessageACBase): @@ -385,8 +300,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0xB0, - ) + body_type=0xB0) self.indirect_wind = None self.prompt_tone = None self.breezeless = None @@ -403,33 +317,29 @@ def _body(self): payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.breezeless, - value=bytearray([0x01 if self.breezeless else 0x00]), - ) - ) + value=bytearray([0x01 if self.breezeless else 0x00]) + )) if self.indirect_wind is not None: pack_count += 1 payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.indirect_wind, - value=bytearray([0x02 if self.indirect_wind else 0x01]), - ) - ) + value=bytearray([0x02 if self.indirect_wind else 0x01]) + )) if self.prompt_tone is not None: pack_count += 1 payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.prompt_tone, - value=bytearray([0x01 if self.prompt_tone else 0x00]), - ) - ) + value=bytearray([0x01 if self.prompt_tone else 0x00]) + )) if self.screen_display_alternate is not None: pack_count += 1 payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.screen_display, - value=bytearray([0x64 if self.screen_display_alternate else 0x00]), - ) - ) + value=bytearray([0x64 if self.screen_display_alternate else 0x00]) + )) if self.fresh_air_1 is not None and len(self.fresh_air_1) == 2: pack_count += 1 fresh_air_power = 2 if self.fresh_air_1[0] > 0 else 1 @@ -437,22 +347,13 @@ def _body(self): payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.fresh_air_1, - value=bytearray( - [ - fresh_air_power, - fresh_air_fan_speed, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ), - ) - ) + value=bytearray([ + fresh_air_power, + fresh_air_fan_speed, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + ]) + )) if self.fresh_air_2 is not None and len(self.fresh_air_2) == 2: pack_count += 1 fresh_air_power = 1 if self.fresh_air_2[0] > 0 else 0 @@ -460,9 +361,12 @@ def _body(self): payload.extend( NewProtocolMessageBody.pack( param=NewProtocolTags.fresh_air_2, - value=bytearray([fresh_air_power, fresh_air_fan_speed, 0xFF]), - ) - ) + value=bytearray([ + fresh_air_power, + fresh_air_fan_speed, + 0xFF + ]) + )) payload[0] = pack_count return payload @@ -471,11 +375,9 @@ class XA0MessageBody(MessageBody): def __init__(self, body): super().__init__(body) self.power = (body[1] & 0x1) > 0 - self.target_temperature = ( - ((body[1] & 0x3E) >> 1) - 4 + 16.0 + (0.5 if body[1] & 0x40 > 0 else 0.0) - ) - self.mode = (body[2] & 0xE0) >> 5 - self.fan_speed = body[3] & 0x7F + self.target_temperature = ((body[1] & 0x3E) >> 1) - 4 + 16.0 + (0.5 if body[1] & 0x40 > 0 else 0.0) + self.mode = (body[2] & 0xe0) >> 5 + self.fan_speed = body[3] & 0x7f self.swing_vertical = (body[7] & 0xC) > 0 self.swing_horizontal = (body[7] & 0x3) > 0 self.boost_mode = ((body[8] & 0x20) > 0) or ((body[10] & 0x2) > 0) @@ -516,15 +418,13 @@ def __init__(self, body, bt): super().__init__(body, bt) params = self.parse() if NewProtocolTags.indirect_wind in params: - self.indirect_wind = params[NewProtocolTags.indirect_wind][0] == 0x02 + self.indirect_wind = (params[NewProtocolTags.indirect_wind][0] == 0x02) if NewProtocolTags.indoor_humidity in params: self.indoor_humidity = params[NewProtocolTags.indoor_humidity][0] if NewProtocolTags.breezeless in params: - self.breezeless = params[NewProtocolTags.breezeless][0] == 1 + self.breezeless = (params[NewProtocolTags.breezeless][0] == 1) if NewProtocolTags.screen_display in params: - self.screen_display_alternate = ( - params[NewProtocolTags.screen_display][0] > 0 - ) + self.screen_display_alternate = (params[NewProtocolTags.screen_display][0] > 0) self.screen_display_new = True if NewProtocolTags.fresh_air_1 in params: self.fresh_air_1 = True @@ -542,10 +442,8 @@ class XC0MessageBody(MessageBody): def __init__(self, body): super().__init__(body) self.power = (body[1] & 0x1) > 0 - self.mode = (body[2] & 0xE0) >> 5 - self.target_temperature = ( - (body[2] & 0x0F) + 16.0 + (0.5 if body[0x02] & 0x10 > 0 else 0.0) - ) + self.mode = (body[2] & 0xe0) >> 5 + self.target_temperature = (body[2] & 0x0F) + 16.0 + (0.5 if body[0x02] & 0x10 > 0 else 0.0) self.fan_speed = body[3] & 0x7F self.swing_vertical = (body[7] & 0x0C) > 0 self.swing_horizontal = (body[7] & 0x03) > 0 @@ -584,13 +482,16 @@ def __init__(self, body, analysis_method=3): super().__init__(body) if body[3] == 0x44: self.total_energy_consumption = XC1MessageBody.parse_consumption( - analysis_method, body[4], body[5], body[6], body[7] + analysis_method, + body[4], body[5], body[6], body[7] ) self.current_energy_consumption = XC1MessageBody.parse_consumption( - analysis_method, body[12], body[13], body[14], body[15] + analysis_method, + body[12], body[13], body[14], body[15] ) self.realtime_power = XC1MessageBody.parse_power( - analysis_method, body[16], body[17], body[18] + analysis_method, + body[16], body[17], body[18] ) elif body[3] == 0x40: pass @@ -602,14 +503,9 @@ def parse_value(byte): @staticmethod def parse_power(analysis_method, byte1, byte2, byte3): if analysis_method == 1: - return ( - float( - XC1MessageBody.parse_value(byte1) * 10000 - + XC1MessageBody.parse_value(byte2) * 100 - + XC1MessageBody.parse_value(byte3) - ) - / 10 - ) + return float(XC1MessageBody.parse_value(byte1) * 10000 + + XC1MessageBody.parse_value(byte2) * 100 + + XC1MessageBody.parse_value(byte3)) / 10 elif analysis_method == 2: return float((byte1 << 16) + (byte2 << 8) + byte3) / 10 else: @@ -618,15 +514,10 @@ def parse_power(analysis_method, byte1, byte2, byte3): @staticmethod def parse_consumption(analysis_method, byte1, byte2, byte3, byte4): if analysis_method == 1: - return ( - float( - XC1MessageBody.parse_value(byte1) * 1000000 - + XC1MessageBody.parse_value(byte2) * 10000 - + XC1MessageBody.parse_value(byte3) * 100 - + XC1MessageBody.parse_value(byte4) - ) - / 100 - ) + return float(XC1MessageBody.parse_value(byte1) * 1000000 + + XC1MessageBody.parse_value(byte2) * 10000 + + XC1MessageBody.parse_value(byte3) * 100 + + XC1MessageBody.parse_value(byte4)) / 100 elif analysis_method == 2: return float((byte1 << 32) + (byte2 << 16) + (byte3 << 8) + byte4) / 10 else: @@ -652,40 +543,22 @@ def __init__(self, body): self.mode = 0 self.target_temperature = (subprotocol_body[6] - 30) / 2 self.fan_speed = subprotocol_body[7] - self.timer = ( - (subprotocol_body[25] & 0x04) > 0 - if subprotocol_body_len > 27 - else False - ) - self.eco_mode = ( - (subprotocol_body[25] & 0x40) > 0 - if subprotocol_body_len > 27 - else False - ) + self.timer = (subprotocol_body[25] & 0x04) > 0 if subprotocol_body_len > 27 else False + self.eco_mode = (subprotocol_body[25] & 0x40) > 0 if subprotocol_body_len > 27 else False elif data_type == 0x10: if subprotocol_body[8] & 0x80 == 0x80: - self.indoor_temperature = ( - 0 - (~(subprotocol_body[7] + subprotocol_body[8] * 256) + 1) - & 0xFFFF - ) / 100 + self.indoor_temperature = (0 - (~(subprotocol_body[7] + subprotocol_body[8] * 256) + 1) & 0xffff) / 100 else: - self.indoor_temperature = ( - subprotocol_body[7] + subprotocol_body[8] * 256 - ) / 100 + self.indoor_temperature = (subprotocol_body[7] + subprotocol_body[8] * 256) / 100 self.indoor_humidity = subprotocol_body[30] self.sn8_flag = subprotocol_body[80] == 0x31 elif data_type == 0x12: pass elif data_type == 0x30: if subprotocol_body[6] & 0x80 == 0x80: - self.outdoor_temperature = ( - 0 - (~(subprotocol_body[5] + subprotocol_body[6] * 256) + 1) - & 0xFFFF - ) / 100 + self.outdoor_temperature = (0 - (~(subprotocol_body[5] + subprotocol_body[6] * 256) + 1) & 0xffff) / 100 else: - self.outdoor_temperature = ( - subprotocol_body[5] + subprotocol_body[6] * 256 - ) / 100 + self.outdoor_temperature = (subprotocol_body[5] + subprotocol_body[6] * 256) / 100 elif data_type == 0x13 or data_type == 0x21: pass @@ -697,25 +570,15 @@ def __init__(self, message, power_analysis_method=3): self.set_body(XA0MessageBody(super().body)) elif self.message_type == MessageType.notify1 and self.body_type == 0xA1: self.set_body(XA1MessageBody(super().body)) - elif self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify2, - ] and self.body_type in [0xB0, 0xB1, 0xB5]: + elif self.message_type in [MessageType.query, MessageType.set, MessageType.notify2] and \ + self.body_type in [0xB0, 0xB1, 0xB5]: self.set_body(XBXMessageBody(super().body, self.body_type)) - elif ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0xC0 - ): + elif self.message_type in [MessageType.query, MessageType.set] and self.body_type == 0xC0: self.set_body(XC0MessageBody(super().body)) elif self.message_type == MessageType.query and self.body_type == 0xC1: self.set_body(XC1MessageBody(super().body, power_analysis_method)) - elif ( - self.message_type - in [MessageType.set, MessageType.query, MessageType.notify2] - and self.body_type == 0xBB - and len(super().body) >= 21 - ): + elif self.message_type in [MessageType.set, MessageType.query, MessageType.notify2] and \ + self.body_type == 0xBB and len(super().body) >= 21: self.used_subprotocol = True self.set_body(XBBMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/b0/device.py b/custom_components/midea_ac_lan/midea/devices/b0/device.py index 8de17bf5..c1f5f6ec 100644 --- a/custom_components/midea_ac_lan/midea/devices/b0/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b0/device.py @@ -1,12 +1,12 @@ import logging - -from .message import MessageB0Response, MessageQuery01 - +from .message import ( + MessageQuery01, + MessageB0Response +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,26 +24,22 @@ class DeviceAttributes(StrEnum): class MideaB0Device(MiedaDevice): _status = { - 0x01: "Standby", - 0x02: "Idle", - 0x03: "Working", - 0x04: "Finished", - 0x05: "Delay", - 0x06: "Paused", + 0x01: "Standby", 0x02: "Idle", 0x03: "Working", + 0x04: "Finished", 0x05: "Delay", 0x06: "Paused" } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -64,8 +60,7 @@ def __init__( DeviceAttributes.tank_ejected: False, DeviceAttributes.water_change_reminder: False, DeviceAttributes.water_shortage: False, - }, - ) + }) def build_query(self): return [MessageQuery01(self._protocol_version)] @@ -79,9 +74,7 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaB0Device._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaB0Device._status.get(value) - ) + self._attributes[DeviceAttributes.status] = MideaB0Device._status.get(value) else: self._attributes[DeviceAttributes.status] = None else: diff --git a/custom_components/midea_ac_lan/midea/devices/b0/message.py b/custom_components/midea_ac_lan/midea/devices/b0/message.py index ebed3bdd..6503d4cc 100644 --- a/custom_components/midea_ac_lan/midea/devices/b0/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b0/message.py @@ -1,13 +1,18 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody +) class MessageB0Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): + def __init__(self, protocol_version, message_type, body_type): super().__init__( device_type=0xB0, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00, - ) + body_type=0x00) @property def _body(self): @@ -33,8 +37,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -57,11 +60,9 @@ def __init__(self, body): if len(body) > 15: self.door = (body[32] & 0x02) > 0 self.status = body[31] - self.time_remaining = ( - (0 if body[22] == 0xFF else body[22]) * 3600 - + (0 if body[23] == 0xFF else body[23]) * 60 - + (0 if body[24] == 0xFF else body[24]) - ) + self.time_remaining = (0 if body[22] == 0xFF else body[22]) * 3600 + \ + (0 if body[23] == 0xFF else body[23]) * 60 + \ + (0 if body[24] == 0xFF else body[24]) self.current_temperature = (body[25] << 8) + (body[26]) if self.current_temperature == 0: self.current_temperature = (body[27] << 8) + body[28] @@ -81,3 +82,4 @@ def __init__(self, message): else: self.set_body(B0MessageBody(super().body)) self.set_attr() + diff --git a/custom_components/midea_ac_lan/midea/devices/b1/device.py b/custom_components/midea_ac_lan/midea/devices/b1/device.py index 9b78b67c..4f62e6a9 100644 --- a/custom_components/midea_ac_lan/midea/devices/b1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b1/device.py @@ -1,12 +1,12 @@ import logging - -from .message import MessageB1Response, MessageQuery - +from .message import ( + MessageQuery, + MessageB1Response +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,26 +24,22 @@ class DeviceAttributes(StrEnum): class MideaB1Device(MiedaDevice): _status = { - 0x01: "Standby", - 0x02: "Idle", - 0x03: "Working", - 0x04: "Finished", - 0x05: "Delay", - 0x06: "Paused", + 0x01: "Standby", 0x02: "Idle", 0x03: "Working", + 0x04: "Finished", 0x05: "Delay", 0x06: "Paused" } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -64,8 +60,7 @@ def __init__( DeviceAttributes.tank_ejected: False, DeviceAttributes.water_change_reminder: False, DeviceAttributes.water_shortage: False, - }, - ) + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -79,9 +74,7 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaB1Device._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaB1Device._status.get(value) - ) + self._attributes[DeviceAttributes.status] = MideaB1Device._status.get(value) else: self._attributes[DeviceAttributes.status] = None else: diff --git a/custom_components/midea_ac_lan/midea/devices/b1/message.py b/custom_components/midea_ac_lan/midea/devices/b1/message.py index d7d6fb63..bbf50abd 100644 --- a/custom_components/midea_ac_lan/midea/devices/b1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b1/message.py @@ -1,13 +1,18 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody +) class MessageB1Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): + def __init__(self, protocol_version, message_type, body_type): super().__init__( device_type=0xB1, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00, - ) + body_type=0x00) @property def _body(self): @@ -33,11 +37,9 @@ def __init__(self, body): super().__init__(body) self.door = (body[16] & 0x02) > 0 self.status = body[1] - self.time_remaining = ( - (0 if body[6] == 0xFF else body[6]) * 3600 - + (0 if body[7] == 0xFF else body[7]) * 60 - + (0 if body[8] == 0xFF else body[8]) - ) + self.time_remaining = (0 if body[6] == 0xFF else body[6]) * 3600 + \ + (0 if body[7] == 0xFF else body[7]) * 60 + \ + (0 if body[8] == 0xFF else body[8]) self.current_temperature = body[19] self.tank_ejected = (body[16] & 0x04) > 0 self.water_shortage = (body[16] & 0x08) > 0 @@ -50,3 +52,4 @@ def __init__(self, message): if self.message_type in [MessageType.notify1, MessageType.query]: self.set_body(B1MessageBody(super().body)) self.set_attr() + diff --git a/custom_components/midea_ac_lan/midea/devices/b3/device.py b/custom_components/midea_ac_lan/midea/devices/b3/device.py index 8894dd19..41fe63c9 100644 --- a/custom_components/midea_ac_lan/midea/devices/b3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b3/device.py @@ -1,12 +1,12 @@ import logging - -from .message import MessageB3Response, MessageQuery - +from .message import ( + MessageQuery, + MessageB3Response +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -39,25 +39,22 @@ class DeviceAttributes(StrEnum): class MideaB2Device(MiedaDevice): _status = { - 0x00: "Off", - 0x01: "Standby", - 0x02: "Working", - 0x03: "Delay", - 0x04: "Finished", + 0x00: "Off", 0x01: "Standby", 0x02: "Working", + 0x03: "Delay", 0x04: "Finished" } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -93,8 +90,7 @@ def __init__( DeviceAttributes.bottom_compartment_preheating: False, DeviceAttributes.bottom_compartment_cooling: False, DeviceAttributes.lock: False, - }, - ) + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -109,7 +105,7 @@ def process_message(self, msg): if status in [ DeviceAttributes.top_compartment_status, DeviceAttributes.middle_compartment_status, - DeviceAttributes.bottom_compartment_status, + DeviceAttributes.bottom_compartment_status ]: if value in MideaB2Device._status.keys(): self._attributes[status] = MideaB2Device._status.get(value) diff --git a/custom_components/midea_ac_lan/midea/devices/b3/message.py b/custom_components/midea_ac_lan/midea/devices/b3/message.py index c06e22a8..a4c68ca5 100644 --- a/custom_components/midea_ac_lan/midea/devices/b3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b3/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody +) class MessageB3Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xB3, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x31, - ) + body_type=0x31) @property def _body(self): @@ -35,37 +39,25 @@ def __init__(self, body): self.top_compartment_mode = body[2] self.top_compartment_temperature = body[3] self.top_compartment_remaining = ( - body[23] * 3600 - if len(body) > 23 and body[23] != 0xFF - else ( - 0 + body[4] * 60 - if body[4] != 0xFF - else 0 + body[5] if body[5] != 0xFF else 0 - ) + body[23] * 3600 if len(body) > 23 and body[23] != 0xFF else 0 + + body[4] * 60 if body[4] != 0xFF else 0 + + body[5] if body[5] != 0xFF else 0 ) self.bottom_compartment_status = body[6] self.bottom_compartment_mode = body[7] self.bottom_compartment_temperature = body[8] self.bottom_compartment_remaining = ( - body[24] * 3600 - if len(body) > 24 and body[24] != 0xFF - else ( - 0 + body[9] * 60 - if body[9] != 0xFF - else 0 + body[10] if body[10] != 0xFF else 0 - ) + body[24] * 3600 if len(body) > 24 and body[24] != 0xFF else 0 + + body[9] * 60 if body[9] != 0xFF else 0 + + body[10] if body[10] != 0xFF else 0 ) self.middle_compartment_status = body[17] self.middle_compartment_mode = body[18] self.middle_compartment_temperature = body[19] self.middle_compartment_remaining = ( - body[25] * 3600 - if len(body) > 25 and body[25] != 0xFF - else ( - 0 + body[20] * 60 - if body[20] != 0xFF - else 0 + body[21] if body[21] != 0xFF else 0 - ) + body[25] * 3600 if len(body) > 25 and body[25] != 0xFF else 0 + + body[20] * 60 if body[20] != 0xFF else 0 + + body[21] if body[21] != 0xFF else 0 ) self.lock = body[11] & 0x01 > 0 self.bottom_compartment_door = body[11] & 0x02 > 0 @@ -86,37 +78,25 @@ def __init__(self, body): self.top_compartment_mode = body[2] self.top_compartment_temperature = body[3] self.top_compartment_remaining = ( - body[17] * 3600 - if len(body) > 17 and body[17] != 0xFF - else ( - 0 + body[4] * 60 - if body[4] != 0xFF - else 0 + body[5] if body[5] != 0xFF else 0 - ) + body[17] * 3600 if len(body) > 17 and body[17] != 0xFF else 0 + + body[4] * 60 if body[4] != 0xFF else 0 + + body[5] if body[5] != 0xFF else 0 ) self.bottom_compartment_status = body[6] self.bottom_compartment_mode = body[7] self.bottom_compartment_temperature = body[8] self.bottom_compartment_remaining = ( - body[18] * 3600 - if len(body) > 18 and body[18] != 0xFF - else ( - 0 + body[9] * 60 - if body[9] != 0xFF - else 0 + body[10] if body[10] != 0xFF else 0 - ) + body[18] * 3600 if len(body) > 18 and body[18] != 0xFF else 0 + + body[9] * 60 if body[9] != 0xFF else 0 + + body[10] if body[10] != 0xFF else 0 ) self.middle_compartment_status = body[12] self.middle_compartment_mode = body[13] self.middle_compartment_temperature = body[14] self.middle_compartment_remaining = ( - body[19] * 3600 - if len(body) > 19 and body[19] != 0xFF - else ( - 0 + body[15] * 60 - if body[15] != 0xFF - else 0 + body[16] if body[16] != 0xFF else 0 - ) + body[19] * 3600 if len(body) > 19 and body[19] != 0xFF else 0 + + body[15] * 60 if body[15] != 0xFF else 0 + + body[16] if body[16] != 0xFF else 0 ) self.lock = body[11] & 0x01 > 0 @@ -128,38 +108,34 @@ def __init__(self, body): self.top_compartment_mode = body[6] self.top_compartment_temperature = body[7] self.top_compartment_remaining = ( - body[8] * 60 if body[8] != 0xFF else 0 + body[9] if body[9] != 0xFF else 0 + body[8] * 60 if body[8] != 0xFF else 0 + + body[9] if body[9] != 0xFF else 0 ) self.bottom_compartment_status = body[10] self.bottom_compartment_mode = body[11] self.bottom_compartment_temperature = body[12] self.bottom_compartment_remaining = ( - body[13] * 60 - if body[13] != 0xFF - else 0 + body[14] if body[14] != 0xFF else 0 + body[13] * 60 if body[13] != 0xFF else 0 + + body[14] if body[14] != 0xFF else 0 ) self.bottom_compartment_status = body[15] self.bottom_compartment_mode = body[16] self.bottom_compartment_temperature = body[17] self.bottom_compartment_remaining = ( - body[18] * 60 - if body[18] != 0xFF - else 0 + body[19] if body[19] != 0xFF else 0 + body[18] * 60 if body[18] != 0xFF else 0 + + body[19] if body[19] != 0xFF else 0 ) class MessageB3Response(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - self.message_type == MessageType.query - and self.body_type == 0x31 - or self.message_type == MessageType.notify1 - and self.body_type == 0x41 - ): + if (self.message_type == MessageType.query and self.body_type == 0x31 or + self.message_type == MessageType.notify1 and self.body_type == 0x41): self.set_body(B3MessageBody31(super().body)) elif self.message_type == MessageType.set and self.body_type == 0x21: self.set_body(B3MessageBody21(super().body)) elif self.message_type == MessageType.set and self.body_type == 0x24: self.set_body(B3MessageBody21(super().body)) self.set_attr() + diff --git a/custom_components/midea_ac_lan/midea/devices/b4/device.py b/custom_components/midea_ac_lan/midea/devices/b4/device.py index 72824244..56e53d67 100644 --- a/custom_components/midea_ac_lan/midea/devices/b4/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b4/device.py @@ -1,12 +1,12 @@ import logging - -from .message import MessageB4Response, MessageQuery - +from .message import ( + MessageQuery, + MessageB4Response +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,26 +24,22 @@ class DeviceAttributes(StrEnum): class MideaB4Device(MiedaDevice): _status = { - 0x01: "Standby", - 0x02: "Idle", - 0x03: "Working", - 0x04: "Finished", - 0x05: "Delay", - 0x06: "Paused", + 0x01: "Standby", 0x02: "Idle", 0x03: "Working", + 0x04: "Finished", 0x05: "Delay", 0x06: "Paused" } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -64,8 +60,7 @@ def __init__( DeviceAttributes.tank_ejected: False, DeviceAttributes.water_change_reminder: False, DeviceAttributes.water_shortage: False, - }, - ) + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -79,9 +74,7 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaB4Device._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaB4Device._status.get(value) - ) + self._attributes[DeviceAttributes.status] = MideaB4Device._status.get(value) else: self._attributes[DeviceAttributes.status] = None else: diff --git a/custom_components/midea_ac_lan/midea/devices/b4/message.py b/custom_components/midea_ac_lan/midea/devices/b4/message.py index 07cc1ee9..489c0f14 100644 --- a/custom_components/midea_ac_lan/midea/devices/b4/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b4/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody +) class MessageB4Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xB4, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -31,12 +35,10 @@ def _body(self): class B4MessageBody(MessageBody): def __init__(self, body): super().__init__(body) - self.time_remaining = ( - (0 if body[22] == 0xFF else body[22]) * 3600 - + (0 if body[23] == 0xFF else body[23]) * 60 - + (0 if body[24] == 0xFF else body[24]) - ) - self.current_temperature = (body[25] << 8) + body[26] + self.time_remaining = (0 if body[22] == 0xFF else body[22]) * 3600 + \ + (0 if body[23] == 0xFF else body[23]) * 60 + \ + (0 if body[24] == 0xFF else body[24]) + self.current_temperature = (body[25] << 8 ) + body[26] if self.current_temperature == 0: self.current_temperature = (body[27] << 8) + body[28] self.status = body[31] @@ -49,11 +51,8 @@ def __init__(self, body): class MessageB4Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [ - MessageType.notify1, - MessageType.query, - MessageType.set, - ]: + if self.message_type in [MessageType.notify1, MessageType.query, MessageType.set]: if self.body_type == 0x01: self.set_body(B4MessageBody(super().body)) self.set_attr() + diff --git a/custom_components/midea_ac_lan/midea/devices/b6/device.py b/custom_components/midea_ac_lan/midea/devices/b6/device.py index 88b50c12..c9d68a1d 100644 --- a/custom_components/midea_ac_lan/midea/devices/b6/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b6/device.py @@ -1,13 +1,14 @@ -import json import logging - -from .message import MessageB6Response, MessageQuery, MessageSet - +import json +from .message import ( + MessageQuery, + MessageB6Response, + MessageSet +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -25,17 +26,17 @@ class DeviceAttributes(StrEnum): class MideaB6Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -55,10 +56,11 @@ def __init__( DeviceAttributes.fan_level: 0, DeviceAttributes.fan_speed: 0, DeviceAttributes.oilcup_full: False, - DeviceAttributes.cleaning_reminder: False, - }, - ) - self._default_speeds = {0: "Off", 1: "Level 1", 2: "Level 2"} + DeviceAttributes.cleaning_reminder: False + }) + self._default_speeds = { + 0: "Off", 1: "Level 1", 2: "Level 2" + } self._default_power_speed = 2 self._power_speed = self._default_power_speed self._speeds = self._default_speeds @@ -85,21 +87,13 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.fan_level: if value in self._speeds.keys(): - self._attributes[DeviceAttributes.mode] = self._speeds.get( - value - ) - self._attributes[DeviceAttributes.fan_speed] = list( - self._speeds.keys() - ).index(value) + self._attributes[DeviceAttributes.mode] = self._speeds.get(value) + self._attributes[DeviceAttributes.fan_speed] = list(self._speeds.keys()).index(value) else: self._attributes[DeviceAttributes.mode] = None self._attributes[DeviceAttributes.fan_speed] = 0 - new_status[DeviceAttributes.mode.value] = self._attributes[ - DeviceAttributes.mode - ] - new_status[DeviceAttributes.fan_speed.value] = self._attributes[ - DeviceAttributes.fan_speed - ] + new_status[DeviceAttributes.mode.value] = self._attributes[DeviceAttributes.mode] + new_status[DeviceAttributes.fan_speed.value] = self._attributes[DeviceAttributes.fan_speed] self._attributes[status] = getattr(message, str(status)) new_status[str(status)] = self._attributes[status] return new_status @@ -113,9 +107,8 @@ def set_attribute(self, attr, value): elif attr == DeviceAttributes.mode: if value in self._speeds.values(): message = MessageSet(self._protocol_version) - message.fan_level = list(self._speeds.keys())[ - list(self._speeds.values()).index(value) - ] + message.fan_level = \ + list(self._speeds.keys())[list(self._speeds.values()).index(value)] elif not value: message = MessageSet(self._protocol_version) message.power = False @@ -137,9 +130,8 @@ def turn_on(self, fan_speed=None, mode=None): else: message.fan_level = self._power_speed if mode is not None in self._speeds.values(): - message.fan_level = list(self._speeds.keys())[ - list(self._speeds.values()).index(mode) - ] + message.fan_level = \ + list(self._speeds.keys())[list(self._speeds.values()).index(mode)] self.build_send(message) def set_customize(self, customize): @@ -159,9 +151,7 @@ def set_customize(self, customize): keys = sorted(speeds.keys()) for k in keys: self._speeds[k] = speeds[k] - self.update_all( - {"speeds": self._speeds, "default_speed": self._power_speed} - ) + self.update_all({"speeds": self._speeds, "default_speed": self._power_speed}) except Exception as e: _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") diff --git a/custom_components/midea_ac_lan/midea/devices/b6/message.py b/custom_components/midea_ac_lan/midea/devices/b6/message.py index 53a1b78e..4ec8010c 100644 --- a/custom_components/midea_ac_lan/midea/devices/b6/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b6/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageB6Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xB6, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,7 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x11 if protocol_version == 2 else 0x31, + body_type=0x11 if protocol_version == 2 else 0x31 ) @property @@ -78,9 +83,10 @@ def _body(self): else: value2 = 0x02 value3 = self.fan_level - return bytearray( - [0x01, light, value2, value3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] - ) + return bytearray([ + 0x01, light, value2, value3, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + ]) else: value13 = 0xFF value14 = 0xFF @@ -107,7 +113,10 @@ def _body(self): value13 = 0x02 value14 = 0x02 value15 = 0x01 if self.light else 0x00 - return bytearray([0x01, value13, value14, value15, value16, 0xFF, 0xFF]) + return bytearray([ + 0x01, value13, value14, value15, value16, + 0xFF, 0xFF + ]) class B6FeedbackBody(MessageBody): @@ -148,7 +157,7 @@ class B6NewProtocolBody(MessageBody): def __init__(self, body): super().__init__(body) if body[1] == 0x01: - pack_bytes = body[3 : 3 + body[2]] + pack_bytes = body[3: 3 + body[2]] if pack_bytes[1] != 0xFF: self.power = True self.power = pack_bytes[1] not in [0x00, 0x01, 0x05, 0x07] @@ -180,17 +189,9 @@ def __init__(self, body): class MessageB6Response(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - self.message_type == MessageType.set - and self.body_type == 0x22 - and super().body[1] == 0x01 - ): + if self.message_type == MessageType.set and self.body_type == 0x22 and super().body[1] == 0x01: self.set_body(B6SpecialBody(super().body)) - elif ( - self.message_type == MessageType.set - and self.body_type == 0x11 - and super().body[1] == 0x01 - ): + elif self.message_type == MessageType.set and self.body_type == 0x11 and super().body[1] == 0x01: ############################# pass elif self.message_type == MessageType.query: diff --git a/custom_components/midea_ac_lan/midea/devices/bf/device.py b/custom_components/midea_ac_lan/midea/devices/bf/device.py index d8fe2070..225cba87 100644 --- a/custom_components/midea_ac_lan/midea/devices/bf/device.py +++ b/custom_components/midea_ac_lan/midea/devices/bf/device.py @@ -1,12 +1,12 @@ import logging - -from .message import MessageBFResponse, MessageQuery - +from .message import ( + MessageQuery, + MessageBFResponse +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,26 +24,22 @@ class DeviceAttributes(StrEnum): class MideaBFDevice(MiedaDevice): _status = { - 0x01: "PowerSave", - 0x02: "Standby", - 0x03: "Working", - 0x04: "Finished", - 0x05: "Delay", - 0x06: "Paused", + 0x01: "PowerSave", 0x02: "Standby", 0x03: "Working", + 0x04: "Finished", 0x05: "Delay", 0x06: "Paused" } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -64,8 +60,7 @@ def __init__( DeviceAttributes.tank_ejected: None, DeviceAttributes.water_change_reminder: None, DeviceAttributes.water_shortage: None, - }, - ) + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -79,9 +74,7 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaBFDevice._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaBFDevice._status.get(value) - ) + self._attributes[DeviceAttributes.status] = MideaBFDevice._status.get(value) else: self._attributes[DeviceAttributes.status] = "Unknown" else: diff --git a/custom_components/midea_ac_lan/midea/devices/bf/message.py b/custom_components/midea_ac_lan/midea/devices/bf/message.py index 358ce3eb..e40da95b 100644 --- a/custom_components/midea_ac_lan/midea/devices/bf/message.py +++ b/custom_components/midea_ac_lan/midea/devices/bf/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody +) class MessageBFBase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xBF, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -33,17 +37,14 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x02, - ) + body_type=0x02) self.power = None self.child_lock = None @property def _body(self): power = 0xFF if self.power is None else 0x11 if self.power else 0x01 - child_lock = ( - 0xFF if self.child_lock is None else 0x01 if self.child_lock else 0x00 - ) + child_lock = 0xFF if self.child_lock is None else 0x01 if self.child_lock else 0x00 return bytearray([power, child_lock] + [0xFF] * 7) @@ -51,11 +52,9 @@ class MessageBFBody(MessageBody): def __init__(self, body): super().__init__(body) self.status = body[31] - self.time_remaining = ( - (0 if body[22] == 0xFF else body[22]) * 3600 - + (0 if body[23] == 0xFF else body[23]) * 60 - + (0 if body[24] == 0xFF else body[24]) - ) + self.time_remaining = (0 if body[22] == 0xFF else body[22]) * 3600 + \ + (0 if body[23] == 0xFF else body[23]) * 60 + \ + (0 if body[24] == 0xFF else body[24]) cur_temperature = body[25] * 256 + body[26] if cur_temperature == 0: cur_temperature = body[27] * 256 + body[28] @@ -70,10 +69,6 @@ def __init__(self, body): class MessageBFResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - self.message_type - in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01 - ): + if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: self.set_body(MessageBFBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/c2/device.py b/custom_components/midea_ac_lan/midea/devices/c2/device.py index 3e1a0444..580f4b98 100644 --- a/custom_components/midea_ac_lan/midea/devices/c2/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c2/device.py @@ -1,13 +1,15 @@ -import json import logging - -from .message import MessageC2Response, MessagePower, MessageQuery, MessageSet - +import json +from .message import ( + MessageQuery, + MessageC2Response, + MessageSet, + MessagePower +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -31,17 +33,17 @@ class DeviceAttributes(StrEnum): class MideaC2Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -67,9 +69,8 @@ def __init__( DeviceAttributes.seat_temp_level: 0, DeviceAttributes.water_temperature: None, DeviceAttributes.seat_temperature: None, - DeviceAttributes.filter_life: None, - }, - ) + DeviceAttributes.filter_life: None + }) self._max_dry_level = None self._max_water_temp_level = None self._max_seat_temp_level = None @@ -114,7 +115,7 @@ def set_attribute(self, attr, value): DeviceAttributes.foam_shield, DeviceAttributes.water_temp_level, DeviceAttributes.seat_temp_level, - DeviceAttributes.dry_level, + DeviceAttributes.dry_level ]: message = MessageSet(self._protocol_version) setattr(message, attr, value) @@ -136,17 +137,10 @@ def set_customize(self, customize): self._max_seat_temp_level = params.get("max_seat_temp_level") except Exception as e: _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all( - { - "dry_level": {"max_dry_level": self._max_dry_level}, - "water_temp_level": { - "max_water_temp_level": self._max_water_temp_level - }, - "seat_temp_level": { - "max_seat_temp_level": self._max_seat_temp_level - }, - } - ) + self.update_all({"dry_level": {"max_dry_level": self._max_dry_level}, + "water_temp_level": {"max_water_temp_level": self._max_water_temp_level}, + "seat_temp_level": {"max_seat_temp_level": self._max_seat_temp_level} + }) class MideaAppliance(MideaC2Device): diff --git a/custom_components/midea_ac_lan/midea/devices/c2/message.py b/custom_components/midea_ac_lan/midea/devices/c2/message.py index 4ab2c023..71c608b4 100644 --- a/custom_components/midea_ac_lan/midea/devices/c2/message.py +++ b/custom_components/midea_ac_lan/midea/devices/c2/message.py @@ -1,6 +1,10 @@ from enum import IntEnum - -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class C2MessageEnum(IntEnum): @@ -8,8 +12,8 @@ class C2MessageEnum(IntEnum): child_lock = 0x10 foam_shield = 0x1F water_temp_level = 0x09 - seat_temp_level = 0x0A - dry_level = 0x0C + seat_temp_level = 0x0a + dry_level = 0x0c C2_MESSAGE_KEYS = { @@ -17,22 +21,8 @@ class C2MessageEnum(IntEnum): C2MessageEnum.sensor_light: {True: 0x01 << 1, False: 0x00}, C2MessageEnum.foam_shield: {True: 0x01 << 2, False: 0x00}, C2MessageEnum.dry_level: {0: 0x00, 1: 0x01 << 1, 2: 0x02 << 1, 3: 0x03 << 1}, - C2MessageEnum.seat_temp_level: { - 0: 0x00, - 1: 0x01 << 3, - 2: 0x02 << 3, - 3: 0x03 << 3, - 4: 0x04 << 3, - 5: 0x05 << 3, - }, - C2MessageEnum.water_temp_level: { - 0: 0x00, - 1: 0x01, - 2: 0x02, - 3: 0x03, - 4: 0x04, - 5: 0x05, - }, + C2MessageEnum.seat_temp_level: {0: 0x00, 1: 0x01 << 3, 2: 0x02 << 3, 3: 0x03 << 3, 4: 0x04 << 3, 5: 0x05 << 3}, + C2MessageEnum.water_temp_level: {0: 0x00, 1: 0x01, 2: 0x02, 3: 0x03, 4: 0x04, 5: 0x05} } @@ -42,7 +32,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xC2, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -55,8 +45,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -68,10 +57,8 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00, - ) + body_type=0x00) self.power = False - @property def _body(self): if self.power: @@ -86,8 +73,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02, - ) + body_type=0x02) @property def _body(self): @@ -99,8 +85,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00, - ) + body_type=0x00) self.child_lock = None self.sensor_light = None @@ -141,9 +126,9 @@ def __init__(self, body): super().__init__(body) self.power = (body[2] & 0x01) > 0 self.seat_status = (body[3] & 0x01) > 0 - self.dry_level = (body[6] & 0x7E) >> 1 - self.water_temp_level = body[9] & 0x07 - self.seat_temp_level = (body[9] & 0x38) >> 3 + self.dry_level = ((body[6] & 0x7E) >> 1) + self.water_temp_level = (body[9] & 0x07) + self.seat_temp_level = ((body[9] & 0x38) >> 3) self.lid_status = (body[12] & 0x40) > 0 self.foam_shield = (body[13] & 0x80) > 0 self.sensor_light = (body[14] & 0x01) > 0 @@ -162,10 +147,6 @@ def __init__(self, body): class MessageC2Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [ - MessageType.notify1, - MessageType.query, - MessageType.set, - ]: + if self.message_type in [MessageType.notify1, MessageType.query, MessageType.set]: self.set_body(C2MessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/c3/device.py b/custom_components/midea_ac_lan/midea/devices/c3/device.py index c3c252cd..cc8ba680 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/device.py @@ -1,18 +1,15 @@ import logging - from .message import ( - MessageC3Response, MessageQuery, - MessageSet, - MessageSetECO, MessageSetSilent, + MessageSetECO, + MessageC3Response, + MessageSet ) - try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -63,17 +60,17 @@ class DeviceAttributes(StrEnum): class MideaC3Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -126,9 +123,8 @@ def __init__( DeviceAttributes.status_ibh: None, DeviceAttributes.total_produced_energy: None, DeviceAttributes.outdoor_temperature: None, - DeviceAttributes.error_code: 0, - }, - ) + DeviceAttributes.error_code: 0 + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -141,48 +137,28 @@ def process_message(self, msg): if hasattr(message, str(status)): self._attributes[status] = getattr(message, str(status)) new_status[str(status)] = getattr(message, str(status)) - if "zone_temp_type" in new_status: + if 'zone_temp_type' in new_status: for zone in [0, 1]: - if self._attributes[DeviceAttributes.zone_temp_type][ - zone - ]: # Water temp mode - self._attributes[DeviceAttributes.target_temperature][zone] = ( + if self._attributes[DeviceAttributes.zone_temp_type][zone]: # Water temp mode + self._attributes[DeviceAttributes.target_temperature][zone] = \ self._attributes[DeviceAttributes.zone_target_temp][zone] - ) - if ( - self._attributes[DeviceAttributes.mode_auto] == 2 - ): # cooling mode - self._attributes[DeviceAttributes.temperature_max][zone] = ( - self._attributes[DeviceAttributes.zone_cooling_temp_max][ - zone - ] - ) - self._attributes[DeviceAttributes.temperature_min][zone] = ( - self._attributes[DeviceAttributes.zone_cooling_temp_min][ - zone - ] - ) + if self._attributes[DeviceAttributes.mode_auto] == 2: # cooling mode + self._attributes[DeviceAttributes.temperature_max][zone] = \ + self._attributes[DeviceAttributes.zone_cooling_temp_max][zone] + self._attributes[DeviceAttributes.temperature_min][zone] = \ + self._attributes[DeviceAttributes.zone_cooling_temp_min][zone] elif self._attributes[DeviceAttributes.mode] == 3: # heating mode - self._attributes[DeviceAttributes.temperature_max][zone] = ( - self._attributes[DeviceAttributes.zone_heating_temp_max][ - zone - ] - ) - self._attributes[DeviceAttributes.temperature_min][zone] = ( - self._attributes[DeviceAttributes.zone_heating_temp_min][ - zone - ] - ) + self._attributes[DeviceAttributes.temperature_max][zone] = \ + self._attributes[DeviceAttributes.zone_heating_temp_max][zone] + self._attributes[DeviceAttributes.temperature_min][zone] = \ + self._attributes[DeviceAttributes.zone_heating_temp_min][zone] else: # Room temp mode - self._attributes[DeviceAttributes.target_temperature][zone] = ( + self._attributes[DeviceAttributes.target_temperature][zone] = \ self._attributes[DeviceAttributes.room_target_temp] - ) - self._attributes[DeviceAttributes.temperature_max][zone] = ( + self._attributes[DeviceAttributes.temperature_max][zone] = \ self._attributes[DeviceAttributes.room_temp_max] - ) - self._attributes[DeviceAttributes.temperature_min][zone] = ( + self._attributes[DeviceAttributes.temperature_min][zone] = \ self._attributes[DeviceAttributes.room_temp_min] - ) if self._attributes[DeviceAttributes.zone1_power]: if self._attributes[DeviceAttributes.zone_temp_type][zone]: self._attributes[DeviceAttributes.zone1_water_temp_mode] = True @@ -203,18 +179,14 @@ def process_message(self, msg): else: self._attributes[DeviceAttributes.zone2_water_temp_mode] = False self._attributes[DeviceAttributes.zone2_room_temp_mode] = False - new_status[DeviceAttributes.zone1_water_temp_mode.value] = self._attributes[ - DeviceAttributes.zone1_water_temp_mode - ] - new_status[DeviceAttributes.zone2_water_temp_mode.value] = self._attributes[ - DeviceAttributes.zone2_water_temp_mode - ] - new_status[DeviceAttributes.zone1_room_temp_mode.value] = self._attributes[ - DeviceAttributes.zone1_room_temp_mode - ] - new_status[DeviceAttributes.zone2_room_temp_mode.value] = self._attributes[ - DeviceAttributes.zone2_room_temp_mode - ] + new_status[DeviceAttributes.zone1_water_temp_mode.value] = \ + self._attributes[DeviceAttributes.zone1_water_temp_mode] + new_status[DeviceAttributes.zone2_water_temp_mode.value] = \ + self._attributes[DeviceAttributes.zone2_water_temp_mode] + new_status[DeviceAttributes.zone1_room_temp_mode.value] = \ + self._attributes[DeviceAttributes.zone1_room_temp_mode] + new_status[DeviceAttributes.zone2_room_temp_mode.value] = \ + self._attributes[DeviceAttributes.zone2_room_temp_mode] return new_status @@ -235,9 +207,7 @@ def make_message_set(self): return message def set_attribute(self, attr, value): - - message = None - + message= None if attr in [ DeviceAttributes.zone1_power, DeviceAttributes.zone2_power, @@ -247,7 +217,7 @@ def set_attribute(self, attr, value): DeviceAttributes.disinfect, DeviceAttributes.fast_dhw, DeviceAttributes.dhw_target_temp, - DeviceAttributes.tbh, + DeviceAttributes.tbh ]: message = self.make_message_set() setattr(message, str(attr), value) diff --git a/custom_components/midea_ac_lan/midea/devices/c3/message.py b/custom_components/midea_ac_lan/midea/devices/c3/message.py index cd2d4243..c339918e 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageC3Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xC3, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -33,8 +37,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01, - ) + body_type=0x01) self.zone1_power = False self.zone2_power = False self.dhw_power = False @@ -63,17 +66,12 @@ def _body(self): zone1_target_temp = int(self.zone_target_temp[0]) zone2_target_temp = int(self.zone_target_temp[1]) dhw_target_temp = int(self.dhw_target_temp) - return bytearray( - [ - zone1_power | zone2_power | dhw_power, - self.mode, - zone1_target_temp, - zone2_target_temp, - dhw_target_temp, - room_target_temp, - zone1_curve | zone2_curve | disinfect | fast_dhw, - ] - ) + return bytearray([ + zone1_power | zone2_power | dhw_power, + self.mode, zone1_target_temp, zone2_target_temp, + dhw_target_temp, room_target_temp, + zone1_curve | zone2_curve | disinfect | fast_dhw + ]) class MessageSetSilent(MessageC3Base): @@ -81,8 +79,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x05, - ) + body_type=0x05) self.silent_mode = False self.super_silent = False @@ -91,9 +88,11 @@ def _body(self): silent_mode = 0x01 if self.silent_mode else 0 super_silent = 0x02 if self.super_silent else 0 - return bytearray( - [silent_mode | super_silent, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - ) + return bytearray([ + silent_mode | super_silent, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + ]) class MessageSetECO(MessageC3Base): @@ -101,15 +100,18 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x07, - ) + body_type=0x07) self.eco_mode = False @property def _body(self): eco_mode = 0x01 if self.eco_mode else 0 - return bytearray([eco_mode, 0x00, 0x00, 0x00, 0x00, 0x00]) + return bytearray([ + eco_mode, + 0x00, 0x00, 0x00, 0x00, + 0x00 + ]) class C3MessageBody(MessageBody): @@ -125,19 +127,34 @@ def __init__(self, body, data_offset=0): self.fast_dhw = body[data_offset + 0] & 0x40 > 0 self.zone_temp_type = [ body[data_offset + 1] & 0x10 > 0, - body[data_offset + 1] & 0x20 > 0, + body[data_offset + 1] & 0x20 > 0 ] self.silent_mode = body[data_offset + 2] & 0x02 > 0 self.eco_mode = body[data_offset + 2] & 0x08 > 0 self.mode = body[data_offset + 3] self.mode_auto = body[data_offset + 4] - self.zone_target_temp = [body[data_offset + 5], body[data_offset + 6]] + self.zone_target_temp = [ + body[data_offset + 5], + body[data_offset + 6] + ] self.dhw_target_temp = body[data_offset + 7] self.room_target_temp = body[data_offset + 8] / 2 - self.zone_heating_temp_max = [body[data_offset + 9], body[data_offset + 13]] - self.zone_heating_temp_min = [body[data_offset + 10], body[data_offset + 14]] - self.zone_cooling_temp_max = [body[data_offset + 11], body[data_offset + 15]] - self.zone_cooling_temp_min = [body[data_offset + 12], body[data_offset + 16]] + self.zone_heating_temp_max = [ + body[data_offset + 9], + body[data_offset + 13] + ] + self.zone_heating_temp_min = [ + body[data_offset + 10], + body[data_offset + 14] + ] + self.zone_cooling_temp_max = [ + body[data_offset + 11], + body[data_offset + 15] + ] + self.zone_cooling_temp_min = [ + body[data_offset + 12], + body[data_offset + 16] + ] self.room_temp_max = body[data_offset + 17] / 2 self.room_temp_min = body[data_offset + 18] / 2 self.dhw_temp_max = body[data_offset + 19] @@ -156,30 +173,25 @@ def __init__(self, body, data_offset=0): self.status_heating = (status_byte & 0x01) > 0 self.total_energy_consumption = ( - (body[data_offset + 1] << 32) - + (body[data_offset + 2] << 16) - + (body[data_offset + 3] << 8) - + (body[data_offset + 4]) - ) + (body[data_offset + 1] << 32) + + (body[data_offset + 2] << 16) + + (body[data_offset + 3] << 8) + + (body[data_offset + 4])) self.total_produced_energy = ( - (body[data_offset + 5] << 32) - + (body[data_offset + 6] << 16) - + (body[data_offset + 7] << 8) - + (body[data_offset + 8]) - ) + (body[data_offset + 5] << 32) + + (body[data_offset + 6] << 16) + + (body[data_offset + 7] << 8) + + (body[data_offset + 8])) self.outdoor_temperature = int(body[data_offset + 9]) class MessageC3Response(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - self.message_type - in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01 - ) or self.message_type == MessageType.notify2: + if (self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] + and self.body_type == 0x01) or self.message_type == MessageType.notify2: self.set_body(C3MessageBody(super().body, data_offset=1)) elif self.message_type == MessageType.notify1 and self.body_type == 0x04: self.set_body(C3Notify1MessageBody(super().body, data_offset=1)) - self.set_attr() + self.set_attr() \ No newline at end of file diff --git a/custom_components/midea_ac_lan/midea/devices/ca/device.py b/custom_components/midea_ac_lan/midea/devices/ca/device.py index f169b4ff..8d05ad69 100644 --- a/custom_components/midea_ac_lan/midea/devices/ca/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ca/device.py @@ -1,12 +1,12 @@ import logging - -from .message import MessageCAResponse, MessageQuery - +from .message import ( + MessageQuery, + MessageCAResponse +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -35,17 +35,17 @@ class DeviceAttributes(StrEnum): class MideaCADevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -75,9 +75,8 @@ def __init__( DeviceAttributes.refrigerator_door: False, DeviceAttributes.freezer_door: False, DeviceAttributes.bar_door: False, - DeviceAttributes.flex_zone_door: False, - }, - ) + DeviceAttributes.flex_zone_door: False + }) self._modes = [""] def build_query(self): diff --git a/custom_components/midea_ac_lan/midea/devices/ca/message.py b/custom_components/midea_ac_lan/midea/devices/ca/message.py index 0611a875..b1284262 100644 --- a/custom_components/midea_ac_lan/midea/devices/ca/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ca/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageCABase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCA, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00, - ) + body_type=0x00) @property def _body(self): @@ -31,8 +35,8 @@ def _body(self): class CAGeneralMessageBody(MessageBody): def __init__(self, body): super().__init__(body) - self.refrigerator_setting_temp = body[2] & 0x0F - self.freezer_setting_temp = -12 - ((body[2] & 0xF0) >> 4) + self.refrigerator_setting_temp = (body[2] & 0x0f) + self.freezer_setting_temp = -12 - ((body[2] & 0xf0) >> 4) flex_zone_setting_temp = body[3] right_flex_zone_setting_temp = body[4] @@ -99,23 +103,14 @@ def __init__(self, body): class MessageCAResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0x00 - ) - or (self.message_type == MessageType.notify1 and self.body_type == 0x02) - ) and len(super().body) > 20: + if ((self.message_type in [MessageType.query, MessageType.set] and self.body_type == 0x00) or + (self.message_type == MessageType.notify1 and self.body_type == 0x02)) and len(super().body) > 20: self.set_body(CAGeneralMessageBody(super().body)) - elif ( - self.message_type == MessageType.exception and self.body_type == 0x01 - ) or (self.message_type == 0x03 and self.body_type == 0x02): + elif (self.message_type == MessageType.exception and self.body_type == 0x01) or \ + (self.message_type == 0x03 and self.body_type == 0x02): self.set_body(CAExceptionMessageBody(super().body)) elif self.message_type == MessageType.notify1 and self.body_type == 0x00: self.set_body(CANotify00MessageBody(super().body)) - elif ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0x01 - ): + elif self.message_type in [MessageType.query, MessageType.notify1] and self.body_type == 0x01: self.set_body(CANotify01MessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cc/device.py b/custom_components/midea_ac_lan/midea/devices/cc/device.py index 680a0ec9..a82a5fbe 100644 --- a/custom_components/midea_ac_lan/midea/devices/cc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cc/device.py @@ -1,12 +1,13 @@ import logging - -from .message import MessageCCResponse, MessageQuery, MessageSet - +from .message import ( + MessageQuery, + MessageSet, + MessageCCResponse +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -33,29 +34,26 @@ class DeviceAttributes(StrEnum): class MideaCCDevice(MiedaDevice): _fan_speeds_7level = { - 0x01: "Level 1", - 0x02: "Level 2", - 0x04: "Level 3", - 0x08: "Level 4", - 0x10: "Level 5", - 0x20: "Level 6", - 0x40: "Level 7", - 0x80: "Auto", + 0x01: "Level 1", 0x02: "Level 2", 0x04: "Level 3", + 0x08: "Level 4", 0x10: "Level 5", 0x20: "Level 6", + 0x40: "Level 7", 0x80: "Auto", + } + _fan_speeds_3level = { + 0x01: "Low", 0x08: "Medium", 0x40: "High", 0x80: "Auto" } - _fan_speeds_3level = {0x01: "Low", 0x08: "Medium", 0x40: "High", 0x80: "Auto"} def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -84,9 +82,8 @@ def __init__( DeviceAttributes.fan_speed_level: None, DeviceAttributes.indoor_temperature: None, DeviceAttributes.temperature_precision: 1, - DeviceAttributes.temp_fahrenheit: False, - }, - ) + DeviceAttributes.temp_fahrenheit: False + }) self._fan_speeds = None @property @@ -109,46 +106,32 @@ def process_message(self, msg): else: self._attributes[status] = getattr(message, str(status)) new_status[str(status)] = getattr(message, str(status)) - if ( - fan_speed is not None - and self._attributes[DeviceAttributes.fan_speed_level] is not None - ): + if fan_speed is not None and self._attributes[DeviceAttributes.fan_speed_level] is not None: if self._fan_speeds is None: if self._attributes[DeviceAttributes.fan_speed_level]: self._fan_speeds = MideaCCDevice._fan_speeds_3level else: self._fan_speeds = MideaCCDevice._fan_speeds_7level if fan_speed in self._fan_speeds.keys(): - self._attributes[DeviceAttributes.fan_speed] = self._fan_speeds.get( - fan_speed - ) + self._attributes[DeviceAttributes.fan_speed] = self._fan_speeds.get(fan_speed) else: self._attributes[DeviceAttributes.fan_speed] = None - new_status[DeviceAttributes.fan_speed.value] = self._attributes[ - DeviceAttributes.fan_speed - ] - aux_heating = ( - self._attributes[DeviceAttributes.aux_heat_status] == 1 - or self._attributes[DeviceAttributes.auto_aux_heat_running] - ) + new_status[DeviceAttributes.fan_speed.value] = self._attributes[DeviceAttributes.fan_speed] + aux_heating = \ + self._attributes[DeviceAttributes.aux_heat_status] == 1 or \ + self._attributes[DeviceAttributes.auto_aux_heat_running] if self._attributes[DeviceAttributes.aux_heating] != aux_heating: self._attributes[DeviceAttributes.aux_heating] = aux_heating - new_status[DeviceAttributes.aux_heating.value] = self._attributes[ - DeviceAttributes.aux_heating - ] + new_status[DeviceAttributes.aux_heating.value] = self._attributes[DeviceAttributes.aux_heating] return new_status def make_message_set(self): message = MessageSet(self._protocol_version) message.power = self._attributes[DeviceAttributes.power] message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] + message.target_temperature = self._attributes[DeviceAttributes.target_temperature] message.fan_speed = list(self._fan_speeds.keys())[ - list(self._fan_speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) + list(self._fan_speeds.values()).index(self._attributes[DeviceAttributes.fan_speed]) ] message.eco_mode = self._attributes[DeviceAttributes.eco_mode] message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] @@ -167,13 +150,11 @@ def set_target_temperature(self, target_temperature, mode): def set_attribute(self, attr, value): # if nat a sensor - if attr not in [ - DeviceAttributes.indoor_temperature, - DeviceAttributes.temperature_precision, - DeviceAttributes.fan_speed_level, - DeviceAttributes.aux_heat_status, - DeviceAttributes.auto_aux_heat_running, - ]: + if attr not in [DeviceAttributes.indoor_temperature, + DeviceAttributes.temperature_precision, + DeviceAttributes.fan_speed_level, + DeviceAttributes.aux_heat_status, + DeviceAttributes.auto_aux_heat_running]: message = self.make_message_set() if attr == DeviceAttributes.fan_speed: if value in self._fan_speeds.values(): diff --git a/custom_components/midea_ac_lan/midea/devices/cc/message.py b/custom_components/midea_ac_lan/midea/devices/cc/message.py index f94a7c34..7aa35319 100644 --- a/custom_components/midea_ac_lan/midea/devices/cc/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cc/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageCCBase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -33,8 +37,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0xC3, - ) + body_type=0xC3) self.power = False self.mode = 4 self.fan_speed = 0x80 @@ -70,38 +73,23 @@ def _body(self): sleep_mode = 0x10 if self.sleep_mode else 0 night_light = 0x08 if self.night_light else 0 # Byte11 Dot of target_temperature - temperature_dot = ( - int((self.target_temperature - temperature_integer) * 10) & 0xFF - ) - return bytearray( - [ - power | mode, - fan_speed, - temperature_integer, - # timer - 0x00, - 0x00, - eco_mode | ventilation | swing | aux_heating, - # non-stepless fan speed - 0xFF, - sleep_mode | night_light, - 0x00, - 0x00, - temperature_dot, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + temperature_dot = int((self.target_temperature - temperature_integer) * 10) & 0xFF + return bytearray([ + power | mode, + fan_speed, + temperature_integer, + # timer + 0x00, 0x00, + eco_mode | ventilation | swing | aux_heating, + # non-stepless fan speed + 0xFF, + sleep_mode | night_light, + 0x00, 0x00, + temperature_dot, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + ]) class CCGeneralMessageBody(MessageBody): @@ -131,13 +119,8 @@ def __init__(self, body): class MessageCCResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - (self.message_type == MessageType.query and self.body_type == 0x01) - or ( - self.message_type in [MessageType.notify1, MessageType.notify2] - and self.body_type == 0x01 - ) - or (self.message_type == MessageType.set and self.body_type == 0xC3) - ): + if (self.message_type == MessageType.query and self.body_type == 0x01) or \ + (self.message_type in [MessageType.notify1, MessageType.notify2] and self.body_type == 0x01) or \ + (self.message_type == MessageType.set and self.body_type == 0xC3): self.set_body(CCGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cd/device.py b/custom_components/midea_ac_lan/midea/devices/cd/device.py index a124f735..8de65e50 100644 --- a/custom_components/midea_ac_lan/midea/devices/cd/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cd/device.py @@ -1,13 +1,14 @@ -import json import logging - -from .message import MessageCDResponse, MessageQuery, MessageSet - +import json +from .message import ( + MessageQuery, + MessageSet, + MessageCDResponse +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -30,17 +31,17 @@ class MideaCDDevice(MiedaDevice): _modes = ["Energy-save", "Standard", "Dual", "Smart"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -63,9 +64,8 @@ def __init__( DeviceAttributes.outdoor_temperature: None, DeviceAttributes.condenser_temperature: None, DeviceAttributes.compressor_temperature: None, - DeviceAttributes.compressor_status: None, - }, - ) + DeviceAttributes.compressor_status: None + }) self._fields = {} self._temperature_step = None self._default_temperature_step = 1 @@ -99,20 +99,12 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.mode, - DeviceAttributes.power, - DeviceAttributes.target_temperature, - ]: + if attr in [DeviceAttributes.mode, DeviceAttributes.power, DeviceAttributes.target_temperature]: message = MessageSet(self._protocol_version) message.fields = self._fields - message.mode = MideaCDDevice._modes.index( - self._attributes[DeviceAttributes.mode] - ) + message.mode = MideaCDDevice._modes.index(self._attributes[DeviceAttributes.mode]) message.power = self._attributes[DeviceAttributes.power] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] + message.target_temperature = self._attributes[DeviceAttributes.target_temperature] if attr == DeviceAttributes.mode: if value in MideaCDDevice._modes: setattr(message, str(attr), MideaCDDevice._modes.index(value)) diff --git a/custom_components/midea_ac_lan/midea/devices/cd/message.py b/custom_components/midea_ac_lan/midea/devices/cd/message.py index 454cfe00..450345e5 100644 --- a/custom_components/midea_ac_lan/midea/devices/cd/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cd/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageCDBase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCD, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -16,12 +21,11 @@ def _body(self): class MessageQuery(MessageCDBase): - def __init__(self, protocol_version): + def __init__(self,protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -29,12 +33,11 @@ def _body(self): class MessageSet(MessageCDBase): - def __init__(self, protocol_version): + def __init__(self,protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01, - ) + body_type=0x01) self.power = False self.target_temperature = 0 self.aux_heating = False @@ -50,18 +53,13 @@ def _body(self): power = 0x01 if self.power else 0x00 mode = self.mode + 1 target_temperature = round(self.target_temperature * 2 + 30) - return bytearray( - [ - 0x01, - power, - mode, - target_temperature, - self.read_field("trValue"), - self.read_field("openPTC"), - self.read_field("ptcTemp"), - 0, # self.read_field("byte8") - ] - ) + return bytearray([ + 0x01, power, mode, target_temperature, + self.read_field("trValue"), + self.read_field("openPTC"), + self.read_field("ptcTemp"), + 0 # self.read_field("byte8") + ]) class CDGeneralMessageBody(MessageBody): @@ -85,7 +83,6 @@ def __init__(self, body): if (body[28] & 0x20) > 0: self.mode = 3 - class CD02MessageBody(MessageBody): def __init__(self, body): super().__init__(body) diff --git a/custom_components/midea_ac_lan/midea/devices/ce/device.py b/custom_components/midea_ac_lan/midea/devices/ce/device.py index c8ac6ecf..f040e27d 100644 --- a/custom_components/midea_ac_lan/midea/devices/ce/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ce/device.py @@ -1,13 +1,14 @@ -import json import logging - -from .message import MessageCEResponse, MessageQuery, MessageSet - +import json +from .message import ( + MessageQuery, + MessageCEResponse, + MessageSet +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -35,20 +36,22 @@ class DeviceAttributes(StrEnum): class MideaCEDevice(MiedaDevice): - _modes = ["Normal", "Sleep mode", "ECO mode"] + _modes = [ + "Normal", "Sleep mode", "ECO mode" + ] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -79,9 +82,8 @@ def __init__( DeviceAttributes.powerful_purify: False, DeviceAttributes.filter_cleaning_reminder: False, DeviceAttributes.filter_change_reminder: False, - DeviceAttributes.error_code: 0, - }, - ) + DeviceAttributes.error_code: 0 + }) self._default_speed_count = 7 self._speed_count = self._default_speed_count self.set_customize(customize) @@ -112,9 +114,7 @@ def process_message(self, msg): self._attributes[DeviceAttributes.mode] = "ECO mode" else: self._attributes[DeviceAttributes.mode] = "None" - new_status[DeviceAttributes.mode.value] = self._attributes[ - DeviceAttributes.mode - ] + new_status[DeviceAttributes.mode.value] = self._attributes[DeviceAttributes.mode] return new_status def make_message_set(self): diff --git a/custom_components/midea_ac_lan/midea/devices/ce/message.py b/custom_components/midea_ac_lan/midea/devices/ce/message.py index 0f6afe3f..dfcb6c9e 100644 --- a/custom_components/midea_ac_lan/midea/devices/ce/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ce/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageFABase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCE, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -33,8 +37,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01, - ) + body_type=0x01) self.power = False self.fan_speed = 0 @@ -56,16 +59,14 @@ def _body(self): powerful_purify = 0x10 if self.powerful_purify else 0x00 scheduled = 0x01 if self.scheduled else 0x00 child_lock = 0x7F if self.child_lock else 0x00 - return bytearray( - [ - power | 0x01, - self.fan_speed, - link_to_ac | sleep_mode | eco_mode | aux_heating | powerful_purify, - scheduled, - 0x00, - child_lock, - ] - ) + return bytearray([ + power | 0x01, + self.fan_speed, + link_to_ac | sleep_mode | eco_mode | aux_heating | powerful_purify, + scheduled, + 0x00, + child_lock + ]) class CEGeneralMessageBody(MessageBody): @@ -125,10 +126,8 @@ def __init__(self, body): class MessageCEResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0x01 - ) or (self.message_type == MessageType.notify1 and self.body_type == 0x02): + if (self.message_type in [MessageType.query, MessageType.set] and self.body_type == 0x01) or \ + (self.message_type == MessageType.notify1 and self.body_type == 0x02): self.set_body(CEGeneralMessageBody(super().body)) elif self.message_type == MessageType.notify1 and self.body_type == 0x01: self.set_body(CENotifyMessageBody(super().body)) diff --git a/custom_components/midea_ac_lan/midea/devices/cf/device.py b/custom_components/midea_ac_lan/midea/devices/cf/device.py index 40b5a9d5..273e3972 100644 --- a/custom_components/midea_ac_lan/midea/devices/cf/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cf/device.py @@ -1,12 +1,13 @@ import logging - -from .message import MessageCFResponse, MessageQuery, MessageSet - +from .message import ( + MessageQuery, + MessageCFResponse, + MessageSet +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,17 +25,17 @@ class DeviceAttributes(StrEnum): class MideaCFDevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -54,9 +55,8 @@ def __init__( DeviceAttributes.aux_heating: False, DeviceAttributes.current_temperature: 0, DeviceAttributes.max_temperature: 55, - DeviceAttributes.min_temperature: 5, - }, - ) + DeviceAttributes.min_temperature: 5 + }) def build_query(self): return [MessageQuery(self._protocol_version)] diff --git a/custom_components/midea_ac_lan/midea/devices/cf/message.py b/custom_components/midea_ac_lan/midea/devices/cf/message.py index 0bf0fb3f..5db9d99a 100644 --- a/custom_components/midea_ac_lan/midea/devices/cf/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cf/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageCFBase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xCF, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -33,8 +37,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01, - ) + body_type=0x01) self.power = False self.mode = 0 # 1 自动 2 制冷 3 制热 self.target_temperature = None @@ -44,15 +47,11 @@ def __init__(self, protocol_version): def _body(self): power = 0x01 if self.power else 0x00 mode = self.mode - target_temperature = ( - 0xFF - if self.target_temperature is None - else (int(self.target_temperature) & 0xFF) - ) - aux_heating = ( - 0xFF if self.aux_heating is None else (0x01 if self.aux_heating else 0x00) - ) - return bytearray([power, mode, target_temperature, aux_heating]) + target_temperature = 0xFF if self.target_temperature is None else (int(self.target_temperature) & 0xFF) + aux_heating = 0xFF if self.aux_heating is None else (0x01 if self.aux_heating else 0x00) + return bytearray([ + power, mode, target_temperature, aux_heating + ]) class CFMessageBody(MessageBody): @@ -78,11 +77,9 @@ def __init__(self, body, data_offset=0): class MessageCFResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0x01 - ): + if self.message_type in [MessageType.query, MessageType.set] and self.body_type == 0x01: self.set_body(CFMessageBody(super().body, data_offset=1)) elif self.message_type in [MessageType.notify1, MessageType.notify2]: self.set_body(CFMessageBody(super().body, data_offset=0)) self.set_attr() + diff --git a/custom_components/midea_ac_lan/midea/devices/da/device.py b/custom_components/midea_ac_lan/midea/devices/da/device.py index f5506e40..305b7bbf 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/device.py +++ b/custom_components/midea_ac_lan/midea/devices/da/device.py @@ -1,12 +1,14 @@ import logging - -from .message import MessageDAResponse, MessagePower, MessageQuery, MessageStart - +from .message import ( + MessageQuery, + MessagePower, + MessageStart, + MessageDAResponse +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -34,17 +36,17 @@ class DeviceAttributes(StrEnum): class MideaDADevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -74,9 +76,8 @@ def __init__( DeviceAttributes.wash_level: None, DeviceAttributes.wash_strength: None, DeviceAttributes.softener: None, - DeviceAttributes.detergent: None, - }, - ) + DeviceAttributes.detergent: None + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -85,47 +86,17 @@ def process_message(self, msg): message = MessageDAResponse(msg) _LOGGER.debug(f"[{self.device_id}] Received: {message}") new_status = {} - progress = ["Idle", "Spin", "Rinse", "Wash", "Weight", "Unknown", "Dry", "Soak"] - program = [ - "Standard", - "Fast", - "Blanket", - "Wool", - "embathe", - "Memory", - "Child", - "Down Jacket", - "Stir", - "Mute", - "Bucket Self Clean", - "Air Dry", - ] + progress = ["Idle", "Spin", "Rinse", "Wash", + "Weight", "Unknown", "Dry", "Soak"] + program = ["Standard", "Fast", "Blanket", "Wool", + "embathe", "Memory", "Child", "Down Jacket", + "Stir", "Mute", "Bucket Self Clean", "Air Dry"] speed = ["-", "Low", "Medium", "High"] strength = ["-", "Week", "Medium", "Strong"] - detergent = [ - "No", - "Less", - "Medium", - "More", - "4", - "5", - "6", - "7", - "8", - "Insufficient", - ] - softener = [ - "No", - "Intelligent", - "Programed", - "3", - "4", - "5", - "6", - "7", - "8", - "Insufficient", - ] + detergent = ["No", "Less", "Medium", "More", "4", + "5", "6", "7", "8", "Insufficient"] + softener = ["No", "Intelligent", "Programed", "3", "4", + "5", "6", "7", "8", "Insufficient"] for status in self._attributes.keys(): if hasattr(message, str(status)): if status == DeviceAttributes.progress: @@ -166,6 +137,5 @@ def set_attribute(self, attr, value): message.washing_data = self._attributes[DeviceAttributes.washing_data] self.build_send(message) - class MideaAppliance(MideaDADevice): pass diff --git a/custom_components/midea_ac_lan/midea/devices/da/message.py b/custom_components/midea_ac_lan/midea/devices/da/message.py index 5578d46b..9baf761a 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/message.py +++ b/custom_components/midea_ac_lan/midea/devices/da/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageDABase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xDA, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x03, - ) + body_type=0x03) @property def _body(self): @@ -33,14 +37,15 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02, - ) + body_type=0x02) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray([power, 0xFF]) + return bytearray([ + power, 0xFF + ]) class MessageStart(MessageDABase): @@ -48,18 +53,21 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02, - ) + body_type=0x02) self.start = False self.washing_data = bytearray([]) @property def _body(self): if self.start: - return bytearray([0xFF, 0x01]) + self.washing_data + return bytearray([ + 0xFF, 0x01 + ]) + self.washing_data else: # Stop - return bytearray([0xFF, 0x00]) + return bytearray([ + 0xFF, 0x00 + ]) class DAGeneralMessageBody(MessageBody): @@ -71,14 +79,14 @@ def __init__(self, body): self.program = body[4] self.wash_time = body[9] self.soak_time = body[12] - self.dehydration_time = (body[10] & 0xF0) >> 4 - self.dehydration_speed = (body[6] & 0xF0) >> 4 - self.rinse_count = body[10] & 0xF - self.rinse_level = (body[5] & 0xF0) >> 4 - self.wash_level = body[5] & 0xF - self.wash_strength = body[6] & 0xF - self.softener = (body[8] & 0xF0) >> 4 - self.detergent = body[8] & 0x0F + self.dehydration_time = (body[10] & 0xf0) >> 4 + self.dehydration_speed = (body[6] & 0xf0) >> 4 + self.rinse_count = body[10] & 0xf + self.rinse_level = (body[5] & 0xf0) >> 4 + self.wash_level = body[5] & 0xf + self.wash_strength = body[6] & 0xf + self.softener = (body[8] & 0xf0) >> 4 + self.detergent = body[8] & 0x0f self.washing_data = body[3:15] self.progress = 0 for i in range(1, 7): @@ -94,8 +102,7 @@ def __init__(self, body): class MessageDAResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or ( - self.message_type == MessageType.notify1 and self.body_type == 0x04 - ): + if self.message_type in [MessageType.query, MessageType.set] or \ + (self.message_type == MessageType.notify1 and self.body_type == 0x04): self.set_body(DAGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/db/device.py b/custom_components/midea_ac_lan/midea/devices/db/device.py index fe1c02e4..0ef737c0 100644 --- a/custom_components/midea_ac_lan/midea/devices/db/device.py +++ b/custom_components/midea_ac_lan/midea/devices/db/device.py @@ -1,12 +1,14 @@ import logging - -from .message import MessageDBResponse, MessagePower, MessageQuery, MessageStart - +from .message import ( + MessageQuery, + MessagePower, + MessageStart, + MessageDBResponse +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -22,17 +24,17 @@ class DeviceAttributes(StrEnum): class MideaDBDevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -50,9 +52,8 @@ def __init__( DeviceAttributes.start: False, DeviceAttributes.washing_data: bytearray([]), DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None, - }, - ) + DeviceAttributes.time_remaining: None + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -61,17 +62,8 @@ def process_message(self, msg): message = MessageDBResponse(msg) _LOGGER.debug(f"[{self.device_id}] Received: {message}") new_status = {} - progress = [ - "Idle", - "Spin", - "Rinse", - "Wash", - "Pre-wash", - "Dry", - "Weight", - "Hi-speed Spin", - "Unknown", - ] + progress = ["Idle", "Spin", "Rinse", "Wash", "Pre-wash", + "Dry", "Weight", "Hi-speed Spin", "Unknown"] for status in self._attributes.keys(): if hasattr(message, str(status)): if status == DeviceAttributes.progress: diff --git a/custom_components/midea_ac_lan/midea/devices/db/message.py b/custom_components/midea_ac_lan/midea/devices/db/message.py index 7a70ca55..603c60d7 100644 --- a/custom_components/midea_ac_lan/midea/devices/db/message.py +++ b/custom_components/midea_ac_lan/midea/devices/db/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageDBBase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xDB, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x03, - ) + body_type=0x03) @property def _body(self): @@ -33,38 +37,20 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02, - ) + body_type=0x02) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray( - [ - power, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - ] - ) + return bytearray([ + power, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF + ]) class MessageStart(MessageDBBase): @@ -72,18 +58,21 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02, - ) + body_type=0x02) self.start = False self.washing_data = bytearray([]) @property def _body(self): - if self.start: # Pause - return bytearray([0xFF, 0x01]) + self.washing_data + if self.start: # Pause + return bytearray([ + 0xFF, 0x01 + ]) + self.washing_data else: # Pause - return bytearray([0xFF, 0x00]) + return bytearray([ + 0xFF, 0x00 + ]) class DBGeneralMessageBody(MessageBody): @@ -106,8 +95,7 @@ def __init__(self, body): class MessageDBResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or ( - self.message_type == MessageType.notify1 and self.body_type == 0x04 - ): + if self.message_type in [MessageType.query, MessageType.set] or \ + (self.message_type == MessageType.notify1 and self.body_type == 0x04): self.set_body(DBGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/dc/device.py b/custom_components/midea_ac_lan/midea/devices/dc/device.py index 7e6d659f..b78c121e 100644 --- a/custom_components/midea_ac_lan/midea/devices/dc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/dc/device.py @@ -1,12 +1,14 @@ import logging - -from .message import MessageDCResponse, MessagePower, MessageQuery, MessageStart - +from .message import ( + MessageQuery, + MessagePower, + MessageStart, + MessageDCResponse +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -22,17 +24,17 @@ class DeviceAttributes(StrEnum): class MideaDADevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -50,9 +52,8 @@ def __init__( DeviceAttributes.start: False, DeviceAttributes.washing_data: bytearray([]), DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None, - }, - ) + DeviceAttributes.time_remaining: None + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -61,16 +62,8 @@ def process_message(self, msg): message = MessageDCResponse(msg) _LOGGER.debug(f"[{self.device_id}] Received: {message}") new_status = {} - progress = [ - "Prog0", - "Prog1", - "Prog2", - "Prog3", - "Prog4", - "Prog5", - "Prog6", - "Prog7", - ] + progress = ["Prog0", "Prog1", "Prog2", "Prog3", + "Prog4", "Prog5", "Prog6", "Prog7"] for status in self._attributes.keys(): if hasattr(message, str(status)): if status == DeviceAttributes.progress: diff --git a/custom_components/midea_ac_lan/midea/devices/dc/message.py b/custom_components/midea_ac_lan/midea/devices/dc/message.py index 3be5d3dd..b9e66a7a 100644 --- a/custom_components/midea_ac_lan/midea/devices/dc/message.py +++ b/custom_components/midea_ac_lan/midea/devices/dc/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageDCBase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xDC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x03, - ) + body_type=0x03) @property def _body(self): @@ -33,14 +37,15 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02, - ) + body_type=0x02) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray([power, 0xFF]) + return bytearray([ + power, 0xFF + ]) class MessageStart(MessageDCBase): @@ -48,18 +53,21 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02, - ) + body_type=0x02) self.start = False self.washing_data = bytearray([]) @property def _body(self): if self.start: - return bytearray([0xFF, 0x01]) + self.washing_data + return bytearray([ + 0xFF, 0x01 + ]) + self.washing_data else: # Stop - return bytearray([0xFF, 0x00]) + return bytearray([ + 0xFF, 0x00 + ]) class DCGeneralMessageBody(MessageBody): @@ -82,8 +90,7 @@ def __init__(self, body): class MessageDCResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or ( - self.message_type == MessageType.notify1 and self.body_type == 0x04 - ): + if self.message_type in [MessageType.query, MessageType.set] or \ + (self.message_type == MessageType.notify1 and self.body_type == 0x04): self.set_body(DCGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e1/device.py b/custom_components/midea_ac_lan/midea/devices/e1/device.py index 43c72ffa..afc3d4c4 100644 --- a/custom_components/midea_ac_lan/midea/devices/e1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e1/device.py @@ -1,18 +1,15 @@ import logging - from .message import ( - MessageE1Response, - MessageLock, - MessagePower, MessageQuery, + MessagePower, MessageStorage, + MessageLock, + MessageE1Response ) - try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -47,17 +44,17 @@ class DeviceAttributes(StrEnum): class MideaE1Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -94,32 +91,31 @@ def __init__( DeviceAttributes.error_code: None, DeviceAttributes.softwater: 0, DeviceAttributes.wrong_operation: None, - DeviceAttributes.bright: 0, - }, - ) + DeviceAttributes.bright: 0 + }) self._modes = { - 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR - 0x1: "Auto", # BYTE_MODE_AUTO_WASH - 0x2: "Heavy", # BYTE_MODE_STRONG_WASH - 0x3: "Normal", # BYTE_MODE_STANDARD_WASH - 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH - 0x5: "Delicate", # BYTE_MODE_GLASS_WASH - 0x6: "Hour", # BYTE_MODE_HOUR_WASH - 0x7: "Quick", # BYTE_MODE_FAST_WASH - 0x8: "Rinse", # BYTE_MODE_SOAK_WASH - 0x9: "90min", # BYTE_MODE_90MIN_WASH - 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN - 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH - 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE - 0xD: "Germ", # BYTE_MODE_GERM ??? - 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH - 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM - 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH - 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH - 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH - 0x14: "Less Wash", # BYTE_MODE_LESS_WASH - 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH - 0x19: "Cloud Wash", # BYTE_MODE_CLOUD_WASH + 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR + 0x1: "Auto", # BYTE_MODE_AUTO_WASH + 0x2: "Heavy", # BYTE_MODE_STRONG_WASH + 0x3: "Normal", # BYTE_MODE_STANDARD_WASH + 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH + 0x5: "Delicate", # BYTE_MODE_GLASS_WASH + 0x6: "Hour", # BYTE_MODE_HOUR_WASH + 0x7: "Quick", # BYTE_MODE_FAST_WASH + 0x8: "Rinse", # BYTE_MODE_SOAK_WASH + 0x9: "90min", # BYTE_MODE_90MIN_WASH + 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN + 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH + 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE + 0xD: "Germ", # BYTE_MODE_GERM ??? + 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH + 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM + 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH + 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH + 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH + 0x14: "Less Wash", # BYTE_MODE_LESS_WASH + 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH + 0x19: "Cloud Wash" # BYTE_MODE_CLOUD_WASH } self._status = ["Off", "Idle", "Delay", "Running", "Error"] self._progress = ["Idle", "Pre-wash", "Wash", "Rinse", "Dry", "Complete"] diff --git a/custom_components/midea_ac_lan/midea/devices/e1/message.py b/custom_components/midea_ac_lan/midea/devices/e1/message.py index 530572e8..1147e648 100644 --- a/custom_components/midea_ac_lan/midea/devices/e1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e1/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageE1Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xE1, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,14 +25,16 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x08, - ) + body_type=0x08) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray([power, 0x00, 0x00, 0x00]) + return bytearray([ + power, + 0x00, 0x00, 0x00 + ]) class MessageLock(MessageE1Base): @@ -35,8 +42,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x83, - ) + body_type=0x83) self.lock = False @property @@ -50,18 +56,14 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x81, - ) + body_type=0x81) self.storage = False @property def _body(self): storage = 0x01 if self.storage else 0x00 - return ( - bytearray([0x00, 0x00, 0x00, storage]) - + bytearray([0xFF] * 6) - + bytearray([0x00] * 27) - ) + return bytearray([0x00, 0x00, 0x00, storage]) + \ + bytearray([0xff] * 6) + bytearray([0x00] * 27) class MessageQuery(MessageE1Base): @@ -69,8 +71,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00, - ) + body_type=0x00) @property def _body(self): @@ -84,9 +85,9 @@ def __init__(self, body): self.status = body[1] self.mode = body[2] self.additional = body[3] - self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close - self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage - self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage + self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close + self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage + self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage start_pause = (body[5] & 0x08) > 0 if start_pause: self.start = True @@ -114,9 +115,7 @@ def __init__(self, body): class MessageE1Response(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0 - ): + if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or \ + (self.message_type in [MessageType.query, MessageType.notify1] and self.body_type == 0): self.set_body(E1GeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e2/device.py b/custom_components/midea_ac_lan/midea/devices/e2/device.py index b261a44f..fc8ca315 100644 --- a/custom_components/midea_ac_lan/midea/devices/e2/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e2/device.py @@ -1,19 +1,16 @@ -import json import logging - +import json from .message import ( - MessageE2Response, - MessageNewProtocolSet, - MessagePower, MessageQuery, MessageSet, + MessageE2Response, + MessagePower, + MessageNewProtocolSet ) - try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -35,17 +32,17 @@ class DeviceAttributes(StrEnum): class MideaE2Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -69,9 +66,8 @@ def __init__( DeviceAttributes.variable_heating: False, DeviceAttributes.heating_time_remaining: 0, DeviceAttributes.water_consumption: None, - DeviceAttributes.heating_power: None, - }, - ) + DeviceAttributes.heating_power: None + }) self._default_old_protocol = "auto" self._old_protocol = self._default_old_protocol self.set_customize(customize) @@ -95,21 +91,15 @@ def process_message(self, msg): def make_message_set(self): message = MessageSet(self._protocol_version) message.protection = self._attributes[DeviceAttributes.protection] - message.whole_tank_heating = self._attributes[ - DeviceAttributes.whole_tank_heating - ] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] + message.whole_tank_heating = self._attributes[DeviceAttributes.whole_tank_heating] + message.target_temperature = self._attributes[DeviceAttributes.target_temperature] message.variable_heating = self._attributes[DeviceAttributes.variable_heating] return message def set_attribute(self, attr, value): - if attr not in [ - DeviceAttributes.heating, - DeviceAttributes.keep_warm, - DeviceAttributes.current_temperature, - ]: + if attr not in [DeviceAttributes.heating, + DeviceAttributes.keep_warm, + DeviceAttributes.current_temperature]: if self._old_protocol is not None and self._old_protocol != "auto": old_protocol = self._old_protocol else: diff --git a/custom_components/midea_ac_lan/midea/devices/e2/message.py b/custom_components/midea_ac_lan/midea/devices/e2/message.py index a70c6cc9..18d80ecc 100644 --- a/custom_components/midea_ac_lan/midea/devices/e2/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e2/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageE2Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xE2, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -33,8 +37,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02, - ) + body_type=0x02) self.power = False @property @@ -51,8 +54,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x14, - ) + body_type=0x14) self.target_temperature = None self.variable_heating = None self.whole_tank_heating = None @@ -78,8 +80,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x04, - ) + body_type=0x04) self.target_temperature = 0 self.variable_heating = False self.whole_tank_heating = False @@ -94,28 +95,18 @@ def _body(self): target_temperature = self.target_temperature & 0xFF # Byte 9 variable_heating variable_heating = 0x10 if self.variable_heating else 0x00 - return bytearray( - [ - 0x01, - 0x00, - 0x80, - whole_tank_heating | protection, - target_temperature, - 0x00, - 0x00, - 0x00, - variable_heating, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + 0x01, + 0x00, + 0x80, + whole_tank_heating | protection, + target_temperature, + 0x00, 0x00, 0x00, + variable_heating, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00 + ]) class E2GeneralMessageBody(MessageBody): @@ -139,12 +130,7 @@ def __init__(self, body): class MessageE2Response(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0x01 - ) or ( - self.message_type == MessageType.set - and self.body_type in [0x01, 0x02, 0x04, 0x14] - ): + if (self.message_type in [MessageType.query, MessageType.notify1] and self.body_type == 0x01) or \ + (self.message_type == MessageType.set and self.body_type in [0x01, 0x02, 0x04, 0x14]): self.set_body(E2GeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e3/device.py b/custom_components/midea_ac_lan/midea/devices/e3/device.py index 250076d1..0b708f08 100644 --- a/custom_components/midea_ac_lan/midea/devices/e3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e3/device.py @@ -1,19 +1,16 @@ -import json import logging - +import json from .message import ( - MessageE3Response, - MessageNewProtocolSet, - MessagePower, MessageQuery, MessageSet, + MessageNewProtocolSet, + MessagePower, + MessageE3Response ) - try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -32,17 +29,17 @@ class DeviceAttributes(StrEnum): class MideaE3Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -64,9 +61,10 @@ def __init__( DeviceAttributes.smart_volume: False, DeviceAttributes.current_temperature: None, DeviceAttributes.target_temperature: 40, - }, - ) - self._old_subtypes = [32, 33, 34, 35, 36, 37, 40, 43, 48, 49, 80] + }) + self._old_subtypes = [ + 32, 33, 34, 35, 36, 37, 40, 43, 48, 49, 80 + ] self._precision_halves = None self._default_precision_halves = False self.set_customize(customize) @@ -84,10 +82,8 @@ def process_message(self, msg): new_status = {} for status in self._attributes.keys(): if hasattr(message, str(status)): - if self._precision_halves and status in [ - DeviceAttributes.current_temperature, - DeviceAttributes.target_temperature, - ]: + if self._precision_halves and status in [DeviceAttributes.current_temperature, + DeviceAttributes.target_temperature]: self._attributes[status] = getattr(message, str(status)) / 2 else: self._attributes[status] = getattr(message, str(status)) @@ -101,17 +97,13 @@ def make_message_set(self): message.protection = self._attributes[DeviceAttributes.protection] message.zero_clod_pulse = self._attributes[DeviceAttributes.zero_cold_pulse] message.smart_volume = self._attributes[DeviceAttributes.smart_volume] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] + message.target_temperature = self._attributes[DeviceAttributes.target_temperature] return message def set_attribute(self, attr, value): - if attr not in [ - DeviceAttributes.burning_state, - DeviceAttributes.current_temperature, - DeviceAttributes.protection, - ]: + if attr not in [DeviceAttributes.burning_state, + DeviceAttributes.current_temperature, + DeviceAttributes.protection]: if self._precision_halves and attr == DeviceAttributes.target_temperature: value = int(value * 2) if attr == DeviceAttributes.power: diff --git a/custom_components/midea_ac_lan/midea/devices/e3/message.py b/custom_components/midea_ac_lan/midea/devices/e3/message.py index 4b6952a2..2b86ff07 100644 --- a/custom_components/midea_ac_lan/midea/devices/e3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e3/message.py @@ -1,11 +1,17 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + NEW_PROTOCOL_PARAMS = { "zero_cold_water": 0x03, # "zero_cold_master": 0x12, "zero_cold_pulse": 0x04, "smart_volume": 0x07, - "target_temperature": 0x08, + "target_temperature": 0x08 } @@ -15,7 +21,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xE3, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -28,8 +34,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): @@ -41,8 +46,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x02, - ) + body_type=0x02) self.power = False @property @@ -59,8 +63,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x04, - ) + body_type=0x04) self.target_temperature = 0 self.zero_cold_water = False @@ -80,24 +83,17 @@ def _body(self): # Byte 5 target_temperature target_temperature = self.target_temperature & 0xFF - return bytearray( - [ - 0x01, - zero_cold_water | 0x02, - protection | zero_cold_pulse | smart_volume, - 0x00, - target_temperature, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + 0x01, + zero_cold_water | 0x02, + protection | zero_cold_pulse | smart_volume, + 0x00, + target_temperature, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, + + ]) class MessageNewProtocolSet(MessageE3Base): @@ -105,8 +101,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x14, - ) + body_type=0x14) self.key = None self.value = None @@ -117,29 +112,14 @@ def _body(self): value = self.value else: value = 0x01 if self.value else 0x00 - return bytearray( - [ - key, - value, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + key, value, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00 + ]) class E3GeneralMessageBody(MessageBody): @@ -158,16 +138,8 @@ def __init__(self, body): class MessageE3Response(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - (self.message_type == MessageType.query and self.body_type == 0x01) - or ( - self.message_type == MessageType.set - and self.body_type in [0x01, 0x02, 0x04, 0x14] - ) - or ( - self.message_type == MessageType.notify1 - and self.body_type in [0x00, 0x01] - ) - ): + if (self.message_type == MessageType.query and self.body_type == 0x01) or \ + (self.message_type == MessageType.set and self.body_type in [0x01, 0x02, 0x04, 0x14]) or \ + (self.message_type == MessageType.notify1 and self.body_type in [0x00, 0x01]): self.set_body(E3GeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e6/device.py b/custom_components/midea_ac_lan/midea/devices/e6/device.py index 82368b40..951125df 100644 --- a/custom_components/midea_ac_lan/midea/devices/e6/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e6/device.py @@ -1,12 +1,13 @@ import logging - -from .message import MessageE6Response, MessageQuery, MessageSet - +from .message import ( + MessageQuery, + MessageSet, + MessageE6Response +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -27,17 +28,17 @@ class DeviceAttributes(StrEnum): class MideaE6Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -60,9 +61,8 @@ def __init__( DeviceAttributes.heating_temperature: 50, DeviceAttributes.bathing_temperature: 40, DeviceAttributes.heating_leaving_temperature: None, - DeviceAttributes.bathing_leaving_temperature: None, - }, - ) + DeviceAttributes.bathing_leaving_temperature: None + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -78,12 +78,10 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.main_power, - DeviceAttributes.heating_power, - DeviceAttributes.heating_temperature, - DeviceAttributes.bathing_temperature, - ]: + if attr in [DeviceAttributes.main_power, + DeviceAttributes.heating_power, + DeviceAttributes.heating_temperature, + DeviceAttributes.bathing_temperature]: message = MessageSet(self._protocol_version) setattr(message, str(attr), value) self.build_send(message) diff --git a/custom_components/midea_ac_lan/midea/devices/e6/message.py b/custom_components/midea_ac_lan/midea/devices/e6/message.py index 230e2d5d..79738af4 100644 --- a/custom_components/midea_ac_lan/midea/devices/e6/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e6/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageE6Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type): device_type=0xE6, protocol_version=protocol_version, message_type=message_type, - body_type=None, + body_type=None ) @property @@ -22,7 +27,8 @@ def _body(self): class MessageQuery(MessageE6Base): def __init__(self, protocol_version): super().__init__( - protocol_version=protocol_version, message_type=MessageType.query + protocol_version=protocol_version, + message_type=MessageType.query ) @property @@ -33,7 +39,8 @@ def _body(self): class MessageSet(MessageE6Base): def __init__(self, protocol_version): super().__init__( - protocol_version=protocol_version, message_type=MessageType.set + protocol_version=protocol_version, + message_type=MessageType.set ) self.main_power = None self.heating_temperature = None @@ -64,8 +71,14 @@ def __init__(self, body): self.heating_working = (body[2] & 0x10) > 0 self.bathing_working = (body[2] & 0x20) > 0 self.heating_power = (body[4] & 0x01) > 0 - self.min_temperature = [body[16], body[11]] - self.max_temperature = [body[15], body[10]] + self.min_temperature = [ + body[16], + body[11] + ] + self.max_temperature = [ + body[15], + body[10] + ] self.heating_temperature = body[17] self.bathing_temperature = body[12] self.heating_leaving_temperature = body[14] diff --git a/custom_components/midea_ac_lan/midea/devices/e8/device.py b/custom_components/midea_ac_lan/midea/devices/e8/device.py index 813941a2..abaaada3 100644 --- a/custom_components/midea_ac_lan/midea/devices/e8/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e8/device.py @@ -1,12 +1,12 @@ import logging - -from .message import MessageE8Response, MessageQuery - +from .message import ( + MessageQuery, + MessageE8Response +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -25,26 +25,22 @@ class DeviceAttributes(StrEnum): class MideaE8Device(MiedaDevice): _status = { - 0x00: "Standby", - 0x01: "Delay", - 0x02: "Working", - 0x03: "Paused", - 0x04: "Keep-Warming", - 0xFF: "Error", + 0x00: "Standby", 0x01: "Delay", 0x02: "Working", + 0x03: "Paused", 0x04: "Keep-Warming", 0xFF: "Error" } def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -66,8 +62,7 @@ def __init__( DeviceAttributes.current_temperature: None, DeviceAttributes.finished: None, DeviceAttributes.water_shortage: None, - }, - ) + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -81,9 +76,7 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.status: if value in MideaE8Device._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaE8Device._status.get(value) - ) + self._attributes[DeviceAttributes.status] = MideaE8Device._status.get(value) else: self._attributes[DeviceAttributes.status] = None else: diff --git a/custom_components/midea_ac_lan/midea/devices/e8/message.py b/custom_components/midea_ac_lan/midea/devices/e8/message.py index 6fe81409..7ee345d5 100644 --- a/custom_components/midea_ac_lan/midea/devices/e8/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e8/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody +) class MessageE8Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xE8, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0xAA, - ) + body_type=0xAA) @property def _body(self): @@ -46,10 +50,8 @@ def __init__(self, message): super().__init__(message) if len(super().body) > 6: sub_cmd = super().body[6] - if ( - (self.message_type == MessageType.set and sub_cmd in [0x02, 0x04, 0x06]) - or self.message_type in [MessageType.query, MessageType.notify1] - and sub_cmd == 2 - ): + if ((self.message_type == MessageType.set and sub_cmd in [0x02, 0x04, 0x06]) or + self.message_type in [MessageType.query, MessageType.notify1] and sub_cmd ==2): self.set_body(E8MessageBody(super().body)) self.set_attr() + diff --git a/custom_components/midea_ac_lan/midea/devices/ea/device.py b/custom_components/midea_ac_lan/midea/devices/ea/device.py index 722d12d8..aa4fbc45 100644 --- a/custom_components/midea_ac_lan/midea/devices/ea/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ea/device.py @@ -1,12 +1,12 @@ import logging - -from .message import MessageEAResponse, MessageQuery - +from .message import ( + MessageQuery, + MessageEAResponse +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,122 +24,43 @@ class DeviceAttributes(StrEnum): class MideaEADevice(MiedaDevice): - _mode_list = ( - [ - "smart", - "reserve", - "cook_rice", - "fast_cook_rice", - "standard_cook_rice", - "gruel", - "cook_congee", - "stew_soup", - "stewing", - "heat_rice", - "make_cake", - "yoghourt", - "soup_rice", - "coarse_rice", - "five_ceeals_rice", - "eight_treasures_rice", - "crispy_rice", - "shelled_rice", - "eight_treasures_congee", - "infant_congee", - "older_rice", - "rice_soup", - "rice_paste", - "egg_custard", - "warm_milk", - "hot_spring_egg", - "millet_congee", - "firewood_rice", - "few_rice", - "red_potato", - "corn", - "quick_freeze_bun", - "steam_ribs", - "steam_egg", - "coarse_congee", - "steep_rice", - "appetizing_congee", - "corn_congee", - "sprout_rice", - "luscious_rice", - "luscious_boiled", - "fast_rice", - "fast_boil", - "bean_rice_congee", - "fast_congee", - "baby_congee", - "cook_soup", - "congee_coup", - "steam_corn", - "steam_red_potato", - "boil_congee", - "delicious_steam", - "boil_egg", - "rice_wine", - "fruit_vegetable_paste", - "vegetable_porridge", - "pork_porridge", - "fragrant_rice", - "assorte_rice", - "steame_fish", - "baby_rice", - "essence_rice", - "fragrant_dense_congee", - "one_two_cook", - "original_steame", - "hot_fast_rice", - "online_celebrity_rice", - "sushi_rice", - "stone_bowl_rice", - "no_water_treat", - "keep_fresh", - "low_sugar_rice", - "black_buckwheat_rice", - "resveratrol_rice", - "yellow_wheat_rice", - "green_buckwheat_rice", - "roughage_rice", - "millet_mixed_rice", - "iron_pan_rice", - "olla_pan_rice", - "vegetable_rice", - "baby_side", - "regimen_congee", - "earthen_pot_congee", - "regimen_soup", - "pottery_jar_soup", - "canton_soup", - "nutrition_stew", - "northeast_stew", - "uncap_boil", - "trichromatic_coarse_grain", - "four_color_vegetables", - "egg", - "chop", - ] - + ["unknown"] * 98 - + ["clean"] - + ["unknown"] * 5 - + ["keep_warm"] - ) + _mode_list = [ + "smart", "reserve", "cook_rice", "fast_cook_rice", "standard_cook_rice", + "gruel", "cook_congee", "stew_soup", "stewing", "heat_rice", "make_cake", + "yoghourt", "soup_rice", "coarse_rice", "five_ceeals_rice", "eight_treasures_rice", + "crispy_rice", "shelled_rice", "eight_treasures_congee", "infant_congee", + "older_rice", "rice_soup", "rice_paste", "egg_custard", "warm_milk", + "hot_spring_egg", "millet_congee", "firewood_rice", "few_rice", + "red_potato", "corn", "quick_freeze_bun", "steam_ribs", "steam_egg", + "coarse_congee", "steep_rice", "appetizing_congee", "corn_congee", + "sprout_rice", "luscious_rice", "luscious_boiled", "fast_rice", "fast_boil", + "bean_rice_congee", "fast_congee", "baby_congee", "cook_soup", "congee_coup", + "steam_corn", "steam_red_potato", "boil_congee", "delicious_steam", "boil_egg", + "rice_wine", "fruit_vegetable_paste", "vegetable_porridge", "pork_porridge", + "fragrant_rice", "assorte_rice", "steame_fish", "baby_rice", "essence_rice", + "fragrant_dense_congee", "one_two_cook", "original_steame", "hot_fast_rice", + "online_celebrity_rice", "sushi_rice", "stone_bowl_rice", "no_water_treat", + "keep_fresh", "low_sugar_rice", "black_buckwheat_rice", "resveratrol_rice", + "yellow_wheat_rice", "green_buckwheat_rice", "roughage_rice", "millet_mixed_rice", + "iron_pan_rice", "olla_pan_rice", "vegetable_rice", "baby_side", "regimen_congee", + "earthen_pot_congee", "regimen_soup", "pottery_jar_soup", "canton_soup", + "nutrition_stew", "northeast_stew", "uncap_boil", "trichromatic_coarse_grain", + "four_color_vegetables", "egg", "chop", + ] + ["unknown"] * 98 + ["clean"] + ["unknown"] * 5 + ["keep_warm"] _progress = ["Idle", "Delay", "Cooking", "Keep-warm"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -160,9 +81,8 @@ def __init__( DeviceAttributes.top_temperature: None, DeviceAttributes.bottom_temperature: None, DeviceAttributes.keep_warm_time: None, - DeviceAttributes.progress: "Unknown", - }, - ) + DeviceAttributes.progress: "Unknown" + }) def build_query(self): return [MessageQuery(self._protocol_version)] diff --git a/custom_components/midea_ac_lan/midea/devices/ea/message.py b/custom_components/midea_ac_lan/midea/devices/ea/message.py index 49347767..3fea521b 100644 --- a/custom_components/midea_ac_lan/midea/devices/ea/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ea/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageEABase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xEA, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,12 +25,13 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=None, - ) + body_type=None) @property def body(self): - return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00]) + return bytearray([ + 0xAA, 0x55, 0x01, 0x03, 0x00 + ]) @property def _body(self): @@ -94,22 +100,16 @@ def __init__(self, message): if self.message_type == MessageType.set and super().body[5] == 0x16: # 381 self.set_body(EABody1(super().body)) elif self.message_type == MessageType.query: - if super().body[6] == 0x52 and super().body[7] == 0xC3: # 404 + if super().body[6] == 0x52 and super().body[7] == 0xc3: # 404 self.set_body(EABody2(super().body)) - elif super().body[5] == 0x3D: # 420 + elif super().body[5] == 0x3d: # 420 self.set_body(EABody1(super().body)) - elif ( - self.message_type == MessageType.notify1 and super().body[5] == 0x3D - ): # 463 + elif self.message_type == MessageType.notify1 and super().body[5] == 0x3d: # 463 self.set_body(EABody1(super().body)) else: - if ( - (self.message_type == MessageType.set and super().body[3] == 0x02) - or (self.message_type == MessageType.query and super().body[3] == 0x03) - or ( - self.message_type == MessageType.notify1 and super().body[3] == 0x04 - ) - ): # 351 + if(self.message_type == MessageType.set and super().body[3] == 0x02) or \ + (self.message_type == MessageType.query and super().body[3] == 0x03) or \ + (self.message_type == MessageType.notify1 and super().body[3] == 0x04): # 351 self.set_body(EABody3(super().body)) elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: self.mode = super().body[4] + (super().body[5] << 8) diff --git a/custom_components/midea_ac_lan/midea/devices/ec/device.py b/custom_components/midea_ac_lan/midea/devices/ec/device.py index a87e9955..c6eed5b1 100644 --- a/custom_components/midea_ac_lan/midea/devices/ec/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ec/device.py @@ -1,12 +1,12 @@ import logging - -from .message import MessageECResponse, MessageQuery - +from .message import ( + MessageQuery, + MessageECResponse +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -24,134 +24,45 @@ class DeviceAttributes(StrEnum): class MideaECDevice(MiedaDevice): - _mode_list = ( - [ - "smart", - "reserve", - "cook_rice", - "fast_cook_rice", - "standard_cook_rice", - "gruel", - "cook_congee", - "stew_soup", - "stewing", - "heat_rice", - "make_cake", - "yoghourt", - "soup_rice", - "coarse_rice", - "five_ceeals_rice", - "eight_treasures_rice", - "crispy_rice", - "shelled_rice", - "eight_treasures_congee", - "infant_congee", - "older_rice", - "rice_soup", - "rice_paste", - "egg_custard", - "warm_milk", - "hot_spring_egg", - "millet_congee", - "firewood_rice", - "few_rice", - "red_potato", - "corn", - "quick_freeze_bun", - "steam_ribs", - "steam_egg", - "coarse_congee", - "steep_rice", - "appetizing_congee", - "corn_congee", - "sprout_rice", - "luscious_rice", - "luscious_boiled", - "fast_rice", - "fast_boil", - "bean_rice_congee", - "fast_congee", - "baby_congee", - "cook_soup", - "congee_coup", - "steam_corn", - "steam_red_potato", - "boil_congee", - "delicious_steam", - "boil_egg", - "rice_wine", - "fruit_vegetable_paste", - "vegetable_porridge", - "pork_porridge", - "fragrant_rice", - "assorte_rice", - "steame_fish", - "baby_rice", - "essence_rice", - "fragrant_dense_congee", - "one_two_cook", - "original_steame", - "hot_fast_rice", - "online_celebrity_rice", - "sushi_rice", - "stone_bowl_rice", - "no_water_treat", - "keep_fresh", - "low_sugar_rice", - "black_buckwheat_rice", - "resveratrol_rice", - "yellow_wheat_rice", - "green_buckwheat_rice", - "roughage_rice", - "millet_mixed_rice", - "iron_pan_rice", - "olla_pan_rice", - "vegetable_rice", - "baby_side", - "regimen_congee", - "earthen_pot_congee", - "regimen_soup", - "pottery_jar_soup", - "canton_soup", - "nutrition_stew", - "northeast_stew", - "uncap_boil", - "trichromatic_coarse_grain", - "four_color_vegetables", - "egg", - "chop", - ] - + ["unknown"] * 98 - + ["clean"] - + ["unknown"] * 5 - + ["keep_warm", "diy"] - ) - _progress = [ - "Idle", - "Cooking", - "Delay", - "Keep-warm", - "Lid-open", - "Relieving", - "Keep-pressure", - "Relieving", - "Cooking", - "Relieving", - "Lid-open", - ] + _mode_list = [ + "smart", "reserve", "cook_rice", "fast_cook_rice", "standard_cook_rice", + "gruel", "cook_congee", "stew_soup", "stewing", "heat_rice", "make_cake", + "yoghourt", "soup_rice", "coarse_rice", "five_ceeals_rice", "eight_treasures_rice", + "crispy_rice", "shelled_rice", "eight_treasures_congee", "infant_congee", + "older_rice", "rice_soup", "rice_paste", "egg_custard", "warm_milk", + "hot_spring_egg", "millet_congee", "firewood_rice", "few_rice", + "red_potato", "corn", "quick_freeze_bun", "steam_ribs", "steam_egg", + "coarse_congee", "steep_rice", "appetizing_congee", "corn_congee", + "sprout_rice", "luscious_rice", "luscious_boiled", "fast_rice", "fast_boil", + "bean_rice_congee", "fast_congee", "baby_congee", "cook_soup", "congee_coup", + "steam_corn", "steam_red_potato", "boil_congee", "delicious_steam", "boil_egg", + "rice_wine", "fruit_vegetable_paste", "vegetable_porridge", "pork_porridge", + "fragrant_rice", "assorte_rice", "steame_fish", "baby_rice", "essence_rice", + "fragrant_dense_congee", "one_two_cook", "original_steame", "hot_fast_rice", + "online_celebrity_rice", "sushi_rice", "stone_bowl_rice", "no_water_treat", + "keep_fresh", "low_sugar_rice", "black_buckwheat_rice", "resveratrol_rice", + "yellow_wheat_rice", "green_buckwheat_rice", "roughage_rice", "millet_mixed_rice", + "iron_pan_rice", "olla_pan_rice", "vegetable_rice", "baby_side", "regimen_congee", + "earthen_pot_congee", "regimen_soup", "pottery_jar_soup", "canton_soup", + "nutrition_stew", "northeast_stew", "uncap_boil", "trichromatic_coarse_grain", + "four_color_vegetables", "egg", "chop", + ] + ["unknown"] * 98 + ["clean"] + ["unknown"] * 5 + ["keep_warm", "diy"] + _progress = ["Idle", "Cooking", "Delay", "Keep-warm", + "Lid-open", "Relieving", "Keep-pressure", + "Relieving", "Cooking", "Relieving", "Lid-open"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -172,9 +83,8 @@ def __init__( DeviceAttributes.bottom_temperature: None, DeviceAttributes.keep_warm_time: None, DeviceAttributes.progress: "Unknown", - DeviceAttributes.with_pressure: None, - }, - ) + DeviceAttributes.with_pressure: None + }) def build_query(self): return [MessageQuery(self._protocol_version)] @@ -188,9 +98,7 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.progress: if value < len(MideaECDevice._progress): - self._attributes[status] = MideaECDevice._progress[ - getattr(message, str(status)) - ] + self._attributes[status] = MideaECDevice._progress[getattr(message, str(status))] else: self._attributes[status] = "Unknown" elif status == DeviceAttributes.mode: diff --git a/custom_components/midea_ac_lan/midea/devices/ec/message.py b/custom_components/midea_ac_lan/midea/devices/ec/message.py index 00c4aefe..a3387768 100644 --- a/custom_components/midea_ac_lan/midea/devices/ec/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ec/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageECBase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xEC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,12 +25,16 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=None, - ) + body_type=None) @property def body(self): - return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + return bytearray([ + 0xAA, 0x55, + 0x01, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + ]) @property def _body(self): @@ -54,7 +63,7 @@ def __init__(self, body): self.keep_warm_time = body[19] * 60 + body[20] self.top_temperature = body[48] self.bottom_temperature = body[49] - self.with_pressure = body[33] > 0 + self.with_pressure = (body[33] > 0) class MessageECResponse(MessageResponse): @@ -62,12 +71,10 @@ def __init__(self, message): super().__init__(message) if self.message_type == MessageType.notify1 and super().body[3] == 0x01: self.set_body(ECBodyNew(super().body)) - elif ( - (self.message_type == MessageType.set and super().body[3] == 0x02) - or (self.message_type == MessageType.query and super().body[3] == 0x03) - or (self.message_type == MessageType.notify1 and super().body[3] == 0x04) - or (self.message_type == MessageType.notify1 and super().body[3] == 0x3D) - ): + elif(self.message_type == MessageType.set and super().body[3] == 0x02) or \ + (self.message_type == MessageType.query and super().body[3] == 0x03) or \ + (self.message_type == MessageType.notify1 and super().body[3] == 0x04) or \ + (self.message_type == MessageType.notify1 and super().body[3] == 0x3d): self.set_body(ECGeneralMessageBody(super().body)) elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: self.mode = super().body[4] + (super().body[5] << 8) diff --git a/custom_components/midea_ac_lan/midea/devices/ed/device.py b/custom_components/midea_ac_lan/midea/devices/ed/device.py index a25872e5..4aa1e4f8 100644 --- a/custom_components/midea_ac_lan/midea/devices/ed/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ed/device.py @@ -1,12 +1,14 @@ import logging - -from .message import MessageEDResponse, MessageNewSet, MessageOldSet, MessageQuery - +from .message import ( + MessageQuery, + MessageEDResponse, + MessageNewSet, + MessageOldSet +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -29,17 +31,17 @@ class DeviceAttributes(StrEnum): class MideaEDDevice(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -63,16 +65,17 @@ def __init__( DeviceAttributes.life1: None, DeviceAttributes.life2: None, DeviceAttributes.life3: None, - DeviceAttributes.child_lock: False, - }, - ) + DeviceAttributes.child_lock: False + }) self._device_class = 0 def _use_new_set(self): - return True # if (self.sub_type > 342 or self.sub_type == 340) else False + return True # if (self.sub_type > 342 or self.sub_type == 340) else False def build_query(self): - return [MessageQuery(self._protocol_version, self._device_class)] + return [ + MessageQuery(self._protocol_version, self._device_class) + ] def process_message(self, msg): message = MessageEDResponse(msg) @@ -89,7 +92,10 @@ def process_message(self, msg): def set_attribute(self, attr, value): message = None if self._use_new_set(): - if attr in [DeviceAttributes.power, DeviceAttributes.child_lock]: + if attr in [ + DeviceAttributes.power, + DeviceAttributes.child_lock + ]: message = MessageNewSet(self._protocol_version) else: if attr in []: diff --git a/custom_components/midea_ac_lan/midea/devices/ed/message.py b/custom_components/midea_ac_lan/midea/devices/ed/message.py index e843d8af..30a5a53b 100644 --- a/custom_components/midea_ac_lan/midea/devices/ed/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ed/message.py @@ -1,6 +1,10 @@ from enum import IntEnum - -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class NewSetTags(IntEnum): @@ -11,9 +15,7 @@ class NewSetTags(IntEnum): class EDNewSetParamPack: @staticmethod def pack(param, value, addition=0): - return bytearray( - [param & 0xFF, param >> 8, value, addition & 0xFF, addition >> 8] - ) + return bytearray([param & 0xFF, param >> 8, value, addition & 0xFF, addition >> 8]) class MessageEDBase(MessageRequest): @@ -22,7 +24,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xED, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -35,8 +37,7 @@ def __init__(self, protocol_version, device_class): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=device_class, - ) + body_type=device_class) @property def _body(self): @@ -48,8 +49,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x15, - ) + body_type=0x15) self.power = None self.lock = None @@ -61,14 +61,16 @@ def _body(self): pack_count += 1 payload.extend( EDNewSetParamPack.pack( - param=NewSetTags.power, value=0x01 if self.power else 0x00 # power + param=NewSetTags.power, # power + value=0x01 if self.power else 0x00 ) ) if self.lock is not None: pack_count += 1 payload.extend( EDNewSetParamPack.pack( - param=NewSetTags.lock, value=0x01 if self.lock else 0x00 # lock + param=NewSetTags.lock, # lock + value=0x01 if self.lock else 0x00 ) ) payload[1] = pack_count @@ -80,8 +82,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=None, - ) + body_type=None) @property def body(self): @@ -156,17 +157,10 @@ def __init__(self, body): self.child_lock = (body[data_offset + 5] & 0x01) > 0 self.power = (body[data_offset + 6] & 0x01) > 0 elif attr == 0x011: - self.water_consumption = ( - float( - ( - body[data_offset + 3] - + (body[data_offset + 4] << 8) - + (body[data_offset + 5] << 16) - + (body[data_offset + 6] << 24) - ) - ) - / 1000 - ) + self.water_consumption = float((body[data_offset + 3] + + (body[data_offset + 4] << 8) + + (body[data_offset + 5] << 16) + + (body[data_offset + 6] << 24))) / 1000 elif attr == 0x013: self.in_tds = body[data_offset + 3] + (body[data_offset + 4] << 8) self.out_tds = body[data_offset + 5] + (body[data_offset + 6] << 8) diff --git a/custom_components/midea_ac_lan/midea/devices/fa/device.py b/custom_components/midea_ac_lan/midea/devices/fa/device.py index 38772c93..58dea3e9 100644 --- a/custom_components/midea_ac_lan/midea/devices/fa/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fa/device.py @@ -1,13 +1,14 @@ -import json import logging - -from .message import MessageFAResponse, MessageQuery, MessageSet - +import json +from .message import ( + MessageQuery, + MessageFAResponse, + MessageSet +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -25,45 +26,32 @@ class DeviceAttributes(StrEnum): class MideaFADevice(MiedaDevice): - _oscillation_angles = ["Off", "30", "60", "90", "120", "180", "360"] - _tilting_angles = ["Off", "30", "60", "90", "120", "180", "360", "+60", "-60", "40"] + _oscillation_angles = [ + "Off", "30", "60", "90", "120", "180", "360" + ] + _tilting_angles = [ + "Off", "30", "60", "90", "120", "180", "360", "+60", "-60", "40" + ] _oscillation_modes = [ - "Off", - "Oscillation", - "Tilting", - "Curve-W", - "Curve-8", - "Reserved", - "Both", + "Off", "Oscillation", "Tilting", "Curve-W", "Curve-8", "Reserved", "Both" ] _modes = [ - "Normal", - "Natural", - "Sleep", - "Comfort", - "Silent", - "Baby", - "Induction", - "Circulation", - "Strong", - "Soft", - "Customize", - "Warm", - "Smart", + "Normal", "Natural", "Sleep", "Comfort", "Silent", "Baby", + "Induction", "Circulation", "Strong", "Soft", "Customize", "Warm", "Smart" ] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -85,8 +73,7 @@ def __init__( DeviceAttributes.oscillation_angle: None, DeviceAttributes.tilting_angle: None, DeviceAttributes.oscillation_mode: None, - }, - ) + }) self._default_speed_count = 3 self._speed_count = self._default_speed_count self.set_customize(customize) @@ -123,9 +110,7 @@ def process_message(self, msg): value = getattr(message, str(status)) if status == DeviceAttributes.oscillation_angle: if value < len(MideaFADevice._oscillation_angles): - self._attributes[status] = MideaFADevice._oscillation_angles[ - value - ] + self._attributes[status] = MideaFADevice._oscillation_angles[value] else: self._attributes[status] = None elif status == DeviceAttributes.tilting_angle: @@ -135,9 +120,7 @@ def process_message(self, msg): self._attributes[status] = None elif status == DeviceAttributes.oscillation_mode: if value < len(MideaFADevice._oscillation_modes): - self._attributes[status] = MideaFADevice._oscillation_modes[ - value - ] + self._attributes[status] = MideaFADevice._oscillation_modes[value] else: self._attributes[status] = None elif status == DeviceAttributes.mode: @@ -149,10 +132,7 @@ def process_message(self, msg): self._attributes[status] = value if not value: self._attributes[DeviceAttributes.fan_speed] = 0 - elif ( - status == DeviceAttributes.fan_speed - and not self._attributes[DeviceAttributes.power] - ): + elif status == DeviceAttributes.fan_speed and not self._attributes[DeviceAttributes.power]: self._attributes[status] = 0 else: self._attributes[status] = value @@ -168,28 +148,20 @@ def set_oscillation(self, attr, value): if value: message.oscillation_angle = 3 # 90 message.oscillation_mode = 1 # Oscillation - elif attr == DeviceAttributes.oscillation_mode and ( - value in MideaFADevice._oscillation_modes or not value - ): + elif attr == DeviceAttributes.oscillation_mode and \ + (value in MideaFADevice._oscillation_modes or not value): message = MessageSet(self._protocol_version, self.subtype) if value == "Off" or not value: message.oscillate = False else: message.oscillate = True - message.oscillation_mode = MideaFADevice._oscillation_modes.index( - value - ) + message.oscillation_mode = MideaFADevice._oscillation_modes.index(value) if value == "Oscillation": - if ( - self._attributes[DeviceAttributes.oscillation_angle] - == "Off" - ): + if self._attributes[DeviceAttributes.oscillation_angle] == "Off": message.oscillation_angle = 3 # 90 else: - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) + message.oscillation_angle = MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] ) elif value == "Tilting": if self._attributes[DeviceAttributes.tilting_angle] == "Off": @@ -199,16 +171,11 @@ def set_oscillation(self, attr, value): self._attributes[DeviceAttributes.tilting_angle] ) else: - if ( - self._attributes[DeviceAttributes.oscillation_angle] - == "Off" - ): + if self._attributes[DeviceAttributes.oscillation_angle] == "Off": message.oscillation_angle = 3 # 90 else: - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) + message.oscillation_angle = MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] ) if self._attributes[DeviceAttributes.tilting_angle] == "Off": message.tilting_angle = 3 # 90 @@ -216,9 +183,8 @@ def set_oscillation(self, attr, value): message.tilting_angle = MideaFADevice._tilting_angles.index( self._attributes[DeviceAttributes.tilting_angle] ) - elif attr == DeviceAttributes.oscillation_angle and ( - value in MideaFADevice._oscillation_angles or not value - ): + elif attr == DeviceAttributes.oscillation_angle and \ + (value in MideaFADevice._oscillation_angles or not value): message = MessageSet(self._protocol_version, self.subtype) if value == "Off" or not value: if self._attributes[DeviceAttributes.tilting_angle] == "Off": @@ -230,22 +196,17 @@ def set_oscillation(self, attr, value): self._attributes[DeviceAttributes.tilting_angle] ) else: - message.oscillation_angle = MideaFADevice._oscillation_angles.index( - value - ) + message.oscillation_angle = MideaFADevice._oscillation_angles.index(value) message.oscillate = True if self._attributes[DeviceAttributes.tilting_angle] == "Off": message.oscillation_mode = 1 - elif ( - self._attributes[DeviceAttributes.oscillation_mode] == "Tilting" - ): + elif self._attributes[DeviceAttributes.oscillation_mode] == "Tilting": message.oscillation_mode = 6 message.tilting_angle = MideaFADevice._tilting_angles.index( self._attributes[DeviceAttributes.tilting_angle] ) - elif attr == DeviceAttributes.tilting_angle and ( - value in MideaFADevice._tilting_angles or not value - ): + elif attr == DeviceAttributes.tilting_angle and \ + (value in MideaFADevice._tilting_angles or not value): message = MessageSet(self._protocol_version, self.subtype) if value == "Off" or not value: if self._attributes[DeviceAttributes.oscillation_angle] == "Off": @@ -253,25 +214,18 @@ def set_oscillation(self, attr, value): else: message.oscillate = True message.oscillation_mode = 1 - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) + message.oscillation_angle = MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] ) else: message.tilting_angle = MideaFADevice._tilting_angles.index(value) message.oscillate = True if self._attributes[DeviceAttributes.oscillation_angle] == "Off": message.oscillation_mode = 2 - elif ( - self._attributes[DeviceAttributes.oscillation_mode] - == "Oscillation" - ): + elif self._attributes[DeviceAttributes.oscillation_mode] == "Oscillation": message.oscillation_mode = 6 - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) + message.oscillation_angle = MideaFADevice._oscillation_angles.index( + self._attributes[DeviceAttributes.oscillation_angle] ) return message @@ -281,14 +235,11 @@ def set_attribute(self, attr, value): DeviceAttributes.oscillate, DeviceAttributes.oscillation_mode, DeviceAttributes.oscillation_angle, - DeviceAttributes.tilting_angle, + DeviceAttributes.tilting_angle ]: message = self.set_oscillation(attr, value) - elif ( - attr == DeviceAttributes.fan_speed - and value > 0 - and not self._attributes[DeviceAttributes.power] - ): + elif attr == DeviceAttributes.fan_speed and value > 0 and \ + not self._attributes[DeviceAttributes.power]: message = MessageSet(self._protocol_version, self.subtype) message.fan_speed = value message.power = True diff --git a/custom_components/midea_ac_lan/midea/devices/fa/message.py b/custom_components/midea_ac_lan/midea/devices/fa/message.py index 6e11a307..4153e040 100644 --- a/custom_components/midea_ac_lan/midea/devices/fa/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fa/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageFABase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xFA, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=None, - ) + body_type=None) @property def body(self): @@ -37,8 +41,7 @@ def __init__(self, protocol_version, subtype): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00, - ) + body_type=0x00) self._subtype = subtype self.power = None self.lock = None @@ -52,84 +55,31 @@ def __init__(self, protocol_version, subtype): @property def _body(self): if 1 <= self._subtype <= 10 or self._subtype == 161: - _body_return = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + _body_return = bytearray([ + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + ]) if self._subtype != 10: _body_return[13] = 0xFF else: - _body_return = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + _body_return = bytearray([ + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00 + ]) if self.power is not None: if self.power: _body_return[3] = 1 @@ -150,13 +100,9 @@ def _body(self): else: _body_return[7] = 0 if self.oscillation_angle is not None: - _body_return[7] = ( - 1 | _body_return[7] | ((self.oscillation_angle << 4) & 0x70) - ) + _body_return[7] = 1 | _body_return[7] | ((self.oscillation_angle << 4) & 0x70) if self.oscillation_mode is not None: - _body_return[7] = ( - 1 | _body_return[7] | ((self.oscillation_mode << 1) & 0x0E) - ) + _body_return[7] = 1 | _body_return[7] | ((self.oscillation_mode << 1) & 0x0E) if self.tilting_angle is not None and len(_body_return) > 24: _body_return[24] = self.tilting_angle return _body_return @@ -171,7 +117,7 @@ def __init__(self, body): else: self.child_lock = False self.power = (body[4] & 0x01) > 0 - mode = (body[4] & 0x1E) >> 1 + mode = ((body[4] & 0x1E) >> 1) if mode > 0: self.mode = mode - 1 fan_speed = body[5] @@ -188,10 +134,6 @@ def __init__(self, body): class MessageFAResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: + if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1]: self.set_body(FAGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fb/device.py b/custom_components/midea_ac_lan/midea/devices/fb/device.py index c5ae1887..c7b614ab 100644 --- a/custom_components/midea_ac_lan/midea/devices/fb/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fb/device.py @@ -1,12 +1,13 @@ import logging - -from .message import MessageFBResponse, MessageQuery, MessageSet - +from .message import ( + MessageQuery, + MessageFBResponse, + MessageSet +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -22,30 +23,22 @@ class DeviceAttributes(StrEnum): class MideaFBDevice(MiedaDevice): - _modes = { - 0x01: "Auto", - 0x02: "ECO", - 0x03: "Sleep", - 0x04: "Anti-freezing", - 0x05: "Comfort", - 0x06: "Constant-temperature", - 0x07: "Normal", - 0x08: "Fast-heating", - 0x10: "Standby", - } + _modes = {0x01: "Auto", 0x02: "ECO", 0x03: "Sleep", + 0x04: "Anti-freezing", 0x05: "Comfort", 0x06: "Constant-temperature", + 0x07: "Normal", 0x08: "Fast-heating", 0x10: "Standby"} def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -65,8 +58,7 @@ def __init__( DeviceAttributes.target_temperature: None, DeviceAttributes.current_temperature: None, DeviceAttributes.child_lock: False, - }, - ) + }) @property def modes(self): diff --git a/custom_components/midea_ac_lan/midea/devices/fb/message.py b/custom_components/midea_ac_lan/midea/devices/fb/message.py index 57e613d9..2fe89860 100644 --- a/custom_components/midea_ac_lan/midea/devices/fb/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fb/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageFBBase(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xFB, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=None, - ) + body_type=None) @property def body(self): @@ -37,8 +41,7 @@ def __init__(self, protocol_version, subtype): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00, - ) + body_type=0x00) self._subtype = subtype self.power = None self.mode = None @@ -50,52 +53,24 @@ def __init__(self, protocol_version, subtype): def body(self): power = 0 if self.power is None else (0x01 if self.power else 0x02) mode = 0 if self.mode is None else self.mode - heating_level = ( - 0 - if self.heating_level is None - else ( - int(self.heating_level if 1 <= self.heating_level <= 10 else 0) & 0xFF - ) - ) - target_temperature = ( - 0 - if self.target_temperature is None - else ( - int( - (self.target_temperature + 41) - if -40 <= self.target_temperature <= 50 - else (0x80 if self.target_temperature in [0x80, 87] else 0) - ) - & 0xFF - ) - ) - child_lock = ( - 0xFF if self.child_lock is None else (0x01 if self.child_lock else 0x00) - ) - _return_body = bytearray( - [ - power, - 0x00, - 0x00, - 0x00, - mode, - heating_level, - target_temperature, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - child_lock, - 0x00, - ] - ) + heating_level = 0 if self.heating_level is None else \ + (int(self.heating_level if 1 <= self.heating_level <= 10 else 0) & 0xFF) + target_temperature = 0 if self.target_temperature is None else \ + (int((self.target_temperature + 41) if -40 <= self.target_temperature <= 50 else + (0x80 if self.target_temperature in [0x80, 87] else 0)) & 0xFF) + child_lock = 0xFF if self.child_lock is None else (0x01 if self.child_lock else 0x00) + _return_body = bytearray([ + power, + 0x00, 0x00, 0x00, + mode, + heating_level, + target_temperature, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + child_lock, + 0x00 + ]) if self._subtype > 5: _return_body += bytearray([0x00, 0x00, 0x00]) return _return_body @@ -125,10 +100,6 @@ def __init__(self, body): class MessageFBResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: + if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1]: self.set_body(FBGeneralMessageBody(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fc/device.py b/custom_components/midea_ac_lan/midea/devices/fc/device.py index fad4f23d..e4282516 100644 --- a/custom_components/midea_ac_lan/midea/devices/fc/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fc/device.py @@ -1,13 +1,14 @@ -import json import logging - -from .message import MessageFCResponse, MessageQuery, MessageSet - +import json +from .message import ( + MessageQuery, + MessageFCResponse, + MessageSet +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -32,29 +33,28 @@ class DeviceAttributes(StrEnum): class MideaFCDevice(MiedaDevice): _modes = { - 0x00: "Standby", - 0x10: "Auto", - 0x20: "Manual", - 0x30: "Sleep", - 0x40: "Fast", - 0x50: "Smoke", + 0x00: "Standby", 0x10: "Auto", 0x20: "Manual", 0x30: "Sleep", 0x40: "Fast", 0x50: "Smoke" + } + _speeds = { + 1: "Auto", 4: "Standby", 39: "Low", 59: "Medium", 80: "High" + } + _screen_displays = { + 0: "Bright", 6: "Dim", 7: "Off" } - _speeds = {1: "Auto", 4: "Standby", 39: "Low", 59: "Medium", 80: "High"} - _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} _detect_modes = ["Off", "PM 2.5", "Methanal"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -82,8 +82,7 @@ def __init__( DeviceAttributes.prompt_tone: True, DeviceAttributes.filter1_life: None, DeviceAttributes.filter2_life: None, - }, - ) + }) self._standby_detect_default = [40, 20] self._standby_detect = self._standby_detect_default @@ -127,9 +126,7 @@ def process_message(self, msg): self._attributes[status] = None elif status == DeviceAttributes.screen_display: if value in MideaFCDevice._screen_displays.keys(): - self._attributes[status] = MideaFCDevice._screen_displays.get( - value - ) + self._attributes[status] = MideaFCDevice._screen_displays.get(value) else: self._attributes[status] = None elif status == DeviceAttributes.detect_mode: @@ -151,40 +148,20 @@ def make_message_set(self): message.anion = self._attributes[DeviceAttributes.anion] message.standby = self._attributes[DeviceAttributes.standby] message.screen_display = self._attributes[DeviceAttributes.screen_display] - message.detect_mode = ( - 0 - if self._attributes[DeviceAttributes.detect_mode] is None - else MideaFCDevice._detect_modes.index( - self._attributes[DeviceAttributes.detect_mode] - ) - ) - message.mode = ( - 0x10 - if self._attributes[DeviceAttributes.mode] is None - else list(MideaFCDevice._modes.keys())[ - list(MideaFCDevice._modes.values()).index( - self._attributes[DeviceAttributes.mode] - ) - ] - ) - message.fan_speed = ( - 39 - if self._attributes[DeviceAttributes.fan_speed] is None - else list(MideaFCDevice._speeds.keys())[ - list(MideaFCDevice._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - ) - message.screen_display = ( - 0 - if self._attributes[DeviceAttributes.screen_display] is None - else list(MideaFCDevice._screen_displays.keys())[ - list(MideaFCDevice._screen_displays.values()).index( - self._attributes[DeviceAttributes.screen_display] - ) - ] - ) + message.detect_mode = 0 if self._attributes[DeviceAttributes.detect_mode] is None else \ + MideaFCDevice._detect_modes.index(self._attributes[DeviceAttributes.detect_mode]) + message.mode = 0x10 if self._attributes[DeviceAttributes.mode] is None else \ + list(MideaFCDevice._modes.keys())[list(MideaFCDevice._modes.values()).index( + self._attributes[DeviceAttributes.mode] + )] + message.fan_speed = 39 if self._attributes[DeviceAttributes.fan_speed] is None else \ + list(MideaFCDevice._speeds.keys())[list(MideaFCDevice._speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + )] + message.screen_display = 0 if self._attributes[DeviceAttributes.screen_display] is None else \ + list(MideaFCDevice._screen_displays.keys())[list(MideaFCDevice._screen_displays.values()).index( + self._attributes[DeviceAttributes.screen_display] + )] message.standby_detect = self._standby_detect return message @@ -206,9 +183,9 @@ def set_attribute(self, attr, value): ] elif attr == DeviceAttributes.screen_display: if value in MideaFCDevice._screen_displays.values(): - message.screen_display = list( - MideaFCDevice._screen_displays.keys() - )[list(MideaFCDevice._screen_displays.values()).index(value)] + message.screen_display = list(MideaFCDevice._screen_displays.keys())[ + list(MideaFCDevice._screen_displays.values()).index(value) + ] elif not value: message.screen_display = 7 elif attr == DeviceAttributes.detect_mode: diff --git a/custom_components/midea_ac_lan/midea/devices/fc/message.py b/custom_components/midea_ac_lan/midea/devices/fc/message.py index db391f8e..febbfbaa 100644 --- a/custom_components/midea_ac_lan/midea/devices/fc/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fc/message.py @@ -1,5 +1,10 @@ from ...core.crc8 import calculate -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageFCBase(MessageRequest): @@ -10,7 +15,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xFC, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) MessageFCBase._message_serial += 1 if MessageFCBase._message_serial >= 254: @@ -33,34 +38,17 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41, - ) + body_type=0x41) @property def _body(self): - return bytearray( - [ - 0x00, - 0x00, - 0xFF, - 0x03, - 0x00, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + 0x00, 0x00, 0xFF, 0x03, + 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + ]) class MessageSet(MessageFCBase): @@ -68,8 +56,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x48, - ) + body_type=0x48) self.power = False self.mode = 0 self.fan_speed = 0 @@ -104,30 +91,16 @@ def _body(self): standby = 0x08 standby_detect_high = 0 standby_detect_low = 0 - return bytearray( - [ - power | prompt_tone | detect | 0x02, - self.mode, - self.fan_speed, - 0x00, - 0x00, - 0x00, - 0x00, - child_lock, - self.screen_display, - anion, - 0x00, - 0x00, - 0x00, - detect_mode, - standby, - standby_detect_high, - standby_detect_low, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + power | prompt_tone | detect | 0x02, + self.mode, + self.fan_speed, + 0x00, 0x00, 0x00, 0x00, + child_lock, self.screen_display, anion, + 0x00, 0x00, 0x00, detect_mode, + standby, standby_detect_high, standby_detect_low, + 0x00, 0x00, 0x00, + ]) class FCGeneralMessageBody(MessageBody): @@ -198,11 +171,8 @@ def __init__(self, message): if self.body_type in [0xB0, 0xB1]: pass else: - if ( - self.message_type - in [MessageType.query, MessageType.set, MessageType.notify1] - and self.body_type == 0xC8 - ): + if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1] and \ + self.body_type == 0xC8: self.set_body(FCGeneralMessageBody(super().body)) elif self.message_type == MessageType.notify1 and self.body_type == 0xA0: self.set_body(FCNotifyMessageBody(super().body)) diff --git a/custom_components/midea_ac_lan/midea/devices/fd/device.py b/custom_components/midea_ac_lan/midea/devices/fd/device.py index 82dfc063..b3f010ff 100644 --- a/custom_components/midea_ac_lan/midea/devices/fd/device.py +++ b/custom_components/midea_ac_lan/midea/devices/fd/device.py @@ -1,12 +1,13 @@ import logging - -from .message import MessageFDResponse, MessageQuery, MessageSet - +from .message import ( + MessageQuery, + MessageFDResponse, + MessageSet +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -27,45 +28,31 @@ class DeviceAttributes(StrEnum): class MideaFDDevice(MiedaDevice): _modes = [ - "Manual", - "Auto", - "Continuous", - "Living-Room", - "Bed-Room", - "Kitchen", - "Sleep", + "Manual", "Auto", "Continuous", "Living-Room", "Bed-Room", "Kitchen", "Sleep" ] _speeds_old = { - 1: "Lowest", - 40: "Low", - 60: "Medium", - 80: "High", - 102: "Auto", - 127: "Off", + 1: "Lowest", 40: "Low", 60: "Medium", 80: "High", 102: "Auto", 127: "Off" } _speeds_new = { - 1: "Lowest", - 39: "Low", - 59: "Medium", - 80: "High", - 101: "Auto", - 127: "Off", + 1: "Lowest", 39: "Low", 59: "Medium", 80: "High", 101: "Auto", 127: "Off" + } + _screen_displays = { + 0: "Bright", 6: "Dim", 7: "Off" } - _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} _detect_modes = ["Off", "PM 2.5", "Methanal"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -89,8 +76,7 @@ def __init__( DeviceAttributes.mode: None, DeviceAttributes.screen_display: None, DeviceAttributes.disinfect: None, - }, - ) + }) if self.subtype > 5: self._speeds = MideaFDDevice._speeds_new else: @@ -135,9 +121,7 @@ def process_message(self, msg): self._attributes[status] = None elif status == DeviceAttributes.screen_display: if value in MideaFDDevice._screen_displays.keys(): - self._attributes[status] = MideaFDDevice._screen_displays.get( - value - ) + self._attributes[status] = MideaFDDevice._screen_displays.get(value) else: self._attributes[status] = None else: @@ -152,29 +136,17 @@ def make_message_set(self): message.screen_display = self._attributes[DeviceAttributes.screen_display] message.disinfect = self._attributes[DeviceAttributes.disinfect] if self._attributes[DeviceAttributes.mode] in MideaFDDevice._modes: - message.mode = ( - MideaFDDevice._modes.index(self._attributes[DeviceAttributes.mode]) + 1 - ) + message.mode = MideaFDDevice._modes.index(self._attributes[DeviceAttributes.mode]) + 1 else: message.mode = 1 - message.fan_speed = ( - 40 - if self._attributes[DeviceAttributes.fan_speed] is None - else list(self._speeds.keys())[ - list(self._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - ) - message.screen_display = ( - 0 - if self._attributes[DeviceAttributes.screen_display] is None - else list(MideaFDDevice._screen_displays.keys())[ - list(MideaFDDevice._screen_displays.values()).index( - self._attributes[DeviceAttributes.screen_display] - ) - ] - ) + message.fan_speed = 40 if self._attributes[DeviceAttributes.fan_speed] is None else \ + list(self._speeds.keys())[list(self._speeds.values()).index( + self._attributes[DeviceAttributes.fan_speed] + )] + message.screen_display = 0 if self._attributes[DeviceAttributes.screen_display] is None else \ + list(MideaFDDevice._screen_displays.keys())[list(MideaFDDevice._screen_displays.values()).index( + self._attributes[DeviceAttributes.screen_display] + )] return message def set_attribute(self, attr, value): @@ -193,9 +165,9 @@ def set_attribute(self, attr, value): ] elif attr == DeviceAttributes.screen_display: if value in MideaFDDevice._screen_displays.values(): - message.screen_display = list( - MideaFDDevice._screen_displays.keys() - )[list(MideaFDDevice._screen_displays.values()).index(value)] + message.screen_display = list(MideaFDDevice._screen_displays.keys())[ + list(MideaFDDevice._screen_displays.values()).index(value) + ] elif not value: message.screen_display = 7 else: diff --git a/custom_components/midea_ac_lan/midea/devices/fd/message.py b/custom_components/midea_ac_lan/midea/devices/fd/message.py index 39aa6ec1..d5402bff 100644 --- a/custom_components/midea_ac_lan/midea/devices/fd/message.py +++ b/custom_components/midea_ac_lan/midea/devices/fd/message.py @@ -1,5 +1,10 @@ from ...core.crc8 import calculate -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class MessageFDBase(MessageRequest): @@ -10,7 +15,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0xFD, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) MessageFDBase._message_serial += 1 if MessageFDBase._message_serial >= 254: @@ -33,34 +38,17 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x41, - ) + body_type=0x41) @property def _body(self): - return bytearray( - [ - 0x81, - 0x00, - 0xFF, - 0x03, - 0x00, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + 0x81, 0x00, 0xFF, 0x03, + 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + ]) class MessageSet(MessageFDBase): @@ -68,8 +56,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x48, - ) + body_type=0x48) self.power = False self.fan_speed = 0 self.target_humidity = 50 @@ -83,31 +70,20 @@ def _body(self): power = 0x01 if self.power else 0x00 prompt_tone = 0x40 if self.prompt_tone else 0x00 disinfect = 0 if self.disinfect is None else (1 if self.disinfect else 2) - return bytearray( - [ - power | prompt_tone | 0x02, - 0x00, - self.fan_speed, - 0x00, - 0x00, - 0x00, - self.target_humidity, - 0x00, - self.screen_display, - self.mode, - 0x00, - 0x00, - 0x00, - 0x00, - disinfect, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + return bytearray([ + power | prompt_tone | 0x02, + 0x00, + self.fan_speed, + 0x00, 0x00, 0x00, + self.target_humidity, + 0x00, + self.screen_display, + self.mode, + 0x00, 0x00, 0x00, 0x00, + disinfect, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + ]) class FDC8MessageBody(MessageBody): @@ -151,11 +127,7 @@ def __init__(self, body): class MessageFDResponse(MessageResponse): def __init__(self, message): super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: + if self.message_type in [MessageType.query, MessageType.set, MessageType.notify1]: if self.body_type in [0xB0, 0xB1]: pass elif self.body_type == 0xA0: @@ -163,9 +135,5 @@ def __init__(self, message): elif self.body_type == 0xC8: self.set_body(FDC8MessageBody(super().body)) self.set_attr() - if ( - hasattr(self, "fan_speed") - and self.fan_speed is not None - and self.fan_speed < 5 - ): + if hasattr(self, "fan_speed") and self.fan_speed is not None and self.fan_speed < 5: self.fan_speed = 1 diff --git a/custom_components/midea_ac_lan/midea/devices/x13/device.py b/custom_components/midea_ac_lan/midea/devices/x13/device.py index cc019d20..50843ec7 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/device.py @@ -1,13 +1,14 @@ -import json import logging - -from .message import Message13Response, MessageQuery, MessageSet - +import json +from .message import ( + MessageQuery, + MessageSet, + Message13Response +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -25,17 +26,17 @@ class Midea13Device(MiedaDevice): _effects = ["Manual", "Living", "Reading", "Mildly", "Cinema", "Night"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -53,9 +54,8 @@ def __init__( DeviceAttributes.color_temperature: None, DeviceAttributes.rgb_color: None, DeviceAttributes.effect: None, - DeviceAttributes.power: False, - }, - ) + DeviceAttributes.power: False + }) self._color_temp_range = None self._default_color_temp_range = [2700, 6500] self.set_customize(customize) @@ -69,17 +69,12 @@ def color_temp_range(self): return self._color_temp_range def kelvin_to_midea(self, kelvin): - return round( - (kelvin - self._color_temp_range[0]) - / (self._color_temp_range[1] - self._color_temp_range[0]) - * 255 - ) + return round((kelvin - self._color_temp_range[0]) / + (self._color_temp_range[1] - self._color_temp_range[0]) * 255) def midea_to_kelvin(self, midea): - return ( - round((self._color_temp_range[1] - self._color_temp_range[0]) / 255 * midea) - + self._color_temp_range[0] - ) + return round((self._color_temp_range[1] - self._color_temp_range[0]) / 255 * midea) + \ + self._color_temp_range[0] def build_query(self): return [MessageQuery(self._protocol_version)] @@ -106,12 +101,10 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.brightness, - DeviceAttributes.color_temperature, - DeviceAttributes.effect, - DeviceAttributes.power, - ]: + if attr in [DeviceAttributes.brightness, + DeviceAttributes.color_temperature, + DeviceAttributes.effect, + DeviceAttributes.power]: message = MessageSet(self._protocol_version) if attr == DeviceAttributes.effect and value in self._effects: setattr(message, str(attr), Midea13Device._effects.index(value)) diff --git a/custom_components/midea_ac_lan/midea/devices/x13/message.py b/custom_components/midea_ac_lan/midea/devices/x13/message.py index fb208a11..bf6bc09a 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody +) class Message13Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0x13, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,12 +25,13 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x24, - ) + body_type=0x24) @property def _body(self): - return bytearray([0x00, 0x00, 0x00, 0x00]) + return bytearray([ + 0x00, 0x00, 0x00, 0x00 + ]) class MessageSet(Message13Base): @@ -33,8 +39,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x00, - ) + body_type=0x00) self.brightness = None self.color_temperature = None self.effect = None @@ -66,11 +71,11 @@ def __init__(self, body): self.effect = self.read_byte(body, 3) - 1 if self.effect > 5: self.effect = 1 - """ + ''' self.rgb_color = [self.read_byte(body, 5), self.read_byte(body, 6), self.read_byte(body, 7)] - """ + ''' self.power = self.read_byte(body, 8) > 0 @@ -79,12 +84,12 @@ def __init__(self, body): super().__init__(body) self.control_success = body[1] > 0 - class Message13Response(MessageResponse): def __init__(self, message): super().__init__(message) - if self.body_type == 0xA4: + if self.body_type == 0xa4: self.set_body(MessageMainLightBody(super().body)) elif self.message_type == MessageType.set and self.body_type > 0x80: self.set_body(MessageMainLightResponseBody(super().body)) self.set_attr() + diff --git a/custom_components/midea_ac_lan/midea/devices/x26/device.py b/custom_components/midea_ac_lan/midea/devices/x26/device.py index b01c67b5..2230154e 100644 --- a/custom_components/midea_ac_lan/midea/devices/x26/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x26/device.py @@ -1,13 +1,14 @@ import logging import math - -from .message import Message26Response, MessageQuery, MessageSet - +from .message import ( + MessageQuery, + MessageSet, + Message26Response +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -28,17 +29,17 @@ class Midea26Device(MiedaDevice): _directions = ["60", "70", "80", "90", "100", "110", "120", "Oscillate"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -58,9 +59,8 @@ def __init__( DeviceAttributes.direction: None, DeviceAttributes.current_humidity: None, DeviceAttributes.current_radar: None, - DeviceAttributes.current_temperature: None, - }, - ) + DeviceAttributes.current_temperature: None + }) self._fields = {} @staticmethod @@ -68,11 +68,8 @@ def _convert_to_midea_direction(direction): if direction == "Oscillate": result = 0xFD else: - result = ( - Midea26Device._directions.index(direction) * 10 + 60 - if direction in Midea26Device._directions - else 0xFD - ) + result = Midea26Device._directions.index(direction) * 10 + 60 \ + if direction in Midea26Device._directions else 0xFD return result @staticmethod @@ -114,23 +111,21 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.main_light, - DeviceAttributes.night_light, - DeviceAttributes.mode, - DeviceAttributes.direction, - ]: + if attr in [DeviceAttributes.main_light, + DeviceAttributes.night_light, + DeviceAttributes.mode, + DeviceAttributes.direction + ]: message = MessageSet(self._protocol_version) message.fields = self._fields message.main_light = self._attributes[DeviceAttributes.main_light] message.night_light = self._attributes[DeviceAttributes.night_light] - message.mode = Midea26Device._modes.index( - self._attributes[DeviceAttributes.mode] - ) - message.direction = self._convert_to_midea_direction( - self._attributes[DeviceAttributes.direction] - ) - if attr in [DeviceAttributes.main_light, DeviceAttributes.night_light]: + message.mode = Midea26Device._modes.index(self._attributes[DeviceAttributes.mode]) + message.direction = self._convert_to_midea_direction(self._attributes[DeviceAttributes.direction]) + if attr in [ + DeviceAttributes.main_light, + DeviceAttributes.night_light + ]: message.main_light = False message.night_light = False setattr(message, str(attr), value) diff --git a/custom_components/midea_ac_lan/midea/devices/x26/message.py b/custom_components/midea_ac_lan/midea/devices/x26/message.py index 51026d3a..268bf96e 100644 --- a/custom_components/midea_ac_lan/midea/devices/x26/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x26/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody +) class Message26Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0x26, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,12 +25,12 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): - return bytearray([]) + return bytearray([ + ]) class MessageSet(Message26Base): @@ -33,8 +38,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01, - ) + body_type=0x01) self.fields = {} self.main_light = False self.night_light = False @@ -47,53 +51,47 @@ def read_field(self, field): @property def _body(self): - return bytearray( - [ - 1 if self.main_light else 0, - self.read_field("MAIN_LIGHT_BRIGHTNESS"), - 1 if self.night_light else 0, - self.read_field("NIGHT_LIGHT_BRIGHTNESS"), - self.read_field("RADAR_INDUCTION_ENABLE"), - self.read_field("RADAR_INDUCTION_CLOSING_TIME"), - self.read_field("LIGHT_INTENSITY_THRESHOLD"), - self.read_field("RADAR_SENSITIVITY"), - 1 if self.mode == 1 or self.mode == 2 else 0, - ( - 0 - if not (self.mode == 1 or self.mode == 2) - else 55 if self.mode == 1 else 30 - ), - self.read_field("HEATING_SPEED"), - self.direction, - 1 if self.mode == 3 else 0, - self.read_field("BATH_HEATING_TIME"), - self.read_field("BATH_TEMPERATURE"), - self.read_field("BATH_SPEED"), - self.direction, - 1 if self.mode == 5 else 0, - self.read_field("VENTILATION_SPEED"), - self.direction, - 1 if self.mode == 6 else 0, - self.read_field("DRYING_TIME"), - self.read_field("DRYING_TEMPERATURE"), - self.read_field("DRYING_SPEED"), - self.direction, - 1 if self.mode == 4 else 0, - self.read_field("BLOWING_SPEED"), - self.direction, - self.read_field("DELAY_ENABLE"), - self.read_field("DELAY_TIME"), - self.read_field("SOFT_WIND_ENABLE"), - self.read_field("SOFT_WIND_TIME"), - self.read_field("SOFT_WIND_TEMPERATURE"), - self.read_field("SOFT_WIND_SPEED"), - self.read_field("SOFT_WIND_DIRECTION"), - self.read_field("WINDLESS_ENABLE"), - self.read_field("ANION_ENABLE"), - self.read_field("SMELLY_ENABLE"), - self.read_field("SMELLY_THRESHOLD"), - ] - ) + return bytearray([ + 1 if self.main_light else 0, + self.read_field("MAIN_LIGHT_BRIGHTNESS"), + 1 if self.night_light else 0, + self.read_field("NIGHT_LIGHT_BRIGHTNESS"), + self.read_field("RADAR_INDUCTION_ENABLE"), + self.read_field("RADAR_INDUCTION_CLOSING_TIME"), + self.read_field("LIGHT_INTENSITY_THRESHOLD"), + self.read_field("RADAR_SENSITIVITY"), + 1 if self.mode == 1 or self.mode == 2 else 0, + 0 if not (self.mode == 1 or self.mode == 2) else 55 if self.mode == 1 else 30, + self.read_field("HEATING_SPEED"), + self.direction, + 1 if self.mode == 3 else 0, + self.read_field("BATH_HEATING_TIME"), + self.read_field("BATH_TEMPERATURE"), + self.read_field("BATH_SPEED"), + self.direction, + 1 if self.mode == 5 else 0, + self.read_field("VENTILATION_SPEED"), + self.direction, + 1 if self.mode == 6 else 0, + self.read_field("DRYING_TIME"), + self.read_field("DRYING_TEMPERATURE"), + self.read_field("DRYING_SPEED"), + self.direction, + 1 if self.mode == 4 else 0, + self.read_field("BLOWING_SPEED"), + self.direction, + self.read_field("DELAY_ENABLE"), + self.read_field("DELAY_TIME"), + self.read_field("SOFT_WIND_ENABLE"), + self.read_field("SOFT_WIND_TIME"), + self.read_field("SOFT_WIND_TEMPERATURE"), + self.read_field("SOFT_WIND_SPEED"), + self.read_field("SOFT_WIND_DIRECTION"), + self.read_field("WINDLESS_ENABLE"), + self.read_field("ANION_ENABLE"), + self.read_field("SMELLY_ENABLE"), + self.read_field("SMELLY_THRESHOLD") + ]) class Message26Body(MessageBody): @@ -109,7 +107,7 @@ def __init__(self, body): self.fields["LIGHT_INTENSITY_THRESHOLD"] = self.read_byte(body, 7) self.fields["RADAR_SENSITIVITY"] = self.read_byte(body, 8) heat_mode = self.read_byte(body, 9) > 0 - heat_temperature = self.read_byte(body, 10) + heat_temperature = self.read_byte(body, 10) self.fields["HEATING_SPEED"] = self.read_byte(body, 11) heat_direction = self.read_byte(body, 12) bath_mode = self.read_byte(body, 13) > 0 @@ -170,10 +168,7 @@ def __init__(self, body): class Message26Response(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - self.message_type - in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01 - ): + if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: self.set_body(Message26Body(super().body)) self.set_attr() + diff --git a/custom_components/midea_ac_lan/midea/devices/x34/device.py b/custom_components/midea_ac_lan/midea/devices/x34/device.py index 76e8f303..a1cb1ef7 100644 --- a/custom_components/midea_ac_lan/midea/devices/x34/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x34/device.py @@ -1,18 +1,15 @@ import logging - from .message import ( - Message34Response, - MessageLock, - MessagePower, MessageQuery, + MessagePower, MessageStorage, + MessageLock, + Message34Response ) - try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -47,17 +44,17 @@ class DeviceAttributes(StrEnum): class Midea34Device(MiedaDevice): def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -94,32 +91,31 @@ def __init__( DeviceAttributes.error_code: None, DeviceAttributes.softwater: 0, DeviceAttributes.wrong_operation: None, - DeviceAttributes.bright: 0, - }, - ) + DeviceAttributes.bright: 0 + }) self._modes = { - 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR - 0x1: "Auto", # BYTE_MODE_AUTO_WASH - 0x2: "Heavy", # BYTE_MODE_STRONG_WASH - 0x3: "Normal", # BYTE_MODE_STANDARD_WASH - 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH - 0x5: "Delicate", # BYTE_MODE_GLASS_WASH - 0x6: "Hour", # BYTE_MODE_HOUR_WASH - 0x7: "Quick", # BYTE_MODE_FAST_WASH - 0x8: "Rinse", # BYTE_MODE_SOAK_WASH - 0x9: "90min", # BYTE_MODE_90MIN_WASH - 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN - 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH - 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE - 0xD: "Germ", # BYTE_MODE_GERM ??? - 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH - 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM - 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH - 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH - 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH - 0x14: "Less Wash", # BYTE_MODE_LESS_WASH - 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH - 0x19: "Cloud Wash", # BYTE_MODE_CLOUD_WASH + 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR + 0x1: "Auto", # BYTE_MODE_AUTO_WASH + 0x2: "Heavy", # BYTE_MODE_STRONG_WASH + 0x3: "Normal", # BYTE_MODE_STANDARD_WASH + 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH + 0x5: "Delicate", # BYTE_MODE_GLASS_WASH + 0x6: "Hour", # BYTE_MODE_HOUR_WASH + 0x7: "Quick", # BYTE_MODE_FAST_WASH + 0x8: "Rinse", # BYTE_MODE_SOAK_WASH + 0x9: "90min", # BYTE_MODE_90MIN_WASH + 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN + 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH + 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE + 0xD: "Germ", # BYTE_MODE_GERM ??? + 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH + 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM + 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH + 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH + 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH + 0x14: "Less Wash", # BYTE_MODE_LESS_WASH + 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH + 0x19: "Cloud Wash" # BYTE_MODE_CLOUD_WASH } self._status = ["Off", "Idle", "Delay", "Running", "Error"] self._progress = ["Idle", "Pre-wash", "Wash", "Rinse", "Dry", "Complete"] diff --git a/custom_components/midea_ac_lan/midea/devices/x34/message.py b/custom_components/midea_ac_lan/midea/devices/x34/message.py index c107acd2..729280f1 100644 --- a/custom_components/midea_ac_lan/midea/devices/x34/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x34/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) class Message34Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0x34, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,8 +25,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x00, - ) + body_type=0x00) @property def _body(self): @@ -33,14 +37,16 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x08, - ) + body_type=0x08) self.power = False @property def _body(self): power = 0x01 if self.power else 0x00 - return bytearray([power, 0x00, 0x00, 0x00]) + return bytearray([ + power, + 0x00, 0x00, 0x00 + ]) class MessageLock(Message34Base): @@ -48,8 +54,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x83, - ) + body_type=0x83) self.lock = False @property @@ -63,18 +68,14 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x81, - ) + body_type=0x81) self.storage = False @property def _body(self): storage = 0x01 if self.storage else 0x00 - return ( - bytearray([0x00, 0x00, 0x00, storage]) - + bytearray([0xFF] * 6) - + bytearray([0x00] * 27) - ) + return bytearray([0x00, 0x00, 0x00, storage]) + \ + bytearray([0xff] * 6) + bytearray([0x00] * 27) class Message34Body(MessageBody): @@ -84,9 +85,9 @@ def __init__(self, body): self.status = body[1] self.mode = body[2] self.additional = body[3] - self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close - self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage - self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage + self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close + self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage + self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage start_pause = (body[5] & 0x08) > 0 if start_pause: self.start = True @@ -114,9 +115,7 @@ def __init__(self, body): class Message34Response(MessageResponse): def __init__(self, message): super().__init__(message) - if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0 - ): + if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or \ + (self.message_type in [MessageType.query, MessageType.notify1] and self.body_type == 0): self.set_body(Message34Body(super().body)) self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/x40/device.py b/custom_components/midea_ac_lan/midea/devices/x40/device.py index 6115e00c..f7d33fd9 100644 --- a/custom_components/midea_ac_lan/midea/devices/x40/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x40/device.py @@ -1,13 +1,14 @@ import logging import math - -from .message import Message40Response, MessageQuery, MessageSet - +from .message import ( + MessageQuery, + MessageSet, + Message40Response +) try: from enum import StrEnum except ImportError: from ...backports.myenum import StrEnum - from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) @@ -26,17 +27,17 @@ class Midea40Device(MiedaDevice): _directions = ["60", "70", "80", "90", "100", "Oscillate"] def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, + self, + name: str, + device_id: int, + ip_address: str, + port: int, + token: str, + key: str, + protocol: int, + model: str, + subtype: int, + customize: str ): super().__init__( name=name, @@ -55,9 +56,8 @@ def __init__( DeviceAttributes.direction: False, DeviceAttributes.ventilation: False, DeviceAttributes.smelly_sensor: False, - DeviceAttributes.current_temperature: None, - }, - ) + DeviceAttributes.current_temperature: None + }) self._fields = {} @property @@ -69,11 +69,8 @@ def _convert_to_midea_direction(direction): if direction == "Oscillate": result = 0xFD else: - result = ( - Midea40Device._directions.index(direction) * 10 + 60 - if direction in Midea40Device._directions - else 0xFD - ) + result = Midea40Device._directions.index(direction) * 10 + 60 \ + if direction in Midea40Device._directions else 0xFD return result @staticmethod @@ -105,22 +102,18 @@ def process_message(self, msg): return new_status def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.light, - DeviceAttributes.fan_speed, - DeviceAttributes.direction, - DeviceAttributes.ventilation, - DeviceAttributes.smelly_sensor, - ]: + if attr in [DeviceAttributes.light, + DeviceAttributes.fan_speed, + DeviceAttributes.direction, + DeviceAttributes.ventilation, + DeviceAttributes.smelly_sensor]: message = MessageSet(self._protocol_version) message.fields = self._fields message.light = self._attributes[DeviceAttributes.light] message.ventilation = self._attributes[DeviceAttributes.ventilation] message.smelly_sensor = self._attributes[DeviceAttributes.smelly_sensor] message.fan_speed = self._attributes[DeviceAttributes.fan_speed] - message.direction = self._convert_to_midea_direction( - self._attributes[DeviceAttributes.direction] - ) + message.direction = self._convert_to_midea_direction(self._attributes[DeviceAttributes.direction]) if attr == DeviceAttributes.direction: message.direction = self._convert_to_midea_direction(value) elif attr == DeviceAttributes.ventilation and message.fan_speed == 2: diff --git a/custom_components/midea_ac_lan/midea/devices/x40/message.py b/custom_components/midea_ac_lan/midea/devices/x40/message.py index fba44d2e..a7afb1be 100644 --- a/custom_components/midea_ac_lan/midea/devices/x40/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x40/message.py @@ -1,4 +1,9 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody +) class Message40Base(MessageRequest): @@ -7,7 +12,7 @@ def __init__(self, protocol_version, message_type, body_type): device_type=0x40, protocol_version=protocol_version, message_type=message_type, - body_type=body_type, + body_type=body_type ) @property @@ -20,12 +25,12 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, - body_type=0x01, - ) + body_type=0x01) @property def _body(self): - return bytearray([]) + return bytearray([ + ]) class MessageSet(Message40Base): @@ -33,8 +38,7 @@ def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, - body_type=0x01, - ) + body_type=0x01) self.fields = {} self.light = False self.fan_speed = 0 @@ -54,49 +58,47 @@ def _body(self): ventilation = 1 if self.ventilation else 0 direction = self.direction smelly_sensor = 1 if self.smelly_sensor else 0 - return bytearray( - [ - light, - self.read_field("MAIN_LIGHT_BRIGHTNESS"), - self.read_field("NIGHT_LIGHT_ENABLE"), - self.read_field("NIGHT_LIGHT_BRIGHTNESS"), - self.read_field("RADAR_INDUCTION_ENABLE"), - self.read_field("RADAR_INDUCTION_CLOSING_TIME"), - self.read_field("LIGHT_INTENSITY_THRESHOLD"), - self.read_field("RADAR_SENSITIVITY"), - self.read_field("HEATING_ENABLE"), - self.read_field("HEATING_TEMPERATURE"), - self.read_field("HEATING_SPEED"), - self.read_field("HEATING_DIRECTION"), - self.read_field("BATH_ENABLE"), - self.read_field("BATH_HEATING_TIME"), - self.read_field("BATH_TEMPERATURE"), - self.read_field("BATH_SPEED"), - self.read_field("BATH_DIRECTION"), - ventilation, - self.read_field("VENTILATION_SPEED"), - self.read_field("VENTILATION_DIRECTION"), - self.read_field("DRYING_ENABLE"), - self.read_field("DRYING_TIME"), - self.read_field("DRYING_TEMPERATURE"), - self.read_field("DRYING_SPEED"), - self.read_field("DRYING_DIRECTION"), - blow, - fan_speed, - direction, - self.read_field("DELAY_ENABLE"), - self.read_field("DELAY_TIME"), - self.read_field("SOFT_WIND_ENABLE"), - self.read_field("SOFT_WIND_TIME"), - self.read_field("SOFT_WIND_TEMPERATURE"), - self.read_field("SOFT_WIND_SPEED"), - self.read_field("SOFT_WIND_DIRECTION"), - self.read_field("WINDLESS_ENABLE"), - self.read_field("ANION_ENABLE"), - smelly_sensor, - self.read_field("SMELLY_THRESHOLD"), - ] - ) + return bytearray([ + light, + self.read_field("MAIN_LIGHT_BRIGHTNESS"), + self.read_field("NIGHT_LIGHT_ENABLE"), + self.read_field("NIGHT_LIGHT_BRIGHTNESS"), + self.read_field("RADAR_INDUCTION_ENABLE"), + self.read_field("RADAR_INDUCTION_CLOSING_TIME"), + self.read_field("LIGHT_INTENSITY_THRESHOLD"), + self.read_field("RADAR_SENSITIVITY"), + self.read_field("HEATING_ENABLE"), + self.read_field("HEATING_TEMPERATURE"), + self.read_field("HEATING_SPEED"), + self.read_field("HEATING_DIRECTION"), + self.read_field("BATH_ENABLE"), + self.read_field("BATH_HEATING_TIME"), + self.read_field("BATH_TEMPERATURE"), + self.read_field("BATH_SPEED"), + self.read_field("BATH_DIRECTION"), + ventilation, + self.read_field("VENTILATION_SPEED"), + self.read_field("VENTILATION_DIRECTION"), + self.read_field("DRYING_ENABLE"), + self.read_field("DRYING_TIME"), + self.read_field("DRYING_TEMPERATURE"), + self.read_field("DRYING_SPEED"), + self.read_field("DRYING_DIRECTION"), + blow, + fan_speed, + direction, + self.read_field("DELAY_ENABLE"), + self.read_field("DELAY_TIME"), + self.read_field("SOFT_WIND_ENABLE"), + self.read_field("SOFT_WIND_TIME"), + self.read_field("SOFT_WIND_TEMPERATURE"), + self.read_field("SOFT_WIND_SPEED"), + self.read_field("SOFT_WIND_DIRECTION"), + self.read_field("WINDLESS_ENABLE"), + self.read_field("ANION_ENABLE"), + smelly_sensor, + self.read_field("SMELLY_THRESHOLD") + ]) class Message40Body(MessageBody): @@ -112,7 +114,7 @@ def __init__(self, body): self.fields["LIGHT_INTENSITY_THRESHOLD"] = body[7] self.fields["RADAR_SENSITIVITY"] = body[8] self.fields["HEATING_ENABLE"] = body[9] - self.fields["HEATING_TEMPERATURE"] = body[10] + self.fields["HEATING_TEMPERATURE"]= body[10] self.fields["HEATING_SPEED"] = body[11] self.fields["HEATING_DIRECTION"] = body[12] self.fields["BATH_ENABLE"] = body[13] > 0 @@ -155,10 +157,7 @@ def __init__(self, body): class Message40Response(MessageResponse): def __init__(self, message): super().__init__(message) - if ( - self.message_type - in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01 - ): + if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: self.set_body(Message40Body(super().body)) self.set_attr() + diff --git a/custom_components/midea_ac_lan/midea_devices.py b/custom_components/midea_ac_lan/midea_devices.py old mode 100755 new mode 100644 index 5ad32bd2..fb46ab95 --- a/custom_components/midea_ac_lan/midea_devices.py +++ b/custom_components/midea_ac_lan/midea_devices.py @@ -1,17 +1,19 @@ -from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass from homeassistant.const import ( - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - CONCENTRATION_PARTS_PER_MILLION, - PERCENTAGE, Platform, - UnitOfEnergy, - UnitOfPower, - UnitOfTemperature, UnitOfTime, + UnitOfTemperature, + UnitOfPower, + PERCENTAGE, UnitOfVolume, + UnitOfEnergy, + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_MILLION ) - +from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass +from .midea.devices.x26.device import DeviceAttributes as X26Attributes +from .midea.devices.x34.device import DeviceAttributes as X34Attributes +from .midea.devices.x40.device import DeviceAttributes as X40Attributes from .midea.devices.a1.device import DeviceAttributes as A1Attributes from .midea.devices.ac.device import DeviceAttributes as ACAttributes from .midea.devices.b0.device import DeviceAttributes as B0Attributes @@ -42,16 +44,18 @@ from .midea.devices.fb.device import DeviceAttributes as FBAttributes from .midea.devices.fc.device import DeviceAttributes as FCAttributes from .midea.devices.fd.device import DeviceAttributes as FDAttributes -from .midea.devices.x26.device import DeviceAttributes as X26Attributes -from .midea.devices.x34.device import DeviceAttributes as X34Attributes -from .midea.devices.x40.device import DeviceAttributes as X40Attributes + MIDEA_DEVICES = { 0x13: { "name": "Light", "entities": { - "light": {"type": Platform.LIGHT, "icon": "mdi:lightbulb", "default": True} - }, + "light": { + "type": Platform.LIGHT, + "icon": "mdi:lightbulb", + "default": True + } + } }, 0x26: { "name": "Bathroom Master", @@ -61,43 +65,43 @@ "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, X26Attributes.current_humidity: { "type": Platform.SENSOR, "name": "Current Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, X26Attributes.current_radar: { "type": Platform.BINARY_SENSOR, "name": "Occupancy Status", - "device_class": BinarySensorDeviceClass.MOVING, + "device_class": BinarySensorDeviceClass.MOVING }, X26Attributes.main_light: { "type": Platform.SWITCH, "name": "Main Light", - "icon": "mdi:lightbulb", + "icon": "mdi:lightbulb" }, X26Attributes.night_light: { "type": Platform.SWITCH, "name": "Night Light", - "icon": "mdi:lightbulb", + "icon": "mdi:lightbulb" }, X26Attributes.mode: { "type": Platform.SELECT, "name": "Mode", "options": "preset_modes", - "icon": "mdi:fan", + "icon": "mdi:fan" }, X26Attributes.direction: { "type": Platform.SELECT, "name": "Direction", "options": "directions", - "icon": "mdi:arrow-split-vertical", - }, - }, + "icon": "mdi:arrow-split-vertical" + } + } }, 0x34: { "name": "Sink Dishwasher", @@ -106,78 +110,81 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, + "device_class": BinarySensorDeviceClass.DOOR }, X34Attributes.rinse_aid: { "type": Platform.BINARY_SENSOR, "name": "Rinse Aid Shortage", "icon": "mdi:bottle-tonic", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, X34Attributes.salt: { "type": Platform.BINARY_SENSOR, "name": "Salt Shortage", "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, X34Attributes.humidity: { "type": Platform.SENSOR, "name": "Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, X34Attributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360", + "icon": "mdi:rotate-360" }, X34Attributes.status: { "type": Platform.SENSOR, "name": "Status", - "icon": "mdi:information", + "icon": "mdi:information" }, X34Attributes.storage_remaining: { "type": Platform.SENSOR, "name": "Storage Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.HOURS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, X34Attributes.temperature: { "type": Platform.SENSOR, "name": "Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, X34Attributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT + }, + X34Attributes.child_lock: { + "type": Platform.LOCK, + "name": "Child Lock" }, - X34Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, X34Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, X34Attributes.storage: { "type": Platform.SWITCH, "name": "Storage", - "icon": "mdi:repeat-variant", + "icon": "mdi:repeat-variant" }, X34Attributes.mode: { "type": Platform.SENSOR, "name": "Working Mode", - "icon": "mdi:dishwasher", + "icon": "mdi:dishwasher" }, X34Attributes.error_code: { "type": Platform.SENSOR, "name": "Error Code", - "icon": "mdi:alert-box", + "icon": "mdi:alert-box" }, X34Attributes.softwater: { "type": Platform.SENSOR, @@ -187,43 +194,47 @@ X34Attributes.bright: { "type": Platform.SENSOR, "name": "Bright Level", - "icon": "mdi:star-four-points", - }, - }, + "icon": "mdi:star-four-points" + } + } }, 0x40: { "name": "Integrated Ceiling Fan", "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + "fan": { + "type": Platform.FAN, + "icon": "mdi:fan", + "default": True + }, X40Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, X40Attributes.light: { "type": Platform.SWITCH, "name": "Light", - "icon": "mdi:lightbulb", + "icon": "mdi:lightbulb" }, X40Attributes.ventilation: { "type": Platform.SWITCH, "name": "Ventilation", - "icon": "mdi:air-filter", + "icon": "mdi:air-filter" }, X40Attributes.smelly_sensor: { "type": Platform.SWITCH, "name": "Smelly Sensor", - "icon": "mdi:scent", + "icon": "mdi:scent" }, X40Attributes.direction: { "type": Platform.SELECT, "name": "Direction", "options": "directions", - "icon": "mdi:arrow-split-vertical", - }, - }, + "icon": "mdi:arrow-split-vertical" + } + } }, 0xA1: { "name": "Dehumidifier", @@ -231,69 +242,72 @@ "humidifier": { "type": Platform.HUMIDIFIER, "icon": "mdi:air-humidifier", - "default": True, + "default": True + }, + A1Attributes.child_lock: { + "type": Platform.LOCK, + "name": "Child Lock" }, - A1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, A1Attributes.anion: { "type": Platform.SWITCH, "name": "Anion", - "icon": "mdi:vanish", + "icon": "mdi:vanish" }, A1Attributes.prompt_tone: { "type": Platform.SWITCH, "name": "Prompt Tone", - "icon": "mdi:bell", + "icon": "mdi:bell" }, A1Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, A1Attributes.swing: { "type": Platform.SWITCH, "name": "swing", - "icon": "mdi:pan-horizontal", + "icon": "mdi:pan-horizontal" }, A1Attributes.fan_speed: { "type": Platform.SELECT, "name": "Fan Speed", "options": "fan_speeds", - "icon": "mdi:fan", + "icon": "mdi:fan" }, A1Attributes.water_level_set: { "type": Platform.SELECT, "name": "Water Level Setting", "options": "water_level_sets", - "icon": "mdi:cup-water", + "icon": "mdi:cup-water" }, A1Attributes.current_humidity: { "type": Platform.SENSOR, "name": "Current Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, A1Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, A1Attributes.tank: { "type": Platform.SENSOR, "name": "Tank", "icon": "mdi:cup-water", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, A1Attributes.tank_full: { "type": Platform.BINARY_SENSOR, "name": "Tank status", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - }, + "device_class": BinarySensorDeviceClass.PROBLEM + } + } }, 0xAC: { "name": "Air Conditioner", @@ -301,143 +315,147 @@ "climate": { "type": Platform.CLIMATE, "icon": "mdi:air-conditioner", - "default": True, + "default": True + }, + "fresh_air": { + "type": Platform.FAN, + "icon": "mdi:fan", + "name": "Fresh Air" }, - "fresh_air": {"type": Platform.FAN, "icon": "mdi:fan", "name": "Fresh Air"}, ACAttributes.aux_heating: { "type": Platform.SWITCH, "name": "Aux Heating", - "icon": "mdi:heat-wave", + "icon": "mdi:heat-wave" }, ACAttributes.boost_mode: { "type": Platform.SWITCH, "name": "Boost Mode", - "icon": "mdi:turbine", + "icon": "mdi:turbine" }, ACAttributes.breezeless: { "type": Platform.SWITCH, "name": "Breezeless", - "icon": "mdi:tailwind", + "icon": "mdi:tailwind" }, ACAttributes.comfort_mode: { "type": Platform.SWITCH, "name": "Comfort Mode", - "icon": "mdi:alpha-c-circle", + "icon": "mdi:alpha-c-circle" }, ACAttributes.dry: { "type": Platform.SWITCH, "name": "Dry", - "icon": "mdi:air-filter", + "icon": "mdi:air-filter" }, ACAttributes.eco_mode: { "type": Platform.SWITCH, "name": "ECO Mode", - "icon": "mdi:leaf-circle", + "icon": "mdi:leaf-circle" }, ACAttributes.frost_protect: { "type": Platform.SWITCH, "name": "Frost Protect", - "icon": "mdi:snowflake-alert", + "icon": "mdi:snowflake-alert" }, ACAttributes.indirect_wind: { "type": Platform.SWITCH, "name": "Indirect Wind", - "icon": "mdi:tailwind", + "icon": "mdi:tailwind" }, ACAttributes.natural_wind: { "type": Platform.SWITCH, "name": "Natural Wind", - "icon": "mdi:tailwind", + "icon": "mdi:tailwind" }, ACAttributes.prompt_tone: { "type": Platform.SWITCH, "name": "Prompt Tone", - "icon": "mdi:bell", + "icon": "mdi:bell" }, ACAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, ACAttributes.screen_display: { "type": Platform.SWITCH, "name": "Screen Display", - "icon": "mdi:television-ambient-light", + "icon": "mdi:television-ambient-light" }, ACAttributes.screen_display_alternate: { "type": Platform.SWITCH, "name": "Screen Display Alternate", - "icon": "mdi:television-ambient-light", + "icon": "mdi:television-ambient-light" }, ACAttributes.sleep_mode: { "type": Platform.SWITCH, "name": "Sleep Mode", - "icon": "mdi:power-sleep", + "icon": "mdi:power-sleep" }, ACAttributes.smart_eye: { "type": Platform.SWITCH, "name": "Smart Eye", - "icon": "mdi:eye", + "icon": "mdi:eye" }, ACAttributes.swing_horizontal: { "type": Platform.SWITCH, "name": "Swing Horizontal", - "icon": "mdi:arrow-split-vertical", + "icon": "mdi:arrow-split-vertical" }, ACAttributes.swing_vertical: { "type": Platform.SWITCH, "name": "Swing Vertical", - "icon": "mdi:arrow-split-horizontal", + "icon": "mdi:arrow-split-horizontal" }, ACAttributes.full_dust: { "type": Platform.BINARY_SENSOR, "name": "Full of Dust", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, ACAttributes.indoor_humidity: { "type": Platform.SENSOR, "name": "Indoor Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, ACAttributes.indoor_temperature: { "type": Platform.SENSOR, "name": "Indoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, ACAttributes.outdoor_temperature: { "type": Platform.SENSOR, "name": "Outdoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, ACAttributes.total_energy_consumption: { "type": Platform.SENSOR, "name": "Total Energy Consumption", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, + "state_class": SensorStateClass.TOTAL_INCREASING }, ACAttributes.current_energy_consumption: { "type": Platform.SENSOR, "name": "Current Energy Consumption", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, + "state_class": SensorStateClass.TOTAL_INCREASING }, ACAttributes.realtime_power: { "type": Platform.SENSOR, "name": "Realtime Power", "device_class": SensorDeviceClass.POWER, "unit": UnitOfPower.WATT, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, + "state_class": SensorStateClass.MEASUREMENT + } + } }, 0xB0: { "name": "Microwave Oven", @@ -446,32 +464,32 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, + "device_class": BinarySensorDeviceClass.DOOR }, B0Attributes.tank_ejected: { "type": Platform.BINARY_SENSOR, "name": "Tank Ejected", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B0Attributes.water_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Water Change Reminder", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B0Attributes.water_shortage: { "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B0Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, B0Attributes.status: { "type": Platform.SENSOR, @@ -483,9 +501,9 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, + "state_class": SensorStateClass.MEASUREMENT + } + } }, 0xB1: { "name": "Electric Oven", @@ -494,32 +512,32 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, + "device_class": BinarySensorDeviceClass.DOOR }, B1Attributes.tank_ejected: { "type": Platform.BINARY_SENSOR, "name": "Tank ejected", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B1Attributes.water_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Water Change Reminder", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B1Attributes.water_shortage: { "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B1Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, B1Attributes.status: { "type": Platform.SENSOR, @@ -531,9 +549,9 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, + "state_class": SensorStateClass.MEASUREMENT + } + } }, 0xB3: { "name": "Dish Sterilizer", @@ -595,58 +613,58 @@ B3Attributes.top_compartment_status: { "type": Platform.SENSOR, "name": "Top Compartment Status", - "icon": "mdi:information", + "icon": "mdi:information" }, B3Attributes.top_compartment_temperature: { "type": Platform.SENSOR, "name": "Top Compartment Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, B3Attributes.top_compartment_remaining: { "type": Platform.SENSOR, "name": "Top Compartment Remaining", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, B3Attributes.middle_compartment_status: { "type": Platform.SENSOR, "name": "Middle Compartment Status", - "icon": "mdi:information", + "icon": "mdi:information" }, B3Attributes.middle_compartment_temperature: { "type": Platform.SENSOR, "name": "Middle Compartment Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, B3Attributes.middle_compartment_remaining: { "type": Platform.SENSOR, "name": "Middle Compartment Remaining", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, B3Attributes.bottom_compartment_status: { "type": Platform.SENSOR, "name": "Bottom Compartment Status", - "icon": "mdi:information", + "icon": "mdi:information" }, B3Attributes.bottom_compartment_temperature: { "type": Platform.SENSOR, "name": "Bottom Compartment Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, B3Attributes.bottom_compartment_remaining: { "type": Platform.SENSOR, "name": "Bottom Compartment Remaining", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, + "state_class": SensorStateClass.MEASUREMENT + } + } }, 0xB4: { "name": "Toaster", @@ -655,32 +673,32 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, + "device_class": BinarySensorDeviceClass.DOOR }, B4Attributes.tank_ejected: { "type": Platform.BINARY_SENSOR, "name": "Tank ejected", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B4Attributes.water_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Water Change Reminder", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B4Attributes.water_shortage: { "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B4Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, B4Attributes.status: { "type": Platform.SENSOR, @@ -692,43 +710,47 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, + "state_class": SensorStateClass.MEASUREMENT + } + } }, 0xB6: { "name": "Range Hood", "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + "fan": { + "type": Platform.FAN, + "icon": "mdi:fan", + "default": True + }, B6Attributes.light: { "type": Platform.SWITCH, "name": "Light", - "icon": "mdi:lightbulb", + "icon": "mdi:lightbulb" }, B6Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, B6Attributes.cleaning_reminder: { "type": Platform.BINARY_SENSOR, "name": "Cleaning Reminder", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B6Attributes.oilcup_full: { "type": Platform.BINARY_SENSOR, "name": "Oil-cup Full", "icon": "mdi:cup", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, B6Attributes.fan_level: { "type": Platform.SENSOR, "name": "Fan level", "icon": "mdi:fan", - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, - }, + } }, 0xBF: { "name": "Microwave Steam Oven", @@ -737,32 +759,32 @@ "type": Platform.BINARY_SENSOR, "name": "Tank ejected", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, BFAttributes.water_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Water Change Reminder", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, BFAttributes.door: { "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, + "device_class": BinarySensorDeviceClass.DOOR }, BFAttributes.water_shortage: { "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, BFAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, BFAttributes.status: { "type": Platform.SENSOR, @@ -774,9 +796,9 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, + "state_class": SensorStateClass.MEASUREMENT + } + } }, 0xC2: { "name": "Toilet", @@ -784,55 +806,58 @@ C2Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, C2Attributes.sensor_light: { "type": Platform.SWITCH, "name": "Sensor Light", - "icon": "mdi:lightbulb", + "icon": "mdi:lightbulb" }, C2Attributes.foam_shield: { "type": Platform.SWITCH, "name": "Foam Shield", "icon": "mdi:chart-bubble", }, - C2Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + C2Attributes.child_lock: { + "type": Platform.LOCK, + "name": "Child Lock" + }, C2Attributes.seat_status: { "type": Platform.BINARY_SENSOR, "name": "Seat Status", - "icon": "mdi:seat-legroom-normal", + "icon": "mdi:seat-legroom-normal" }, C2Attributes.lid_status: { "type": Platform.BINARY_SENSOR, "name": "Lid Status", - "icon": "mdi:toilet", + "icon": "mdi:toilet" }, C2Attributes.light_status: { "type": Platform.BINARY_SENSOR, "name": "Light Status", "icon": "mdi:lightbulb", - "device_class": BinarySensorDeviceClass.LIGHT, + "device_class": BinarySensorDeviceClass.LIGHT }, C2Attributes.water_temperature: { "type": Platform.SENSOR, "name": "Water Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, C2Attributes.seat_temperature: { "type": Platform.SENSOR, "name": "Seat Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, C2Attributes.filter_life: { "type": Platform.SENSOR, "name": "Filter Life", "icon": "mdi:toilet", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, C2Attributes.dry_level: { "type": Platform.NUMBER, @@ -840,7 +865,7 @@ "icon": "mdi:fire", "max": "max_dry_level", "min": 0, - "step": 1, + "step": 1 }, C2Attributes.water_temp_level: { "type": Platform.NUMBER, @@ -848,7 +873,7 @@ "icon": "mdi:fire", "max": "max_water_temp_level", "min": 0, - "step": 1, + "step": 1 }, C2Attributes.seat_temp_level: { "type": Platform.NUMBER, @@ -856,9 +881,9 @@ "icon": "mdi:fire", "max": "max_seat_temp_level", "min": 0, - "step": 1, - }, - }, + "step": 1 + } + } }, 0xC3: { "name": "Heat Pump Wi-Fi Controller", @@ -868,70 +893,70 @@ "icon": "mdi:air-conditioner", "name": "Zone1 Thermostat", "zone": 0, - "default": True, + "default": True }, "climate_zone2": { "type": Platform.CLIMATE, "icon": "mdi:air-conditioner", "name": "Zone2 Thermostat", "zone": 1, - "default": True, + "default": True }, "water_heater": { "type": Platform.WATER_HEATER, "icon": "mdi:heat-pump", "name": "Domestic hot water", - "default": True, + "default": True }, C3Attributes.disinfect: { "type": Platform.SWITCH, "name": "Disinfect", - "icon": "mdi:water-plus-outline", + "icon": "mdi:water-plus-outline" }, C3Attributes.dhw_power: { "type": Platform.SWITCH, "name": "DHW Power", - "icon": "mdi:power", + "icon": "mdi:power" }, C3Attributes.eco_mode: { "type": Platform.SWITCH, "name": "ECO Mode", - "icon": "mdi:leaf-circle", + "icon": "mdi:leaf-circle" }, C3Attributes.fast_dhw: { "type": Platform.SWITCH, "name": "Fast DHW", - "icon": "mdi:rotate-orbit", + "icon": "mdi:rotate-orbit" }, C3Attributes.silent_mode: { "type": Platform.SWITCH, "name": "Silent Mode", - "icon": "mdi:fan-remove", + "icon": "mdi:fan-remove" }, C3Attributes.tbh: { "type": Platform.SWITCH, "name": "TBH", - "icon": "mdi:water-boiler", + "icon": "mdi:water-boiler" }, C3Attributes.zone1_curve: { "type": Platform.SWITCH, "name": "Zone1 Curve", - "icon": "mdi:chart-bell-curve-cumulative", + "icon": "mdi:chart-bell-curve-cumulative" }, C3Attributes.zone2_curve: { "type": Platform.SWITCH, "name": "Zone2 Curve", - "icon": "mdi:chart-bell-curve-cumulative", + "icon": "mdi:chart-bell-curve-cumulative" }, C3Attributes.zone1_power: { "type": Platform.SWITCH, "name": "Zone1 Power", - "icon": "mdi:power", + "icon": "mdi:power" }, C3Attributes.zone2_power: { "type": Platform.SWITCH, "name": "Zone2 Power", - "icon": "mdi:power", + "icon": "mdi:power" }, C3Attributes.zone1_water_temp_mode: { "type": Platform.BINARY_SENSOR, @@ -960,14 +985,14 @@ C3Attributes.error_code: { "type": Platform.SENSOR, "name": "Error Code", - "icon": "mdi:alpha-e-circle", + "icon": "mdi:alpha-e-circle" }, C3Attributes.tank_actual_temperature: { "type": Platform.SENSOR, "name": "Tank Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, C3Attributes.status_dhw: { "type": Platform.BINARY_SENSOR, @@ -998,23 +1023,23 @@ "name": "Total energy consumption", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, + "state_class": SensorStateClass.TOTAL_INCREASING }, C3Attributes.total_produced_energy: { "type": Platform.SENSOR, "name": "Total produced energy", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, + "state_class": SensorStateClass.TOTAL_INCREASING }, C3Attributes.outdoor_temperature: { "type": Platform.SENSOR, "name": "Outdoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, + "state_class": SensorStateClass.MEASUREMENT + } + } }, 0xCA: { "name": "Refrigerator", @@ -1023,112 +1048,112 @@ "type": Platform.BINARY_SENSOR, "name": "Bar Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, + "device_class": BinarySensorDeviceClass.DOOR }, CAAttributes.bar_door_overtime: { "type": Platform.BINARY_SENSOR, "name": "Bar Door Overtime", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, CAAttributes.flex_zone_door: { "type": Platform.BINARY_SENSOR, "name": "Flex Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, + "device_class": BinarySensorDeviceClass.DOOR }, CAAttributes.flex_zone_door_overtime: { "type": Platform.BINARY_SENSOR, "name": "Flex Zone Door", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, CAAttributes.freezer_door: { "type": Platform.BINARY_SENSOR, "name": "Freezer Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, + "device_class": BinarySensorDeviceClass.DOOR }, CAAttributes.freezer_door_overtime: { "type": Platform.BINARY_SENSOR, "name": "Freezer Door Overtime", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, CAAttributes.refrigerator_door: { "type": Platform.BINARY_SENSOR, "name": "Refrigerator Door", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, CAAttributes.refrigerator_door_overtime: { "type": Platform.BINARY_SENSOR, "name": "Refrigerator Door Overtime", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, CAAttributes.flex_zone_actual_temp: { "type": Platform.SENSOR, "name": "Flex Zone Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CAAttributes.flex_zone_setting_temp: { "type": Platform.SENSOR, "name": "Flex Zone Setting Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CAAttributes.freezer_actual_temp: { "type": Platform.SENSOR, "name": "Freezer Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CAAttributes.freezer_setting_temp: { "type": Platform.SENSOR, "name": "Freezer Setting Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CAAttributes.energy_consumption: { "type": Platform.SENSOR, "name": "Energy Consumption", "device_class": SensorDeviceClass.ENERGY, "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, + "state_class": SensorStateClass.TOTAL_INCREASING }, CAAttributes.refrigerator_actual_temp: { "type": Platform.SENSOR, "name": "Refrigerator Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CAAttributes.refrigerator_setting_temp: { "type": Platform.SENSOR, "name": "Refrigerator Setting Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CAAttributes.right_flex_zone_actual_temp: { "type": Platform.SENSOR, "name": "Right Flex Zone Actual Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CAAttributes.right_flex_zone_setting_temp: { "type": Platform.SENSOR, "name": "Right Flex Zone Setting Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, }, }, @@ -1138,46 +1163,46 @@ "climate": { "type": Platform.CLIMATE, "icon": "hass:air-conditioner", - "default": True, + "default": True }, CCAttributes.aux_heating: { "type": Platform.SWITCH, "name": "Aux Heating", - "icon": "mdi:heat-wave", + "icon": "mdi:heat-wave" }, CCAttributes.eco_mode: { "type": Platform.SWITCH, "name": "ECO Mode", - "icon": "mdi:leaf-circle", + "icon": "mdi:leaf-circle" }, CCAttributes.night_light: { "type": Platform.SWITCH, "name": "Night Light", - "icon": "mdi:lightbulb", + "icon": "mdi:lightbulb" }, CCAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, CCAttributes.sleep_mode: { "type": Platform.SWITCH, "name": "Sleep Mode", - "icon": "mdi:power-sleep", + "icon": "mdi:power-sleep" }, CCAttributes.swing: { "type": Platform.SWITCH, "name": "Swing", - "icon": "mdi:arrow-split-horizontal", + "icon": "mdi:arrow-split-horizontal" }, CCAttributes.indoor_temperature: { "type": Platform.SENSOR, "name": "Indoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, - }, + } }, 0xCD: { "name": "Heat Pump Water Heater", @@ -1185,125 +1210,132 @@ "water_heater": { "type": Platform.WATER_HEATER, "icon": "mdi:heat-pump", - "default": True, + "default": True }, CDAttributes.compressor_status: { "type": Platform.BINARY_SENSOR, "name": "Compressor Status", "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, CDAttributes.compressor_temperature: { "type": Platform.SENSOR, "name": "Compressor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CDAttributes.condenser_temperature: { "type": Platform.SENSOR, "name": "Condenser Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CDAttributes.outdoor_temperature: { "type": Platform.SENSOR, "name": "Outdoor Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CDAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", - }, - }, + "icon": "mdi:power" + } + } }, 0xCE: { "name": "Fresh Air Appliance", "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + "fan": { + "type": Platform.FAN, + "icon": "mdi:fan", + "default": True + }, CEAttributes.filter_cleaning_reminder: { "type": Platform.BINARY_SENSOR, "name": "Filter Cleaning Reminder", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, CEAttributes.filter_change_reminder: { "type": Platform.BINARY_SENSOR, "name": "Filter Change Reminder", "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, CEAttributes.current_humidity: { "type": Platform.SENSOR, "name": "Current Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CEAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CEAttributes.co2: { "type": Platform.SENSOR, "name": "Carbon Dioxide", "device_class": SensorDeviceClass.CO2, "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CEAttributes.hcho: { "type": Platform.SENSOR, "name": "Methanal", "icon": "mdi:molecule", "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, CEAttributes.pm25: { "type": Platform.SENSOR, "name": "PM 2.5", "device_class": SensorDeviceClass.PM25, "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT + }, + CEAttributes.child_lock: { + "type": Platform.LOCK, + "name": "Child Lock" }, - CEAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, CEAttributes.aux_heating: { "type": Platform.SWITCH, "name": "Aux Heating", - "icon": "mdi:heat-wave", + "icon": "mdi:heat-wave" }, CEAttributes.eco_mode: { "type": Platform.SWITCH, "name": "ECO Mode", - "icon": "mdi:leaf-circle", + "icon": "mdi:leaf-circle" }, CEAttributes.link_to_ac: { "type": Platform.SWITCH, "name": "Link to AC", - "icon": "mdi:link", + "icon": "mdi:link" }, CEAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, CEAttributes.powerful_purify: { "type": Platform.SWITCH, "name": "Powerful Purification", - "icon": "mdi:turbine", + "icon": "mdi:turbine" }, CEAttributes.sleep_mode: { "type": Platform.SWITCH, "name": "Sleep Mode", - "icon": "mdi:power-sleep", + "icon": "mdi:power-sleep" }, - }, + } }, 0xCF: { "name": "Heat Pump", @@ -1311,26 +1343,26 @@ "climate": { "type": Platform.CLIMATE, "icon": "hass:air-conditioner", - "default": True, + "default": True }, CFAttributes.aux_heating: { "type": Platform.SWITCH, "name": "Aux Heating", - "icon": "mdi:heat-wave", + "icon": "mdi:heat-wave" }, CFAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, CFAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, - }, + } }, 0xDA: { "name": "Top Load Washer", @@ -1340,90 +1372,90 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, DAAttributes.wash_time: { "type": Platform.SENSOR, "name": "wash time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, DAAttributes.soak_time: { "type": Platform.SENSOR, "name": "soak time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, DAAttributes.dehydration_time: { "type": Platform.SENSOR, "name": "dehydration time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, DAAttributes.dehydration_speed: { "type": Platform.SENSOR, "name": "dehydration speed", - "icon": "mdi:speedometer", + "icon": "mdi:speedometer" }, DAAttributes.error_code: { "type": Platform.SENSOR, "name": "error code", - "icon": "mdi:washing-machine-alert", + "icon": "mdi:washing-machine-alert" }, DAAttributes.rinse_count: { "type": Platform.SENSOR, "name": "rinse count", - "icon": "mdi:water-sync", + "icon": "mdi:water-sync" }, DAAttributes.rinse_level: { "type": Platform.SENSOR, "name": "rinse level", - "icon": "mdi:hydraulic-oil-level", + "icon": "mdi:hydraulic-oil-level" }, DAAttributes.wash_level: { "type": Platform.SENSOR, "name": "rinse count", - "icon": "mdi:hydraulic-oil-level", + "icon": "mdi:hydraulic-oil-level" }, DAAttributes.wash_strength: { "type": Platform.SENSOR, "name": "wash strength", - "icon": "mdi:network-strength-4-cog", + "icon": "mdi:network-strength-4-cog" }, DAAttributes.softener: { "type": Platform.SENSOR, "name": "softener", - "icon": "mdi:tshirt-crew", + "icon": "mdi:tshirt-crew" }, DAAttributes.detergent: { "type": Platform.SENSOR, "name": "detergent", - "icon": "mdi:spray-bottle", + "icon": "mdi:spray-bottle" }, DAAttributes.program: { "type": Platform.SENSOR, "name": "Program", - "icon": "mdi:progress-wrench", + "icon": "mdi:progress-wrench" }, DAAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360", + "icon": "mdi:rotate-360" }, DAAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, DAAttributes.start: { "type": Platform.SWITCH, "name": "Start", - "icon": "mdi:motion-play-outline", + "icon": "mdi:motion-play-outline" }, - }, + } }, 0xDB: { "name": "Front Load Washer", @@ -1433,24 +1465,24 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, DBAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360", + "icon": "mdi:rotate-360" }, DBAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, DBAttributes.start: { "type": Platform.SWITCH, "name": "Start", - "icon": "mdi:motion-play-outline", + "icon": "mdi:motion-play-outline" }, - }, + } }, 0xDC: { "name": "Clothes Dryer", @@ -1460,24 +1492,24 @@ "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, DCAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360", + "icon": "mdi:rotate-360" }, DCAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, DCAttributes.start: { "type": Platform.SWITCH, "name": "Start", - "icon": "mdi:motion-play-outline", + "icon": "mdi:motion-play-outline" }, - }, + } }, 0xE1: { "name": "Dishwasher", @@ -1486,78 +1518,81 @@ "type": Platform.BINARY_SENSOR, "name": "Door", "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, + "device_class": BinarySensorDeviceClass.DOOR }, E1Attributes.rinse_aid: { "type": Platform.BINARY_SENSOR, "name": "Rinse Aid Shortage", "icon": "mdi:bottle-tonic", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, E1Attributes.salt: { "type": Platform.BINARY_SENSOR, "name": "Salt Shortage", "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, E1Attributes.humidity: { "type": Platform.SENSOR, "name": "Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E1Attributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360", + "icon": "mdi:rotate-360" }, E1Attributes.status: { "type": Platform.SENSOR, "name": "Status", - "icon": "mdi:information", + "icon": "mdi:information" }, E1Attributes.storage_remaining: { "type": Platform.SENSOR, "name": "Storage Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.HOURS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E1Attributes.temperature: { "type": Platform.SENSOR, "name": "Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E1Attributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT + }, + E1Attributes.child_lock: { + "type": Platform.LOCK, + "name": "Child Lock" }, - E1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, E1Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, E1Attributes.storage: { "type": Platform.SWITCH, "name": "Storage", - "icon": "mdi:repeat-variant", + "icon": "mdi:repeat-variant" }, E1Attributes.mode: { "type": Platform.SENSOR, "name": "Working Mode", - "icon": "mdi:dishwasher", + "icon": "mdi:dishwasher" }, E1Attributes.error_code: { "type": Platform.SENSOR, "name": "Error Code", - "icon": "mdi:alert-box", + "icon": "mdi:alert-box" }, E1Attributes.softwater: { "type": Platform.SENSOR, @@ -1567,9 +1602,9 @@ E1Attributes.bright: { "type": Platform.SENSOR, "name": "Bright Level", - "icon": "mdi:star-four-points", - }, - }, + "icon": "mdi:star-four-points" + } + } }, 0xE2: { "name": "Electric Water Heater", @@ -1577,70 +1612,70 @@ "water_heater": { "type": Platform.WATER_HEATER, "icon": "mdi:meter-electric-outline", - "default": True, + "default": True }, E2Attributes.heating: { "type": Platform.BINARY_SENSOR, "name": "Heating", "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, E2Attributes.keep_warm: { "type": Platform.BINARY_SENSOR, "name": "Keep Warm", "icon": "mdi:menu", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, E2Attributes.protection: { "type": Platform.BINARY_SENSOR, "name": "Protection", "icon": "mdi:shield-check", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, E2Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E2Attributes.heating_time_remaining: { "type": Platform.SENSOR, "name": "Heating Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E2Attributes.heating_power: { "type": Platform.SENSOR, "name": "Heating Power", "device_class": SensorDeviceClass.POWER, "unit": UnitOfPower.WATT, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E2Attributes.water_consumption: { "type": Platform.SENSOR, "name": "Water Consumption", "icon": "mdi:water", "unit": UnitOfVolume.LITERS, - "state_class": SensorStateClass.TOTAL_INCREASING, + "state_class": SensorStateClass.TOTAL_INCREASING }, E2Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, E2Attributes.variable_heating: { "type": Platform.SWITCH, "name": "Variable Heating", - "icon": "mdi:waves", + "icon": "mdi:waves" }, E2Attributes.whole_tank_heating: { "type": Platform.SWITCH, "name": "Whole Tank Heating", - "icon": "mdi:restore", - }, - }, + "icon": "mdi:restore" + } + } }, 0xE3: { "name": "Gas Water Heater", @@ -1648,48 +1683,48 @@ "water_heater": { "type": Platform.WATER_HEATER, "icon": "mdi:meter-gas", - "default": True, + "default": True }, E3Attributes.burning_state: { "type": Platform.BINARY_SENSOR, "name": "Burning State", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, E3Attributes.protection: { "type": Platform.BINARY_SENSOR, "name": "Protection", "icon": "mdi:shield-check", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, E3Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E3Attributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, E3Attributes.smart_volume: { "type": Platform.SWITCH, "name": "Smart Volume", - "icon": "mdi:recycle", + "icon": "mdi:recycle" }, E3Attributes.zero_cold_water: { "type": Platform.SWITCH, "name": "Zero Cold Water", - "icon": "mdi:restore", + "icon": "mdi:restore" }, E3Attributes.zero_cold_pulse: { "type": Platform.SWITCH, "name": "Zero Cold Water (Pulse)", - "icon": "mdi:restore-alert", + "icon": "mdi:restore-alert" }, - }, + } }, 0xE6: { "name": "Gas Boilers", @@ -1699,52 +1734,52 @@ "icon": "mdi:meter-gas", "name": "Heating", "use": 0, - "default": True, + "default": True }, "water_heater_bathing": { "type": Platform.WATER_HEATER, "icon": "mdi:meter-gas", "name": "Bathing", "use": 1, - "default": True, + "default": True }, E6Attributes.heating_working: { "type": Platform.BINARY_SENSOR, "name": "Heating Working Status", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, E6Attributes.bathing_working: { "type": Platform.BINARY_SENSOR, "name": "Bathing Working Status", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, E6Attributes.heating_leaving_temperature: { "type": Platform.SENSOR, "name": "Heating Leaving Water Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E6Attributes.bathing_leaving_temperature: { "type": Platform.SENSOR, "name": "Bathing Leaving Water Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E6Attributes.main_power: { "type": Platform.SWITCH, "name": "Main Power", - "icon": "mdi:power", + "icon": "mdi:power" }, E6Attributes.heating_power: { "type": Platform.SWITCH, "name": "Heating Power", - "icon": "mdi:heating-coil", - }, - }, + "icon": "mdi:heating-coil" + } + } }, 0xE8: { "name": "Electric Slow Cooker", @@ -1758,49 +1793,50 @@ "type": Platform.BINARY_SENSOR, "name": "Water Shortage", "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM, + "device_class": BinarySensorDeviceClass.PROBLEM }, E8Attributes.status: { "type": Platform.SENSOR, "name": "Status", - "icon": "mdi:information", + "icon": "mdi:information" }, E8Attributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E8Attributes.keep_warm_remaining: { "type": Platform.SENSOR, "name": "Keep Warm Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E8Attributes.working_time: { "type": Platform.SENSOR, "name": "Working Time", "icon": "mdi:progress-clock", "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E8Attributes.target_temperature: { "type": Platform.SENSOR, "name": "Target Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, E8Attributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, - }, + + } }, 0xEA: { "name": "Electric Rice Cooker", @@ -1809,53 +1845,53 @@ "type": Platform.BINARY_SENSOR, "name": "Cooking", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, EAAttributes.keep_warm: { "type": Platform.BINARY_SENSOR, "name": "Keep Warm", "icon": "mdi:menu", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, EAAttributes.bottom_temperature: { "type": Platform.SENSOR, "name": "Bottom Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EAAttributes.keep_warm_time: { "type": Platform.SENSOR, "name": "Keep Warm Time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EAAttributes.mode: { "type": Platform.SENSOR, "name": "Mode", - "icon": "mdi:orbit", + "icon": "mdi:orbit" }, EAAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360", + "icon": "mdi:rotate-360" }, EAAttributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EAAttributes.top_temperature: { "type": Platform.SENSOR, "name": "Top Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, - }, + } }, 0xEC: { "name": "Electric Pressure Cooker", @@ -1864,162 +1900,172 @@ "type": Platform.BINARY_SENSOR, "name": "Cooking", "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, ECAttributes.with_pressure: { "type": Platform.BINARY_SENSOR, "name": "With Pressure", "icon": "mdi:information", - "device_class": BinarySensorDeviceClass.RUNNING, + "device_class": BinarySensorDeviceClass.RUNNING }, ECAttributes.bottom_temperature: { "type": Platform.SENSOR, "name": "Bottom Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, ECAttributes.keep_warm_time: { "type": Platform.SENSOR, "name": "Keep Warm Time", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, ECAttributes.mode: { "type": Platform.SENSOR, "name": "Mode", - "icon": "mdi:orbit", + "icon": "mdi:orbit" }, ECAttributes.progress: { "type": Platform.SENSOR, "name": "Progress", - "icon": "mdi:rotate-360", + "icon": "mdi:rotate-360" }, ECAttributes.time_remaining: { "type": Platform.SENSOR, "name": "Time Remaining", "icon": "mdi:progress-clock", "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, ECAttributes.top_temperature: { "type": Platform.SENSOR, "name": "Top Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, - }, + } }, 0xED: { "name": "Water Drinking Appliance", "entities": { - EDAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + EDAttributes.child_lock: { + "type": Platform.LOCK, + "name": "Child Lock" + }, EDAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, EDAttributes.filter1: { "type": Platform.SENSOR, "name": "Filter1 Available Days", "icon": "mdi:air-filter", "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EDAttributes.filter2: { "type": Platform.SENSOR, "name": "Filter2 Available Days", "icon": "mdi:air-filter", "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EDAttributes.filter3: { "type": Platform.SENSOR, "name": "Filter3 Available Days", "icon": "mdi:air-filter", "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EDAttributes.life1: { "type": Platform.SENSOR, "name": "Filter1 Life Level", "icon": "mdi:percent", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EDAttributes.life2: { "type": Platform.SENSOR, "name": "Filter2 Life Level", "icon": "mdi:percent", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EDAttributes.life3: { "type": Platform.SENSOR, "name": "Filter3 Life Level", "icon": "mdi:percent", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EDAttributes.in_tds: { "type": Platform.SENSOR, "name": "In TDS", "icon": "mdi:water", "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EDAttributes.out_tds: { "type": Platform.SENSOR, "name": "Out TDS", "icon": "mdi:water-plus", "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, EDAttributes.water_consumption: { "type": Platform.SENSOR, "name": "Water Consumption", "icon": "mdi:water-pump", "unit": UnitOfVolume.LITERS, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - }, + "state_class": SensorStateClass.TOTAL_INCREASING + } + } }, 0xFA: { "name": "Fan", "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + "fan": { + "type": Platform.FAN, + "icon": "mdi:fan", + "default": True + }, FAAttributes.oscillation_mode: { "type": Platform.SELECT, "name": "Oscillation Mode", "options": "oscillation_modes", - "icon": "mdi:swap-horizontal-variant", + "icon": "mdi:swap-horizontal-variant" }, FAAttributes.oscillation_angle: { "type": Platform.SELECT, "name": "Oscillation Angle", "options": "oscillation_angles", - "icon": "mdi:pan-horizontal", + "icon": "mdi:pan-horizontal" }, FAAttributes.tilting_angle: { "type": Platform.SELECT, "name": "Tilting Angle", "options": "tilting_angles", - "icon": "mdi:pan-vertical", + "icon": "mdi:pan-vertical" + }, + FAAttributes.child_lock: { + "type": Platform.LOCK, + "name": "Child Lock" }, - FAAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, FAAttributes.oscillate: { "type": Platform.SWITCH, "name": "Oscillate", - "icon": "mdi:swap-horizontal-bold", + "icon": "mdi:swap-horizontal-bold" }, FAAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, - }, + } }, 0xFB: { "name": "Electric Heater", @@ -2027,115 +2073,121 @@ "climate": { "type": Platform.CLIMATE, "icon": "mdi:air-conditioner", - "default": True, + "default": True + }, + FBAttributes.child_lock: { + "type": Platform.LOCK, + "name": "Child Lock" }, - FBAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, FBAttributes.heating_level: { "type": Platform.NUMBER, "name": "Heating Level", "icon": "mdi:fire", "max": 10, "min": 1, - "step": 1, + "step": 1 }, FBAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, FBAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, - }, + } }, 0xFC: { "name": "Air Purifier", "entities": { - FCAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + FCAttributes.child_lock: { + "type": Platform.LOCK, + "name": "Child Lock" + }, FCAttributes.anion: { "type": Platform.SWITCH, "name": "Anion", - "icon": "mdi:vanish", + "icon": "mdi:vanish" }, FCAttributes.prompt_tone: { "type": Platform.SWITCH, "name": "Prompt Tone", - "icon": "mdi:bell", + "icon": "mdi:bell" }, FCAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, FCAttributes.standby: { "type": Platform.SWITCH, "name": "Standby", - "icon": "mdi:smoke-detector-variant", + "icon": "mdi:smoke-detector-variant" }, FCAttributes.detect_mode: { "type": Platform.SELECT, "name": "Detect Mode", "options": "detect_modes", - "icon": "mdi:smoke-detector-variant", + "icon": "mdi:smoke-detector-variant" }, FCAttributes.mode: { "type": Platform.SELECT, "name": "Mode", "options": "modes", - "icon": "mdi:rotate-360", + "icon": "mdi:rotate-360" }, FCAttributes.fan_speed: { "type": Platform.SELECT, "name": "Fan Speed", "options": "fan_speeds", - "icon": "mdi:fan", + "icon": "mdi:fan" }, FCAttributes.screen_display: { "type": Platform.SELECT, "name": "Screen Display", "options": "screen_displays", - "icon": "mdi:television-ambient-light", + "icon": "mdi:television-ambient-light" }, FCAttributes.pm25: { "type": Platform.SENSOR, "name": "PM 2.5", "device_class": SensorDeviceClass.PM25, "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, FCAttributes.tvoc: { "type": Platform.SENSOR, "name": "TVOC", "icon": "mdi:heat-wave", "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, FCAttributes.hcho: { "type": Platform.SENSOR, "name": "Methanal", "icon": "mdi:molecule", "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, FCAttributes.filter1_life: { "type": Platform.SENSOR, "name": "Filter1 Life Level", "icon": "mdi:air-filter", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, FCAttributes.filter2_life: { "type": Platform.SENSOR, "name": "Filter2 Life Level", "icon": "mdi:air-filter", "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, + "state_class": SensorStateClass.MEASUREMENT + } + } }, 0xFD: { "name": "Humidifier", @@ -2143,49 +2195,49 @@ Platform.HUMIDIFIER: { "type": Platform.HUMIDIFIER, "icon": "mdi:air-humidifier", - "default": True, + "default": True }, FDAttributes.disinfect: { "type": Platform.SWITCH, "name": "Disinfect", - "icon": "mdi:water-plus-outline", + "icon": "mdi:water-plus-outline" }, FDAttributes.prompt_tone: { "type": Platform.SWITCH, "name": "Prompt Tone", - "icon": "mdi:bell", + "icon": "mdi:bell" }, FDAttributes.power: { "type": Platform.SWITCH, "name": "Power", - "icon": "mdi:power", + "icon": "mdi:power" }, FDAttributes.fan_speed: { "type": Platform.SELECT, "name": "Fan Speed", "options": "fan_speeds", - "icon": "mdi:fan", + "icon": "mdi:fan" }, FDAttributes.screen_display: { "type": Platform.SELECT, "name": "Screen Display", "options": "screen_displays", - "icon": "mdi:television-ambient-light", + "icon": "mdi:television-ambient-light" }, FDAttributes.current_humidity: { "type": Platform.SENSOR, "name": "Current Humidity", "device_class": SensorDeviceClass.HUMIDITY, "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, + "state_class": SensorStateClass.MEASUREMENT }, FDAttributes.current_temperature: { "type": Platform.SENSOR, "name": "Current Temperature", "device_class": SensorDeviceClass.TEMPERATURE, "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, + "state_class": SensorStateClass.MEASUREMENT + } + } }, } diff --git a/custom_components/midea_ac_lan/midea_entity.py b/custom_components/midea_ac_lan/midea_entity.py old mode 100755 new mode 100644 index 3293aecb..d418b928 --- a/custom_components/midea_ac_lan/midea_entity.py +++ b/custom_components/midea_ac_lan/midea_entity.py @@ -1,10 +1,8 @@ -import logging - from homeassistant.helpers.entity import Entity - from .const import DOMAIN from .midea_devices import MIDEA_DEVICES +import logging _LOGGER = logging.getLogger(__name__) @@ -18,7 +16,6 @@ def __init__(self, device, entity_key: str): self.entity_id = self._unique_id self._device_name = self._device.name - @property def device(self): return self._device @@ -28,10 +25,10 @@ def device_info(self): return { "manufacturer": "Midea", "model": f"{MIDEA_DEVICES[self._device.device_type]['name']} " - f"{self._device.model}" - f" ({self._device.subtype})", + f"{self._device.model}" + f" ({self._device.subtype})", "identifiers": {(DOMAIN, self._device.device_id)}, - "name": self._device_name, + "name": self._device_name } @property @@ -44,11 +41,8 @@ def should_poll(self): @property def name(self): - return ( - f"{self._device_name} {self._config.get('name')}" - if "name" in self._config + return f"{self._device_name} {self._config.get('name')}" if "name" in self._config \ else self._device_name - ) @property def available(self): @@ -63,6 +57,4 @@ def update_state(self, status): try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) + _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") diff --git a/custom_components/midea_ac_lan/number.py b/custom_components/midea_ac_lan/number.py old mode 100755 new mode 100644 index dbe8dd19..94f5032e --- a/custom_components/midea_ac_lan/number.py +++ b/custom_components/midea_ac_lan/number.py @@ -1,15 +1,23 @@ -from homeassistant.components.number import NumberEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +from .midea_devices import MIDEA_DEVICES +from homeassistant.components.number import NumberEntity +from homeassistant.const import ( + Platform, + CONF_DEVICE_ID, + CONF_SWITCHES +) +from .const import ( + DOMAIN, + DEVICES, +) async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) + extra_switches = config_entry.options.get( + CONF_SWITCHES, [] + ) numbers = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.NUMBER and entity_key in extra_switches: @@ -27,39 +35,24 @@ def __init__(self, device, entity_key: str): @property def native_min_value(self): - return ( - self._min_value - if isinstance(self._min_value, int) - else ( - self._device.get_attribute(attr=self._min_value) - if self._device.get_attribute(attr=self._min_value) - else getattr(self._device, self._min_value) - ) - ) + return self._min_value if (type(self._min_value) is int) else \ + self._device.get_attribute(attr=self._min_value) \ + if self._device.get_attribute(attr=self._min_value) else \ + getattr(self._device, self._min_value) @property def native_max_value(self): - return ( - self._max_value - if isinstance(self._max_value, int) - else ( - self._device.get_attribute(attr=self._max_value) - if self._device.get_attribute(attr=self._max_value) - else getattr(self._device, self._max_value) - ) - ) + return self._max_value if (type(self._max_value) is int) else \ + self._device.get_attribute(attr=self._max_value) \ + if self._device.get_attribute(attr=self._max_value) else \ + getattr(self._device, self._max_value) @property def native_step(self): - return ( - self._step_value - if isinstance(self._step_value, int) - else ( - self._device.get_attribute(attr=self._step_value) - if self._device.get_attribute(attr=self._step_value) - else getattr(self._device, self._step_value) - ) - ) + return self._step_value if (type(self._step_value) is int) else \ + self._device.get_attribute(attr=self._step_value) \ + if self._device.get_attribute(attr=self._step_value) else \ + getattr(self._device, self._step_value) @property def native_value(self): diff --git a/custom_components/midea_ac_lan/select.py b/custom_components/midea_ac_lan/select.py old mode 100755 new mode 100644 index 67cf1aec..3e44b022 --- a/custom_components/midea_ac_lan/select.py +++ b/custom_components/midea_ac_lan/select.py @@ -1,15 +1,23 @@ -from homeassistant.components.select import SelectEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +from .midea_devices import MIDEA_DEVICES +from homeassistant.components.select import SelectEntity +from homeassistant.const import ( + Platform, + CONF_DEVICE_ID, + CONF_SWITCHES +) +from .const import ( + DOMAIN, + DEVICES, +) async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) + extra_switches = config_entry.options.get( + CONF_SWITCHES, [] + ) selects = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.SELECT and entity_key in extra_switches: diff --git a/custom_components/midea_ac_lan/sensor.py b/custom_components/midea_ac_lan/sensor.py old mode 100755 new mode 100644 index 78221898..956bcb90 --- a/custom_components/midea_ac_lan/sensor.py +++ b/custom_components/midea_ac_lan/sensor.py @@ -1,15 +1,23 @@ -from homeassistant.components.sensor import SensorEntity -from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +from .midea_devices import MIDEA_DEVICES +from homeassistant.components.sensor import SensorEntity +from homeassistant.const import ( + Platform, + CONF_DEVICE_ID, + CONF_SENSORS +) +from .const import ( + DOMAIN, + DEVICES +) async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_sensors = config_entry.options.get(CONF_SENSORS, []) + extra_sensors = config_entry.options.get( + CONF_SENSORS, [] + ) sensors = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.SENSOR and entity_key in extra_sensors: diff --git a/custom_components/midea_ac_lan/services.yaml b/custom_components/midea_ac_lan/services.yaml old mode 100755 new mode 100644 index 49103179..f5e06d7a --- a/custom_components/midea_ac_lan/services.yaml +++ b/custom_components/midea_ac_lan/services.yaml @@ -1,4 +1,3 @@ ---- set_attribute: fields: device_id: @@ -15,4 +14,4 @@ send_command: cmd_type: example: 2 cmd_body: - example: "B0FF01370E0000A500" + example: "B0FF01370E0000A500" \ No newline at end of file diff --git a/custom_components/midea_ac_lan/switch.py b/custom_components/midea_ac_lan/switch.py old mode 100755 new mode 100644 index c6a24cff..6c91cadd --- a/custom_components/midea_ac_lan/switch.py +++ b/custom_components/midea_ac_lan/switch.py @@ -1,15 +1,23 @@ -from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform -from homeassistant.helpers.entity import ToggleEntity - -from .const import DEVICES, DOMAIN -from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +from .midea_devices import MIDEA_DEVICES +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.const import ( + Platform, + CONF_DEVICE_ID, + CONF_SWITCHES +) +from .const import ( + DOMAIN, + DEVICES, +) async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) + extra_switches = config_entry.options.get( + CONF_SWITCHES, [] + ) switches = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): if config["type"] == Platform.SWITCH and entity_key in extra_switches: diff --git a/custom_components/midea_ac_lan/water_heater.py b/custom_components/midea_ac_lan/water_heater.py old mode 100755 new mode 100644 index 20d3959c..83fb04db --- a/custom_components/midea_ac_lan/water_heater.py +++ b/custom_components/midea_ac_lan/water_heater.py @@ -1,29 +1,31 @@ import functools as ft -import logging from homeassistant.components.water_heater import ( WaterHeaterEntity, WaterHeaterEntityFeature, ) from homeassistant.const import ( + Platform, + UnitOfTemperature, + PRECISION_WHOLE, + PRECISION_HALVES, ATTR_TEMPERATURE, CONF_DEVICE_ID, CONF_SWITCHES, - PRECISION_HALVES, - PRECISION_WHOLE, - STATE_OFF, STATE_ON, - Platform, - UnitOfTemperature, + STATE_OFF, ) - -from .const import DEVICES, DOMAIN +from .const import ( + DOMAIN, + DEVICES +) +from .midea.devices.e6.device import DeviceAttributes as E6Attributes from .midea.devices.c3.device import DeviceAttributes as C3Attributes from .midea.devices.cd.device import DeviceAttributes as CDAttributes -from .midea.devices.e6.device import DeviceAttributes as E6Attributes from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity +import logging _LOGGER = logging.getLogger(__name__) E2_TEMPERATURE_MAX = 75 @@ -35,12 +37,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): device_id = config_entry.data.get(CONF_DEVICE_ID) device = hass.data[DOMAIN][DEVICES].get(device_id) - extra_switches = config_entry.options.get(CONF_SWITCHES, []) + extra_switches = config_entry.options.get( + CONF_SWITCHES, [] + ) devs = [] for entity_key, config in MIDEA_DEVICES[device.device_type]["entities"].items(): - if config["type"] == Platform.WATER_HEATER and ( - config.get("default") or entity_key in extra_switches - ): + if config["type"] == Platform.WATER_HEATER and (config.get("default") or entity_key in extra_switches): if device.device_type == 0xE2: devs.append(MideaE2WaterHeater(device, entity_key)) elif device.device_type == 0xE3: @@ -96,11 +98,7 @@ def temperature_unit(self): @property def current_operation(self): - return ( - self._device.get_attribute("mode") - if self._device.get_attribute("power") - else STATE_OFF - ) + return self._device.get_attribute("mode") if self._device.get_attribute("power") else STATE_OFF @property def current_temperature(self): @@ -139,9 +137,7 @@ def update_state(self, status): try: self.schedule_update_ha_state() except Exception as e: - _LOGGER.debug( - f"Entity {self.entity_id} update_state {repr(e)}, status = {status}" - ) + _LOGGER.debug(f"Entity {self.entity_id} update_state {repr(e)}, status = {status}") class MideaE2WaterHeater(MideaWaterHeater): @@ -180,11 +176,7 @@ def __init__(self, device, entity_key): @property def state(self): - return ( - STATE_ON - if self._device.get_attribute(C3Attributes.dhw_power) - else STATE_OFF - ) + return STATE_ON if self._device.get_attribute(C3Attributes.dhw_power) else STATE_OFF @property def current_temperature(self): @@ -233,28 +225,20 @@ def __init__(self, device, entity_key, use): super().__init__(device, entity_key) self._use = use self._power_attr = MideaE6WaterHeater._powers[self._use] - self._current_temperature_attr = MideaE6WaterHeater._current_temperatures[ - self._use - ] - self._target_temperature_attr = MideaE6WaterHeater._target_temperatures[ - self._use - ] + self._current_temperature_attr = MideaE6WaterHeater._current_temperatures[self._use] + self._target_temperature_attr = MideaE6WaterHeater._target_temperatures[self._use] @property def state(self): if self._use == 0: # for heating - return ( - STATE_ON - if self._device.get_attribute(E6Attributes.main_power) - and self._device.get_attribute(E6Attributes.heating_power) + return STATE_ON if \ + self._device.get_attribute(E6Attributes.main_power) and \ + self._device.get_attribute(E6Attributes.heating_power) \ else STATE_OFF - ) else: # for bathing - return ( - STATE_ON - if self._device.get_attribute(E6Attributes.main_power) + return STATE_ON if \ + self._device.get_attribute(E6Attributes.main_power) \ else STATE_OFF - ) @property def current_temperature(self): @@ -291,10 +275,8 @@ def __init__(self, device, entity_key): @property def supported_features(self): - return ( - WaterHeaterEntityFeature.TARGET_TEMPERATURE - | WaterHeaterEntityFeature.OPERATION_MODE - ) + return WaterHeaterEntityFeature.TARGET_TEMPERATURE | \ + WaterHeaterEntityFeature.OPERATION_MODE @property def min_temp(self): From ee56ad1b17fad8aff471eb518d4d72d5e4ec55a8 Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Fri, 17 May 2024 18:54:58 +0800 Subject: [PATCH 11/14] sync with master --- LICENSE | 2 +- README.md | 76 +++++++++++++----------------------------------- README_hans.md | 68 +++++++++++-------------------------------- doc/AC.md | 8 +---- doc/C3.md | 9 ++---- doc/C3_hans.md | 7 +---- doc/E2.md | 10 ++----- doc/E3.md | 9 ++---- doc/E3_hans.md | 7 +---- doc/E6.md | 6 ++-- doc/E6_hans.md | 5 +--- doc/E8.md | 6 ++-- doc/E8_hans.md | 6 ++-- doc/EA.md | 5 +--- doc/EA_hans.md | 5 +--- doc/EC.md | 5 +--- doc/EC_hans.md | 5 +--- doc/ED.md | 5 +--- doc/ED_hans.md | 5 +--- doc/FA.md | 11 ++----- doc/FA_hans.md | 7 +---- doc/FB.md | 6 +--- doc/FB_hans.md | 6 +--- doc/FC.md | 6 ++-- doc/FC_hans.md | 4 +-- doc/FD.md | 8 ++--- doc/FD_hans.md | 7 +---- scripts/run.sh | 4 +-- scripts/setup.sh | 6 ++-- 29 files changed, 79 insertions(+), 235 deletions(-) diff --git a/LICENSE b/LICENSE index 0401ec35..0387ca43 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -# MIT License +MIT License Copyright (c) 2021 George Zhao diff --git a/README.md b/README.md index 2765d454..b67caff2 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # Midea AC LAN - [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) [![Donate](https://img.shields.io/badge/donate-BuyMeCoffee-yellow.svg)](https://www.buymeacoffee.com/georgezhao2010) [![Stable](https://img.shields.io/github/v/release/wuwentao/midea_ac_lan)](https://github.com/wuwentao/midea_ac_lan/releases/latest) -[![Super-Linter](https://github.com/wuwentao/midea_ac_lan/actions/workflows/linter.yml/badge.svg)](https://github.com/marketplace/actions/super-linter) -> :warning: **This is a fork of midea_ac_lan done by Georgezhao** +> :warning: **This is a fork of Midea_ac_lan done by Georgezhao **: As the project is in a vegetative state we have done a fork and merged some pending fixes. +I'm trying to get in touch with the maintainer at the moment to find a solution, and at the same time we're looking for people available to maintain the project, which is actively used by the community. please contact me if you can help ! English | [简体中文](README_hans.md) @@ -23,18 +22,13 @@ Thanks also to [@NeoAcheron](https://github.com/NeoAcheron/midea-ac-py). ***❗Note: Home Assistant 2023.1 or higher required for this integration*** -## Supported brands +# Supported brands -![ariston](brands/ariston.png) ![beverly](brands/beverly.png) ![bugu](brands/bugu.png) \ -![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) \ -![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) \ -![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) \ -![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) \ -![wahin](brands/wahin.png) +![ariston](brands/ariston.png) ![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) And more. -## Supported appliances +# Supported appliances | Type | Name | Documents | |------|----------------------------|--------------------| @@ -73,28 +67,12 @@ And more. | FC | Air Purifier | [FC.md](doc/FC.md) | | FD | Humidifier | [FD.md](doc/FD.md) | -## Installation - -**Search `Midea AC LAN` in HACS not available now, it will be ready later** - -Please use manual install as below: - -Option 1: - -1. make sure you have installed HACS to Home Assistant [HACS install guide](https://hacs.xyz/docs/setup/download) -2. open HACS, click [Custom repositories], Repository input: `https://github.com/wuwentao/midea_ac_lan`, Category select [Integration] -3. **Restart Home Assistant**. +# Installation +Search 'Midea AC LAN' in HACS and install, or copy all files in `custom_components/midea_ac_lan` from [Latest Release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest) to your `/custom_components/midea_ac_lan` in Home Assistant manually. -Option 2: - -1. Download `midea_ac_lan.zip` from [Latest Release](https://github.com/wuwentao/midea_ac_lan/releases/latest) -2. copy `midea_ac_lan.zip` to `/custom_components/midea_ac_lan` in Home Assistant. -3. **Restart Home Assistant**. - -Once it done, open `[Settings]`, `[Device & services]`, `[Integrations]`, `[Midea AC Lan]`, do init config and add all your devices. - -## Add device +Restart Home Assistant. +# Add device ***❗Note: First, set a static IP address for your appliance in the router, in case the IP address of the appliance changes after set-up.*** After installation, search and add component Midea AC LAN in Home Assistant integrations page. @@ -105,18 +83,15 @@ Or click [![Configuration](https://my.home-assistant.io/badges/config_flow_start After the account is configured, Click 'ADD DEVICE' once more to add new device. You could repeat the above action to add multiple devices. -### Discover automatically - +## Discover automatically Using this option, the component could auto-discover and list Midea M-Smart appliances in network or specified IP address, select one and add it in. You can also use an IP address to search within a specified network, such as `192.168.1.255`. ***❗Note: Discovery automatically requires your appliances and your Home Assistant must be in the same sub-network. Otherwise, devices may not be auto-discovered. Check this by yourself.*** -### Configure manually - +## Configure manually If you already know following information, you could add the appliance manually. - - Appliance code - Appliance type (one of [Supported appliances](README.md#supported-appliances)) - IP address @@ -125,39 +100,30 @@ If you already know following information, you could add the appliance manually. - Token - Key -### List all appliances only - +## List all appliances only Using this option, you can list all discoverable Midea M-Smart devices on the network, along with their IDs, types, SNs, and other information. ***❗Note: For certain reasons, not all supported devices may be listed here.*** -## Configure +# Configure Configure can be found in `Settings -> Devices & Services -> Midea AC LAN -> Devices -> CONFIGURE`. You can re-set the IP address when device IP changed. You can also add extra sensor and switch entities or customize your own device. -### IP address - +## IP address Set the IP address of device. You can reset this when your device IP is changed. -### Refresh interval - +## Refresh interval Set the interval for actively refreshing the status of a single device (the unit is second) (30 by default and 0 means not refresh actively) -Mostly the status update of Midea devices relies on the active information notification of the device, \ -in which condition the status update in HA still works normally even if the refresh interval is set to be "0". \ -This component will also actively query the device status at regular intervals, and the default time is 30 seconds. \ -Some devices do not have active information notifications when their status changed, so synchronization with the status in HA will be slower. \ -If you are very concerned about the synchronization speed of the status, you can try to set a shorter status refresh interval. +Mostly the status update of Midea devices relies on the active information notification of the device, in which condition the status update in HA still works normally even if the refresh interval is set to be “0”. This component will also actively query the device status at regular intervals, and the default time is 30 seconds. Some devices do not have active information notifications when their status changed, so synchronization with the status in HA will be slower. If you are very concerned about the synchronization speed of the status, you can try to set a shorter status refresh interval. ***❗Note: shorter refresh interval may mean more power consumption*** -### Extra sensor and switch entities - +## Extra sensor and switch entities After configuration, one of few main entity (e.g. climate entity) may be generated . If you want to make the attributes to extra sensor and switch entities, click CONFIGURE in Midea AC LAN integration card to choose (if your devices supported). -### Customize - +## Customize Some types of device have their own configuration items, if your device does not work properly, you may need to customize it. Refer to the device documentation for specific information. The format of customizations must be JSON. @@ -165,15 +131,13 @@ The format of customizations must be JSON. If multiple customization items need to be configured, the settings must comply with the JSON format. Example - ```json {"refresh_interval": 15, "fan_speed": 100} ``` -## Debug +# Debug Turn on the debug log out,config in configuration.yaml - ```yaml logger: default: warn @@ -181,7 +145,7 @@ logger: custom_components.midea_ac_lan: debug ``` -## Support my works +# Support my works If you like this integration, why do not you support my works by buying me a coffee? diff --git a/README_hans.md b/README_hans.md index 3853e00e..bcf132a7 100644 --- a/README_hans.md +++ b/README_hans.md @@ -1,9 +1,7 @@ # Midea AC LAN - [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) [![Donate](https://img.shields.io/badge/donate-BuyMeCoffee-yellow.svg)](https://www.buymeacoffee.com/georgezhao2010) [![Stable](https://img.shields.io/github/v/release/wuwentao/midea_ac_lan)](https://github.com/wuwentao/midea_ac_lan/releases/latest) -[![Super-Linter](https://github.com/wuwentao/midea_ac_lan/actions/workflows/linter.yml/badge.svg)](https://github.com/marketplace/actions/super-linter) [English](README.md) | 简体中文 @@ -21,17 +19,13 @@ ***❗注意: 本集成需要Home Assistant 2023.1或更高版本*** -## 已支持的品牌 +# 已支持的品牌 -![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) \ -![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) \ -![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) \ -![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) \ -![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) +![beverly](brands/beverly.png) ![bugu](brands/bugu.png) ![carrier](brands/carrier.png) ![colmo](brands/colmo.png) ![comfee](brands/comfee.png) ![electrolux](brands/electrolux.png) ![invertor](brands/invertor.png) ![littleswan](brands/littleswan.png) ![midea](brands/midea.png) ![netsu](brands/netsu.png) ![ProBreeze](brands/probreeze.png) ![rotenso](brands/rotenso.png) ![toshiba](brands/toshiba.png) ![vandelo](brands/vandelo.png) ![wahin](brands/wahin.png) 以及更多。 -## 已支持的设备 +# 已支持的设备 | 类型 | 名称 | 文档 | |----|-------------------|------------------------------| @@ -70,28 +64,12 @@ | FC | 空气净化器 | [FC_hans.md](doc/FC_hans.md) | | FD | 加湿器 | [FD_hans.md](doc/FD_hans.md) | -## 安装 - -**在HACS中搜索`Midea AC LAN`并安装的方式当前还不可用**,请耐心等待HACS审核和处理流程。 - -请先使用以下二种方式手工安装: - -方式1: - -1. 确保`Home Assistant`中已安装HACS [HACS install docs](https://hacs.xyz/docs/setup/download) -2. 打开`HACS`, 点击`[Custom repositories]`, `Repository` 输入: `https://github.com/wuwentao/midea_ac_lan`, `Category` 选择 `[Integration]` -3. **重启Home Assistant**. +# 安装 +在HACS中搜索'Midea AC LAN'并安装, 或者从[Latest release](https://github.com/georgezhao2010/midea_ac_lan/releases/latest)下载最新的Release版本, 将其中的`custom_components/midea_ac_lan`放到你的Home Assistant的`custom_components/midea_ac_lan`中。 -方式2: - -1. 从[Latest Release](https://github.com/wuwentao/midea_ac_lan/releases/latest) 下载 `midea_ac_lan.zip` -2. 复制 `midea_ac_lan.zip` 到 `/custom_components/midea_ac_lan`. -3. **重启 Home Assistant**. - -重启完成后, 打开 `[Settings]`, `[Device & services]`, `[Integrations]`, `[Midea AC Lan]`, 进行初始化设置并添加所有设备. - -## 添加设备 +重启Home Assistant +# 添加设备 ***❗注意: 首先, 在路由器上为你的设备设置一个静态IP地址, 以防设置后设备的IP地址发生改变。*** 安装之后, 在Home Assistant的集成界面搜索添加集成Midea AC LAN, 如果需要添加多台设备, 多次添加本集成并执行自动配置即可。 @@ -102,18 +80,15 @@ 完成美的账户配置之后, 点击'添加设备'进行设备添加。你可以多次重复操作以添加多台设备。 -### Discover automatically / 自动搜索 - +## Discover automatically / 自动搜索 使用此选项, 组件会列出网络上或者指定IP地址上的设备, 选择一个并进行添加。 你也可以使用IP地址在指定网络中搜索, 比如`192.168.1.255` ***❗注意: 自动配置要求设备必须与HA在同一网段, 否则可能搜索不到设备, 请自行确认这点*** -### Configure manually / 手动配置 - +## Configure manually / 手动配置 如果之前你已经通过其它集成手工配置过设备, 并知道以下信息, 也可以进行手动配置 - - 设备ID - 设备类型 ([已支持的设备](README_hans.md#%E5%B7%B2%E6%94%AF%E6%8C%81%E7%9A%84%E8%AE%BE%E5%A4%87)之一) - IP地址 @@ -122,23 +97,20 @@ - Token - Key -### List all appliances only / 仅列出所有设备 +## List all appliances only / 仅列出所有设备 使用此选择, 可以列出网络中所有可以被搜索到的美的M-Smart设备, 以及他们的ID, 类型, SN等信息 ***❗注意: 出于某些原因, 可能不是所有受支持的设备都能于此列出*** -## 配置 - +# 配置 集成配置位于`配置 -> 设备与服务 -> Midea AC LAN -> 设备 -> 选项`。 在配置中, 你可以在设备IP改变后重新指定IP地址, 也可以增加扩展的传感器或开关等实体或者自定义你的设备 -### IP地址 - +## IP地址 指定设备的IP地址。当你的设备IP地址变动后, 可以重新设定它 -### 刷新间隔 - +## 刷新间隔 指定单台设备的主动状态刷新间隔 (单位为秒) (默认值为30, 设0代表不进行主动刷新) 大部分的美的设备在自身状态改变时会主动发送通知, 在这种情况下, 即使将改值设为0, 也不会影响HA中的设备状态更新。组件默认每间隔30秒会进行主动刷新。部分设备没有状态改变时主动通知的机制, 状态同步就会表现得很慢, 这种情况下可以尝试更短的主动状态更新间隔。 @@ -146,11 +118,9 @@ ***❗注意: 更小的更新间隔意味着更多的能源消耗*** ## 额外的传感器及开关实体 - 配置完成后, 可能会默认生成一个或几个主要实体(比如climate实体)。如果需要其它属性生成为扩展的传感器及开关实体, 在Midea AC LAN集成卡片上点击'选项', 并选择要生成的传感器及开关(如果你的设备支持该属性)。 -### 自定义 - +## 自定义 某些类型的设备有它们自己的自定义项, 可以通过自定义项定制设备的特点。如果你的设备无法正常工作, 也许你需要自定义它。请参阅设备文档以获取具体信息。 自定义的格式必须是JSON。 @@ -158,15 +128,12 @@ 如果要设置多个自定义项, 请注意遵循JSON的格式要求 示例 - ```json {"refresh_interval": 15, "fan_speed": 100} ``` -## 调试 - +# 调试 要打开调试日志输出, 在configuration.yaml中做如下配置 - ```yaml logger: default: warn @@ -174,8 +141,7 @@ logger: custom_components.midea_ac_lan: debug ``` -## 支持我的工作 - +# 支持我的工作 如果喜欢这个集成, 可以使用支付宝或者微信支付赞助我来支持我的工作。 -![alipay](doc/images/alipay.png) ![wechatpay](doc/images/wechatpay.png) +![alipay](doc/images/alipay.png) ![wechatpay](doc/images/wechatpay.png) \ No newline at end of file diff --git a/doc/AC.md b/doc/AC.md index 972231a4..6a058270 100644 --- a/doc/AC.md +++ b/doc/AC.md @@ -1,7 +1,5 @@ # Air Conditioner - ## Features - - Supports target temperature - Supports run mode - Supports fan mode @@ -10,7 +8,6 @@ - Supports build-in fresh air system ### Supported Run-Modes - - Comfort Mode - ECO Mode - Boost Mode @@ -34,9 +31,7 @@ ``` ## Entities - ### Default entity - | EntityID | Class | Description | |----------------------------|---------|----------------| | climate.{DEVICEID}_climate | climate | Climate entity | @@ -94,7 +89,6 @@ Set the attribute of appliance. Service data: | value | Range 1 to 100 or "auto" | Example - ```yaml service: midea_ac_lan.set_attribute data: @@ -109,4 +103,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: auto -``` +``` \ No newline at end of file diff --git a/doc/C3.md b/doc/C3.md index f7438269..7fb75463 100644 --- a/doc/C3.md +++ b/doc/C3.md @@ -1,14 +1,10 @@ # Heat Pump Wi-Fi Controller - ## Features - - Supports target temperature - Supports run mode ## Entities - ### Default entity - | EntityID | Class | Description | |--------------------------------------|--------------|----------------------| | climate.{DEVICEID}_climate_zone1 | climate | Zone1 climate entity | @@ -24,7 +20,7 @@ | binary_sensor.{DEVICEID}_zone1_room_temp_mode | binary_sensor | Zone1 Room Temperature Mode | | binary_sensor.{DEVICEID}_zone2_room_temp_mode | binary_sensor | Zone2 Room Temperature Mode | | binary_sensor.{DEVICEID}_status_dhw | binary_sensor | DHW Status | -| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status | +| binary_sensor.{DEVICEID}_status_tbh | binary_sensor | TBH Status |**** | binary_sensor.{DEVICEID}_status_ibh | binary_sensor | IBH Status | | binary_sensor.{DEVICEID}_status_heating | binary_sensor | Heating Status | | sensor.{DEVICEID}_error_code | sensor | Error Code | @@ -58,11 +54,10 @@ Set the attribute of appliance. Service data: | value | true or false | Example - ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: zone1_curve value: true -``` +``` \ No newline at end of file diff --git a/doc/C3_hans.md b/doc/C3_hans.md index 2feed9d2..1974af15 100644 --- a/doc/C3_hans.md +++ b/doc/C3_hans.md @@ -1,14 +1,10 @@ # 热泵空调Wi-Fi线控器 - ## 特性 - - 支持目标温度设定 - 支持运行模式设定 ## 生成实体 - ### 默认生成实体 - | 实体ID | 类型 | 描述 | |--------------------------------------|--------------|----------| | climate.{DEVICEID}_climate_zone1 | climate | 区域1恒温器实体 | @@ -58,11 +54,10 @@ | value | true 或 false | 示例 - ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: zone1_curve value: true -``` +``` \ No newline at end of file diff --git a/doc/E2.md b/doc/E2.md index efb72367..2ed23381 100644 --- a/doc/E2.md +++ b/doc/E2.md @@ -1,11 +1,9 @@ # Electric Water Heater - ## Features - - Supports target temperature -## Customize +## Customize - Set the protocol of device ("auto" by default). There are 2 different protocol version to control Electric Water Heater, "old protocol" or "new protocol". If you can't control your device, try change this item and see if it works. @@ -16,9 +14,7 @@ The options include true, false, and "auto". ``` ## Entities - ### Default entity - | EntityID | Class | Description | |--------------------------------------|--------------|---------------------| | water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | @@ -39,6 +35,7 @@ The options include true, false, and "auto". ## Services + ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -52,11 +49,10 @@ Set the attribute of appliance. Service data: | value | true or false | Example - ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: variable_heating value: true -``` +``` \ No newline at end of file diff --git a/doc/E3.md b/doc/E3.md index 420f740b..e9afee87 100644 --- a/doc/E3.md +++ b/doc/E3.md @@ -1,9 +1,8 @@ # Gas Water Heater - ## Features - - Supports target temperature + ## Customize - Set the temperature precision for whole or halves (false for whole and true for halves, default by false) @@ -15,9 +14,7 @@ ``` ## Entities - ### Default entity - | EntityID | Class | Description | |--------------------------------------|--------------|---------------------| | water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | @@ -36,6 +33,7 @@ ## Services + ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -49,11 +47,10 @@ Set the attribute of appliance. Service data: | value | true or false | Example - ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: smart_volume value: true -``` +``` \ No newline at end of file diff --git a/doc/E3_hans.md b/doc/E3_hans.md index 35eeb79f..53864e2a 100644 --- a/doc/E3_hans.md +++ b/doc/E3_hans.md @@ -1,7 +1,5 @@ # 燃气热水器 - ## 特性 - - 支持温度设定 ## 自定义 @@ -15,9 +13,7 @@ ``` ## 生成实体 - ### 默认生成实体 - | 实体ID | 类型 | 描述 | |--------------------------------------|--------------|-------| | water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | @@ -49,11 +45,10 @@ | value | true or false | 示例 - ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: smart_volume value: true -``` +``` \ No newline at end of file diff --git a/doc/E6.md b/doc/E6.md index f54964e9..677f32f0 100644 --- a/doc/E6.md +++ b/doc/E6.md @@ -1,9 +1,7 @@ # Gas Boilers ## Entities - ### Default entity - | EntityID | Class | Description | |----------------------------------------------|--------------|-----------------------------| | water_heater.{DEVICEID}_water_heater_heating | water_heater | Heating water heater entity | @@ -22,6 +20,7 @@ ## Service + ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -35,11 +34,10 @@ Set the attribute of appliance. Service data: | value | true or false | Example - ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: main_power value: true -``` +``` \ No newline at end of file diff --git a/doc/E6_hans.md b/doc/E6_hans.md index 6d481ab8..1f3ca6a8 100644 --- a/doc/E6_hans.md +++ b/doc/E6_hans.md @@ -1,9 +1,7 @@ # 壁挂炉 ## 生成实体 - ### 默认实体 - | 实体ID | 类型 | 描述 | |----------------------------------------------|--------------|---------| | water_heater.{DEVICEID}_water_heater_heating | water_heater | 取暖热水器实体 | @@ -35,11 +33,10 @@ | value | true or false | 示例 - ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: main_power value: true -``` +``` \ No newline at end of file diff --git a/doc/E8.md b/doc/E8.md index e2c75c6b..0af848e3 100644 --- a/doc/E8.md +++ b/doc/E8.md @@ -1,9 +1,7 @@ # Electric Slow Cooker ## Entities - ### Default entity - No default entity. ### Extra entities @@ -19,6 +17,6 @@ No default entity. | sensor.{DEVICEID}_target_temperature | sensor | Target Temperature | | sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | -## Service -No services. +## Service +No services. \ No newline at end of file diff --git a/doc/E8_hans.md b/doc/E8_hans.md index 605603c3..05755269 100644 --- a/doc/E8_hans.md +++ b/doc/E8_hans.md @@ -1,9 +1,7 @@ # 慢炖锅 ## 实体 - ### 默认实体 - 无默认实体 ### 扩展实体 @@ -19,6 +17,6 @@ | sensor.{DEVICEID}_target_temperature | sensor | Target Temperature | 设定温度 | | sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | -## 服务 -无服务 +## 服务 +无服务 \ No newline at end of file diff --git a/doc/EA.md b/doc/EA.md index 1e51d4d9..b07c5e6d 100644 --- a/doc/EA.md +++ b/doc/EA.md @@ -1,9 +1,7 @@ # Electric Rice Cooker ## Entities - ### Default entity - No default entity. ### Extra entities @@ -20,5 +18,4 @@ No default entity. | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | ## Service - -No services. +No services. \ No newline at end of file diff --git a/doc/EA_hans.md b/doc/EA_hans.md index 8fb91828..5e79971b 100644 --- a/doc/EA_hans.md +++ b/doc/EA_hans.md @@ -1,9 +1,7 @@ # 电饭煲 ## 生成实体 - ### 默认实体 - 无默认实体 ### 额外生成实体 @@ -20,5 +18,4 @@ | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | ## 服务 - -无服务 +无服务 \ No newline at end of file diff --git a/doc/EC.md b/doc/EC.md index ed9d7bec..f4907aae 100644 --- a/doc/EC.md +++ b/doc/EC.md @@ -1,9 +1,7 @@ # Electric Pressure Cooker ## Entities - ### Default entity - No default entity. ### Extra entities @@ -20,5 +18,4 @@ No default entity. | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | ## Service - -No services. +No services. \ No newline at end of file diff --git a/doc/EC_hans.md b/doc/EC_hans.md index 5dcf98f6..d65529b7 100644 --- a/doc/EC_hans.md +++ b/doc/EC_hans.md @@ -1,9 +1,7 @@ # 电压力锅 ## 生成实体 - ### 默认实体 - 无默认实体 ### 额外生成实体 @@ -20,5 +18,4 @@ | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | ## 服务 - -无服务 +无服务 \ No newline at end of file diff --git a/doc/ED.md b/doc/ED.md index fffd2401..cfce3672 100644 --- a/doc/ED.md +++ b/doc/ED.md @@ -1,9 +1,7 @@ # Water Drinking Appliance ## Entities - ### Default entity - No default entity. ### Extra entities @@ -23,5 +21,4 @@ No default entity. | switch.{DEVICEID}_power | switch | Power | ## Service - -No services. +No services. \ No newline at end of file diff --git a/doc/ED_hans.md b/doc/ED_hans.md index 711d9e9e..4bfd17c4 100644 --- a/doc/ED_hans.md +++ b/doc/ED_hans.md @@ -1,9 +1,7 @@ # 饮用水设备 ## 生成实体 - ### 默认实体 - 无默认实体 ### 额外生成实体 @@ -23,5 +21,4 @@ | switch.{DEVICEID}_power | switch | Power | 电源开关 | ## 服务 - -无服务 +无服务 \ No newline at end of file diff --git a/doc/FA.md b/doc/FA.md index cfc8d86b..e8f7323d 100644 --- a/doc/FA.md +++ b/doc/FA.md @@ -1,7 +1,5 @@ # Fan - ## Features - - Supports fan speed - Supports preset mode - Supports oscillation @@ -16,9 +14,7 @@ Set the levels of the fan device except "Off" (3 by default). ``` ## Entities - ### Default entity - | EntityID | Class | Description | |--------------------|-------|-------------| | fan.{DEVICEID}_fan | fan | Fan entity | @@ -67,8 +63,7 @@ Set the attribute of appliance. Service data: | value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | Example - -```yaml +``` service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX @@ -76,10 +71,10 @@ data: value: true ``` -```yaml +``` service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: oscillation_angle value: "90" -``` +``` \ No newline at end of file diff --git a/doc/FA_hans.md b/doc/FA_hans.md index 33426dd0..962f14b2 100644 --- a/doc/FA_hans.md +++ b/doc/FA_hans.md @@ -1,7 +1,5 @@ # 电风扇 - ## 特性 - - 支持风速调节 - 支持预设模式 - 支持水平摆头 @@ -16,9 +14,7 @@ ``` ## 生成实体 - ### 默认生成实体 - | 实体ID | 类型 | 描述 | |--------------------|-----|------| | fan.{DEVICEID}_fan | fan | 风扇实体 | @@ -67,7 +63,6 @@ | value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | 示例 - ```yaml service: midea_ac_lan.set_attribute data: @@ -82,4 +77,4 @@ data: device_id: XXXXXXXXXXXX attribute: oscillation_angle value: "90" -``` +``` \ No newline at end of file diff --git a/doc/FB.md b/doc/FB.md index a09062fc..222b6d9c 100644 --- a/doc/FB.md +++ b/doc/FB.md @@ -1,14 +1,11 @@ # Electric Heater ## Features - - Supports target temperature - Supports preset mode ## Entities - ### Default entity - | EntityID | Class | Description | |----------------------------|---------|----------------| | climate.{DEVICEID}_climate | climate | Climate entity | @@ -43,7 +40,6 @@ Set the attribute of appliance. Service data: | value | 1 - 10 | Example - ```yaml service: midea_ac_lan.set_attribute data: @@ -58,4 +54,4 @@ data: device_id: XXXXXXXXXXXX attribute: heating_level value: 9 -``` +``` \ No newline at end of file diff --git a/doc/FB_hans.md b/doc/FB_hans.md index a5a84463..217dd5eb 100644 --- a/doc/FB_hans.md +++ b/doc/FB_hans.md @@ -1,14 +1,11 @@ # 电取暖器 ## Features - - 支持目标温度设定 - 支持预设模式设定 ## 生成实体 - ### 默认生成实体 - | 实体ID | 类型 | 描述 | |----------------------------|---------|-------| | climate.{DEVICEID}_climate | climate | 恒温器实体 | @@ -43,7 +40,6 @@ | value | 1 - 10 | 示例 - ```yaml service: midea_ac_lan.set_attribute data: @@ -58,4 +54,4 @@ data: device_id: XXXXXXXXXXXX attribute: heating_level value: 9 -``` +``` \ No newline at end of file diff --git a/doc/FC.md b/doc/FC.md index e71b41bd..d02e20c6 100644 --- a/doc/FC.md +++ b/doc/FC.md @@ -9,9 +9,7 @@ Set the high/low value of PM2.5 to automatically turn standby mode on or off. ``` ## Entities - ### Default entity - No default entity. ### Extra entities @@ -35,6 +33,7 @@ No default entity. ## Service + ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -72,7 +71,6 @@ Set the attribute of appliance. Service data: | value | "Bright"
"Dim"
"Off" | Example - ```yaml service: midea_ac_lan.set_attribute data: @@ -87,4 +85,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Auto -``` +``` \ No newline at end of file diff --git a/doc/FC_hans.md b/doc/FC_hans.md index f6dc04ae..e1cdfc13 100644 --- a/doc/FC_hans.md +++ b/doc/FC_hans.md @@ -9,9 +9,7 @@ ``` ## 生成实体 - ### 默认生成实体 - 无默认实体 ### 额外生成实体 @@ -86,4 +84,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Auto -``` +``` \ No newline at end of file diff --git a/doc/FD.md b/doc/FD.md index 325d4728..c71371be 100644 --- a/doc/FD.md +++ b/doc/FD.md @@ -1,15 +1,11 @@ # Humidifier - ## Features - - Supports preset mode - Supports fan mode - Supports humidity setting ## Entities - ### Default entity - | EntityID | Class | Description | |----------------------------------|------------|-------------------| | humidifier.{DEVICEID}_humidifier | humidifier | Humidifier entity | @@ -28,6 +24,7 @@ ## Service + ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -53,7 +50,6 @@ Set the attribute of appliance. Service data: | value | "Bright"
"Dim"
"Off" | Example - ```yaml service: midea_ac_lan.set_attribute data: @@ -68,4 +64,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Medium -``` +``` \ No newline at end of file diff --git a/doc/FD_hans.md b/doc/FD_hans.md index 3cd9f5fe..ba6be127 100644 --- a/doc/FD_hans.md +++ b/doc/FD_hans.md @@ -1,15 +1,11 @@ # 加湿器 - ## 特性 - - 支持运行模式 - 支持风扇模式设定 - 支持湿度设定 ## 生成实体 - ### 默认生成实体 - | 实体ID | 类型 | 描述 | |----------------------------------|------------|-------| | humidifier.{DEVICEID}_humidifier | humidifier | 加湿器实体 | @@ -53,7 +49,6 @@ | value | "Bright"
"Dim"
"Off" | 示例 - ```yaml service: midea_ac_lan.set_attribute data: @@ -68,4 +63,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Medium -``` +``` \ No newline at end of file diff --git a/scripts/run.sh b/scripts/run.sh index 035649a3..1c529a66 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -6,8 +6,8 @@ cd "$(dirname "$0")/.." # Create config dir if not present if [[ ! -d "${PWD}/config" ]]; then - mkdir -p "${PWD}/config" - hass --config "${PWD}/config" --script ensure_config + mkdir -p "${PWD}/config" + hass --config "${PWD}/config" --script ensure_config fi # Set the path to custom_components diff --git a/scripts/setup.sh b/scripts/setup.sh index 43421316..692cc8b7 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -8,6 +8,6 @@ python3 -m pip install --requirement requirements.txt # Create config dir if not present if [[ ! -d "${PWD}/config" ]]; then - mkdir -p "${PWD}/config" - hass --config "${PWD}/config" --script ensure_config -fi + mkdir -p "${PWD}/config" + hass --config "${PWD}/config" --script ensure_config +fi \ No newline at end of file From c3862de908044d52cadbfc1acc383d4b22df2d69 Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Tue, 21 May 2024 11:08:38 +0800 Subject: [PATCH 12/14] fix flake8 and yaml linter error --- .github/workflows/codeql.yml | 13 ++++++++----- .github/workflows/linter.yml | 12 ++++-------- custom_components/midea_ac_lan/__init__.py | 2 -- custom_components/midea_ac_lan/climate.py | 2 +- custom_components/midea_ac_lan/config_flow.py | 4 ++-- .../midea_ac_lan/midea/backports/myenum.py | 2 +- custom_components/midea_ac_lan/midea/core/cloud.py | 6 +++--- custom_components/midea_ac_lan/midea/core/device.py | 2 +- .../midea_ac_lan/midea/core/security.py | 2 +- .../midea_ac_lan/midea/devices/__init__.py | 2 ++ .../midea_ac_lan/midea/devices/a1/message.py | 1 + .../midea_ac_lan/midea/devices/ac/message.py | 5 ++--- .../midea_ac_lan/midea/devices/b0/message.py | 1 - .../midea_ac_lan/midea/devices/b1/message.py | 1 - .../midea_ac_lan/midea/devices/b3/message.py | 1 - .../midea_ac_lan/midea/devices/b4/message.py | 3 +-- .../midea_ac_lan/midea/devices/c2/message.py | 1 + .../midea_ac_lan/midea/devices/c3/device.py | 2 +- .../midea_ac_lan/midea/devices/c3/message.py | 2 +- .../midea_ac_lan/midea/devices/cd/message.py | 5 +++-- .../midea_ac_lan/midea/devices/cf/message.py | 1 - .../midea_ac_lan/midea/devices/da/device.py | 3 ++- .../midea_ac_lan/midea/devices/db/message.py | 2 +- .../midea_ac_lan/midea/devices/e3/device.py | 5 +++-- .../midea_ac_lan/midea/devices/e8/message.py | 3 +-- .../midea_ac_lan/midea/devices/ea/message.py | 2 +- .../midea_ac_lan/midea/devices/ec/message.py | 2 +- .../midea_ac_lan/midea/devices/ed/device.py | 2 +- .../midea_ac_lan/midea/devices/x13/device.py | 2 +- .../midea_ac_lan/midea/devices/x13/message.py | 2 +- .../midea_ac_lan/midea/devices/x26/message.py | 3 +-- .../midea_ac_lan/midea/devices/x40/message.py | 2 +- custom_components/midea_ac_lan/number.py | 6 +++--- custom_components/midea_ac_lan/services.yaml | 3 ++- 34 files changed, 52 insertions(+), 55 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 98eb3686..34774ab8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,12 +1,15 @@ +--- name: "CodeQL" on: push: - branches: [ "master" ] + branches: ["master"] pull_request: - branches: [ "master" ] - # schedule: - # - cron: "42 8 * * 6" + branches: ["master"] + # schedule: + # - cron: "42 8 * * 6" + +permissions: {} jobs: analyze: @@ -20,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - language: [ python ] + language: [python] steps: - name: Checkout diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 514633c3..8d15f23e 100755 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,13 +1,9 @@ --- -name: Linter +name: Lint -on: - push: - branches: - - master - pull_request: - branches: - - master +on: # yamllint disable-line rule:truthy + push: null + pull_request: null permissions: {} diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py index 1125f120..a78ee2d0 100644 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -9,9 +9,7 @@ CONF_SUBTYPE, CONF_REFRESH_INTERVAL, DEVICES, - EXTRA_SENSOR, EXTRA_SWITCH, - EXTRA_CONTROL, ALL_PLATFORM, ) from .midea_devices import MIDEA_DEVICES diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py index 640e67bc..7c9bb525 100644 --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -516,4 +516,4 @@ def set_hvac_mode(self, hvac_mode: str) -> None: self.turn_on() def set_preset_mode(self, preset_mode: str) -> None: - self._device.set_attribute(attr=FBAttributes.mode,value=preset_mode) + self._device.set_attribute(attr=FBAttributes.mode, value=preset_mode) diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py index 16433b0a..900bd5ce 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -254,7 +254,7 @@ async def async_step_auto(self, user_input=None, error=None): self.found_device[CONF_SUBTYPE] = device_info.get("model_number") if device.get(CONF_PROTOCOL) == 3: if self.account[CONF_SERVER] == "美的美居": - _LOGGER.debug(f"Try to get the Token and the Key use the preset MSmartHome account") + _LOGGER.debug("Try to get the Token and the Key use the preset MSmartHome account") self.cloud = get_midea_cloud( "MSmartHome", self.session, @@ -426,7 +426,7 @@ async def async_step_init(self, user_input=None): sensors = {} switches = {} for attribute, attribute_config in MIDEA_DEVICES.get(self._device_type).get("entities").items(): - attribute_name = attribute if type(attribute) is str else attribute.value + attribute_name = attribute if isinstance(attribute, str) else attribute.value if attribute_config.get("type") in EXTRA_SENSOR: sensors[attribute_name] = attribute_config.get("name") elif attribute_config.get("type") in EXTRA_CONTROL and not attribute_config.get("default"): diff --git a/custom_components/midea_ac_lan/midea/backports/myenum.py b/custom_components/midea_ac_lan/midea/backports/myenum.py index c256cb8d..e3ef7d54 100644 --- a/custom_components/midea_ac_lan/midea/backports/myenum.py +++ b/custom_components/midea_ac_lan/midea/backports/myenum.py @@ -31,4 +31,4 @@ def _generate_next_value_( We may revisit this when it's very clear that Python 3.11's `StrEnum.auto()` behavior will no longer change. """ - raise TypeError("auto() is not supported by this implementation") \ No newline at end of file + raise TypeError("auto() is not supported by this implementation") diff --git a/custom_components/midea_ac_lan/midea/core/cloud.py b/custom_components/midea_ac_lan/midea/core/cloud.py index 12e396c4..286ea204 100644 --- a/custom_components/midea_ac_lan/midea/core/cloud.py +++ b/custom_components/midea_ac_lan/midea/core/cloud.py @@ -280,7 +280,7 @@ async def list_appliances(self, home_id) -> dict | None: "sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "", "sn8": appliance.get("sn8", "00000000"), "model_number": model_number, - "manufacturer_code":appliance.get("enterpriseCode", "0000"), + "manufacturer_code": appliance.get("enterpriseCode", "0000"), "model": appliance.get("productModel"), "online": appliance.get("onlineStatus") == "1", } @@ -464,7 +464,7 @@ async def list_appliances(self, home_id) -> dict | None: "sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "", "sn8": "", "model_number": model_number, - "manufacturer_code":appliance.get("enterpriseCode", "0000"), + "manufacturer_code": appliance.get("enterpriseCode", "0000"), "model": "", "online": appliance.get("onlineStatus") == "1", } @@ -624,7 +624,7 @@ async def list_appliances(self, home_id) -> dict | None: "sn": appliance.get("sn"), "sn8": "", "model_number": model_number, - "manufacturer_code":appliance.get("enterpriseCode", "0000"), + "manufacturer_code": appliance.get("enterpriseCode", "0000"), "model": "", "online": appliance.get("onlineStatus") == "1", } diff --git a/custom_components/midea_ac_lan/midea/core/device.py b/custom_components/midea_ac_lan/midea/core/device.py index 1eaa24f4..7ba2fe30 100644 --- a/custom_components/midea_ac_lan/midea/core/device.py +++ b/custom_components/midea_ac_lan/midea/core/device.py @@ -254,7 +254,7 @@ def parse_message(self, msg): else: _LOGGER.debug(f"[{self._device_id}] Unidentified protocol") except Exception as e: - _LOGGER.error(f"[{self._device_id}] Error in process message, msg = {decrypted.hex()}") + _LOGGER.error(f"[{self._device_id}] Error in process message: {e}, msg = {decrypted.hex()}") else: _LOGGER.warning( f"[{self._device_id}] Illegal payload, " diff --git a/custom_components/midea_ac_lan/midea/core/security.py b/custom_components/midea_ac_lan/midea/core/security.py index 4f9ac85d..26cf0262 100644 --- a/custom_components/midea_ac_lan/midea/core/security.py +++ b/custom_components/midea_ac_lan/midea/core/security.py @@ -173,7 +173,7 @@ def __init__(self): def aes_decrypt(self, raw): try: return unpad(AES.new(self.aes_key, AES.MODE_ECB).decrypt(bytearray(raw)), 16) - except ValueError as e: + except ValueError: return bytearray(0) def aes_encrypt(self, raw): diff --git a/custom_components/midea_ac_lan/midea/devices/__init__.py b/custom_components/midea_ac_lan/midea/devices/__init__.py index 279e0a91..4a732c88 100644 --- a/custom_components/midea_ac_lan/midea/devices/__init__.py +++ b/custom_components/midea_ac_lan/midea/devices/__init__.py @@ -2,6 +2,7 @@ from importlib import import_module from types import ModuleType + async def async_device_selector( hass: HomeAssistant, name: str, @@ -24,6 +25,7 @@ async def async_device_selector( device_path = f".{'%02x' % device_type}.device" modules: list[ModuleType] = [] + def _load_device_module() -> None: """Load all service modules.""" modules.append(import_module(device_path, __package__)) diff --git a/custom_components/midea_ac_lan/midea/devices/a1/message.py b/custom_components/midea_ac_lan/midea/devices/a1/message.py index a954f98b..e1dbe517 100644 --- a/custom_components/midea_ac_lan/midea/devices/a1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/a1/message.py @@ -8,6 +8,7 @@ NewProtocolMessageBody ) + class NewProtocolTags(IntEnum): light = 0x005B diff --git a/custom_components/midea_ac_lan/midea/devices/ac/message.py b/custom_components/midea_ac_lan/midea/devices/ac/message.py index 9468f689..96037f1b 100644 --- a/custom_components/midea_ac_lan/midea/devices/ac/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ac/message.py @@ -256,9 +256,8 @@ def _body(self): # Byte 3, fan_speed fan_speed = self.fan_speed & 0x7f # Byte 7, swing_mode - swing_mode = 0x30 | \ - (0x0c if self.swing_vertical else 0) | \ - (0x03 if self.swing_horizontal else 0) + swing_mode = 0x30 | (0x0c if self.swing_vertical else 0) \ + | (0x03 if self.swing_horizontal else 0) # Byte 8, turbo boost_mode = 0x20 if self.boost_mode else 0 # Byte 9 aux_heating eco_mode diff --git a/custom_components/midea_ac_lan/midea/devices/b0/message.py b/custom_components/midea_ac_lan/midea/devices/b0/message.py index 6503d4cc..8dc4d0ce 100644 --- a/custom_components/midea_ac_lan/midea/devices/b0/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b0/message.py @@ -82,4 +82,3 @@ def __init__(self, message): else: self.set_body(B0MessageBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/b1/message.py b/custom_components/midea_ac_lan/midea/devices/b1/message.py index bbf50abd..1810e1e8 100644 --- a/custom_components/midea_ac_lan/midea/devices/b1/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b1/message.py @@ -52,4 +52,3 @@ def __init__(self, message): if self.message_type in [MessageType.notify1, MessageType.query]: self.set_body(B1MessageBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/b3/message.py b/custom_components/midea_ac_lan/midea/devices/b3/message.py index a4c68ca5..8af1d9e6 100644 --- a/custom_components/midea_ac_lan/midea/devices/b3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b3/message.py @@ -138,4 +138,3 @@ def __init__(self, message): elif self.message_type == MessageType.set and self.body_type == 0x24: self.set_body(B3MessageBody21(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/b4/message.py b/custom_components/midea_ac_lan/midea/devices/b4/message.py index 489c0f14..91f55059 100644 --- a/custom_components/midea_ac_lan/midea/devices/b4/message.py +++ b/custom_components/midea_ac_lan/midea/devices/b4/message.py @@ -38,7 +38,7 @@ def __init__(self, body): self.time_remaining = (0 if body[22] == 0xFF else body[22]) * 3600 + \ (0 if body[23] == 0xFF else body[23]) * 60 + \ (0 if body[24] == 0xFF else body[24]) - self.current_temperature = (body[25] << 8 ) + body[26] + self.current_temperature = (body[25] << 8) + body[26] if self.current_temperature == 0: self.current_temperature = (body[27] << 8) + body[28] self.status = body[31] @@ -55,4 +55,3 @@ def __init__(self, message): if self.body_type == 0x01: self.set_body(B4MessageBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/c2/message.py b/custom_components/midea_ac_lan/midea/devices/c2/message.py index 71c608b4..343fa1b3 100644 --- a/custom_components/midea_ac_lan/midea/devices/c2/message.py +++ b/custom_components/midea_ac_lan/midea/devices/c2/message.py @@ -59,6 +59,7 @@ def __init__(self, protocol_version): message_type=MessageType.set, body_type=0x00) self.power = False + @property def _body(self): if self.power: diff --git a/custom_components/midea_ac_lan/midea/devices/c3/device.py b/custom_components/midea_ac_lan/midea/devices/c3/device.py index cc8ba680..90b41e5a 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/device.py @@ -207,7 +207,7 @@ def make_message_set(self): return message def set_attribute(self, attr, value): - message= None + message = None if attr in [ DeviceAttributes.zone1_power, DeviceAttributes.zone2_power, diff --git a/custom_components/midea_ac_lan/midea/devices/c3/message.py b/custom_components/midea_ac_lan/midea/devices/c3/message.py index c339918e..f9703572 100644 --- a/custom_components/midea_ac_lan/midea/devices/c3/message.py +++ b/custom_components/midea_ac_lan/midea/devices/c3/message.py @@ -194,4 +194,4 @@ def __init__(self, message): self.set_body(C3MessageBody(super().body, data_offset=1)) elif self.message_type == MessageType.notify1 and self.body_type == 0x04: self.set_body(C3Notify1MessageBody(super().body, data_offset=1)) - self.set_attr() \ No newline at end of file + self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cd/message.py b/custom_components/midea_ac_lan/midea/devices/cd/message.py index 450345e5..40c4ba0c 100644 --- a/custom_components/midea_ac_lan/midea/devices/cd/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cd/message.py @@ -21,7 +21,7 @@ def _body(self): class MessageQuery(MessageCDBase): - def __init__(self,protocol_version): + def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.query, @@ -33,7 +33,7 @@ def _body(self): class MessageSet(MessageCDBase): - def __init__(self,protocol_version): + def __init__(self, protocol_version): super().__init__( protocol_version=protocol_version, message_type=MessageType.set, @@ -83,6 +83,7 @@ def __init__(self, body): if (body[28] & 0x20) > 0: self.mode = 3 + class CD02MessageBody(MessageBody): def __init__(self, body): super().__init__(body) diff --git a/custom_components/midea_ac_lan/midea/devices/cf/message.py b/custom_components/midea_ac_lan/midea/devices/cf/message.py index 5db9d99a..49bf150c 100644 --- a/custom_components/midea_ac_lan/midea/devices/cf/message.py +++ b/custom_components/midea_ac_lan/midea/devices/cf/message.py @@ -82,4 +82,3 @@ def __init__(self, message): elif self.message_type in [MessageType.notify1, MessageType.notify2]: self.set_body(CFMessageBody(super().body, data_offset=0)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/da/device.py b/custom_components/midea_ac_lan/midea/devices/da/device.py index 305b7bbf..f213424f 100644 --- a/custom_components/midea_ac_lan/midea/devices/da/device.py +++ b/custom_components/midea_ac_lan/midea/devices/da/device.py @@ -94,7 +94,7 @@ def process_message(self, msg): speed = ["-", "Low", "Medium", "High"] strength = ["-", "Week", "Medium", "Strong"] detergent = ["No", "Less", "Medium", "More", "4", - "5", "6", "7", "8", "Insufficient"] + "5", "6", "7", "8", "Insufficient"] softener = ["No", "Intelligent", "Programed", "3", "4", "5", "6", "7", "8", "Insufficient"] for status in self._attributes.keys(): @@ -137,5 +137,6 @@ def set_attribute(self, attr, value): message.washing_data = self._attributes[DeviceAttributes.washing_data] self.build_send(message) + class MideaAppliance(MideaDADevice): pass diff --git a/custom_components/midea_ac_lan/midea/devices/db/message.py b/custom_components/midea_ac_lan/midea/devices/db/message.py index 603c60d7..580e5542 100644 --- a/custom_components/midea_ac_lan/midea/devices/db/message.py +++ b/custom_components/midea_ac_lan/midea/devices/db/message.py @@ -64,7 +64,7 @@ def __init__(self, protocol_version): @property def _body(self): - if self.start: # Pause + if self.start: # Pause return bytearray([ 0xFF, 0x01 ]) + self.washing_data diff --git a/custom_components/midea_ac_lan/midea/devices/e3/device.py b/custom_components/midea_ac_lan/midea/devices/e3/device.py index 0b708f08..69b92557 100644 --- a/custom_components/midea_ac_lan/midea/devices/e3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e3/device.py @@ -82,8 +82,9 @@ def process_message(self, msg): new_status = {} for status in self._attributes.keys(): if hasattr(message, str(status)): - if self._precision_halves and status in [DeviceAttributes.current_temperature, - DeviceAttributes.target_temperature]: + if self._precision_halves and status in \ + [DeviceAttributes.current_temperature, + DeviceAttributes.target_temperature]: self._attributes[status] = getattr(message, str(status)) / 2 else: self._attributes[status] = getattr(message, str(status)) diff --git a/custom_components/midea_ac_lan/midea/devices/e8/message.py b/custom_components/midea_ac_lan/midea/devices/e8/message.py index 7ee345d5..9c45c688 100644 --- a/custom_components/midea_ac_lan/midea/devices/e8/message.py +++ b/custom_components/midea_ac_lan/midea/devices/e8/message.py @@ -51,7 +51,6 @@ def __init__(self, message): if len(super().body) > 6: sub_cmd = super().body[6] if ((self.message_type == MessageType.set and sub_cmd in [0x02, 0x04, 0x06]) or - self.message_type in [MessageType.query, MessageType.notify1] and sub_cmd ==2): + self.message_type in [MessageType.query, MessageType.notify1] and sub_cmd == 2): self.set_body(E8MessageBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/ea/message.py b/custom_components/midea_ac_lan/midea/devices/ea/message.py index 3fea521b..50ac934a 100644 --- a/custom_components/midea_ac_lan/midea/devices/ea/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ea/message.py @@ -107,7 +107,7 @@ def __init__(self, message): elif self.message_type == MessageType.notify1 and super().body[5] == 0x3d: # 463 self.set_body(EABody1(super().body)) else: - if(self.message_type == MessageType.set and super().body[3] == 0x02) or \ + if (self.message_type == MessageType.set and super().body[3] == 0x02) or \ (self.message_type == MessageType.query and super().body[3] == 0x03) or \ (self.message_type == MessageType.notify1 and super().body[3] == 0x04): # 351 self.set_body(EABody3(super().body)) diff --git a/custom_components/midea_ac_lan/midea/devices/ec/message.py b/custom_components/midea_ac_lan/midea/devices/ec/message.py index a3387768..5911670c 100644 --- a/custom_components/midea_ac_lan/midea/devices/ec/message.py +++ b/custom_components/midea_ac_lan/midea/devices/ec/message.py @@ -71,7 +71,7 @@ def __init__(self, message): super().__init__(message) if self.message_type == MessageType.notify1 and super().body[3] == 0x01: self.set_body(ECBodyNew(super().body)) - elif(self.message_type == MessageType.set and super().body[3] == 0x02) or \ + elif (self.message_type == MessageType.set and super().body[3] == 0x02) or \ (self.message_type == MessageType.query and super().body[3] == 0x03) or \ (self.message_type == MessageType.notify1 and super().body[3] == 0x04) or \ (self.message_type == MessageType.notify1 and super().body[3] == 0x3d): diff --git a/custom_components/midea_ac_lan/midea/devices/ed/device.py b/custom_components/midea_ac_lan/midea/devices/ed/device.py index 4aa1e4f8..3052f861 100644 --- a/custom_components/midea_ac_lan/midea/devices/ed/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ed/device.py @@ -70,7 +70,7 @@ def __init__( self._device_class = 0 def _use_new_set(self): - return True # if (self.sub_type > 342 or self.sub_type == 340) else False + return True # if (self.sub_type > 342 or self.sub_type == 340) else False def build_query(self): return [ diff --git a/custom_components/midea_ac_lan/midea/devices/x13/device.py b/custom_components/midea_ac_lan/midea/devices/x13/device.py index 50843ec7..d719ad1f 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/device.py @@ -70,7 +70,7 @@ def color_temp_range(self): def kelvin_to_midea(self, kelvin): return round((kelvin - self._color_temp_range[0]) / - (self._color_temp_range[1] - self._color_temp_range[0]) * 255) + (self._color_temp_range[1] - self._color_temp_range[0]) * 255) def midea_to_kelvin(self, midea): return round((self._color_temp_range[1] - self._color_temp_range[0]) / 255 * midea) + \ diff --git a/custom_components/midea_ac_lan/midea/devices/x13/message.py b/custom_components/midea_ac_lan/midea/devices/x13/message.py index bf6bc09a..ce8e3699 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/message.py @@ -84,6 +84,7 @@ def __init__(self, body): super().__init__(body) self.control_success = body[1] > 0 + class Message13Response(MessageResponse): def __init__(self, message): super().__init__(message) @@ -92,4 +93,3 @@ def __init__(self, message): elif self.message_type == MessageType.set and self.body_type > 0x80: self.set_body(MessageMainLightResponseBody(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/x26/message.py b/custom_components/midea_ac_lan/midea/devices/x26/message.py index 268bf96e..f0afd96c 100644 --- a/custom_components/midea_ac_lan/midea/devices/x26/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x26/message.py @@ -107,7 +107,7 @@ def __init__(self, body): self.fields["LIGHT_INTENSITY_THRESHOLD"] = self.read_byte(body, 7) self.fields["RADAR_SENSITIVITY"] = self.read_byte(body, 8) heat_mode = self.read_byte(body, 9) > 0 - heat_temperature = self.read_byte(body, 10) + heat_temperature = self.read_byte(body, 10) self.fields["HEATING_SPEED"] = self.read_byte(body, 11) heat_direction = self.read_byte(body, 12) bath_mode = self.read_byte(body, 13) > 0 @@ -171,4 +171,3 @@ def __init__(self, message): if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: self.set_body(Message26Body(super().body)) self.set_attr() - diff --git a/custom_components/midea_ac_lan/midea/devices/x40/message.py b/custom_components/midea_ac_lan/midea/devices/x40/message.py index a7afb1be..0dd620a1 100644 --- a/custom_components/midea_ac_lan/midea/devices/x40/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x40/message.py @@ -114,7 +114,7 @@ def __init__(self, body): self.fields["LIGHT_INTENSITY_THRESHOLD"] = body[7] self.fields["RADAR_SENSITIVITY"] = body[8] self.fields["HEATING_ENABLE"] = body[9] - self.fields["HEATING_TEMPERATURE"]= body[10] + self.fields["HEATING_TEMPERATURE"] = body[10] self.fields["HEATING_SPEED"] = body[11] self.fields["HEATING_DIRECTION"] = body[12] self.fields["BATH_ENABLE"] = body[13] > 0 diff --git a/custom_components/midea_ac_lan/number.py b/custom_components/midea_ac_lan/number.py index 94f5032e..987e78eb 100644 --- a/custom_components/midea_ac_lan/number.py +++ b/custom_components/midea_ac_lan/number.py @@ -35,21 +35,21 @@ def __init__(self, device, entity_key: str): @property def native_min_value(self): - return self._min_value if (type(self._min_value) is int) else \ + return self._min_value if isinstance(self._min_value, int) else \ self._device.get_attribute(attr=self._min_value) \ if self._device.get_attribute(attr=self._min_value) else \ getattr(self._device, self._min_value) @property def native_max_value(self): - return self._max_value if (type(self._max_value) is int) else \ + return self._max_value if isinstance(self._max_value, int) else \ self._device.get_attribute(attr=self._max_value) \ if self._device.get_attribute(attr=self._max_value) else \ getattr(self._device, self._max_value) @property def native_step(self): - return self._step_value if (type(self._step_value) is int) else \ + return self._step_value if isinstance(self._step_value, int) else \ self._device.get_attribute(attr=self._step_value) \ if self._device.get_attribute(attr=self._step_value) else \ getattr(self._device, self._step_value) diff --git a/custom_components/midea_ac_lan/services.yaml b/custom_components/midea_ac_lan/services.yaml index f5e06d7a..49103179 100644 --- a/custom_components/midea_ac_lan/services.yaml +++ b/custom_components/midea_ac_lan/services.yaml @@ -1,3 +1,4 @@ +--- set_attribute: fields: device_id: @@ -14,4 +15,4 @@ send_command: cmd_type: example: 2 cmd_body: - example: "B0FF01370E0000A500" \ No newline at end of file + example: "B0FF01370E0000A500" From 3bfd80a8a95455ec9c46f5546890259be33455fa Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Tue, 21 May 2024 11:13:32 +0800 Subject: [PATCH 13/14] fix flake8 and yaml linter error --- .github/workflows/linter.yml | 2 ++ custom_components/midea_ac_lan/midea/devices/x40/message.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 8d15f23e..fa783c06 100755 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -35,3 +35,5 @@ jobs: VALIDATE_JSCPD: false VALIDATE_PYTHON_PYLINT: false VALIDATE_PYTHON_MYPY: false + VALIDATE_PYTHON_BLACK: false + VALIDATE_PYTHON_ISORT: false diff --git a/custom_components/midea_ac_lan/midea/devices/x40/message.py b/custom_components/midea_ac_lan/midea/devices/x40/message.py index 0dd620a1..71baa781 100644 --- a/custom_components/midea_ac_lan/midea/devices/x40/message.py +++ b/custom_components/midea_ac_lan/midea/devices/x40/message.py @@ -160,4 +160,3 @@ def __init__(self, message): if self.message_type in [MessageType.set, MessageType.notify1, MessageType.query] and self.body_type == 0x01: self.set_body(Message40Body(super().body)) self.set_attr() - From ccb52b3ea49a67a570c25c96e6d89e07fd7d839e Mon Sep 17 00:00:00 2001 From: Wentao Wu Date: Tue, 21 May 2024 11:42:09 +0800 Subject: [PATCH 14/14] fix markdown lint and shell lint error --- doc/C2.md | 6 +++++- doc/C2_hans.md | 6 +++++- doc/C3.md | 7 ++++++- doc/C3_hans.md | 7 ++++++- doc/CA.md | 5 ++++- doc/CA_hans.md | 5 ++++- doc/CC.md | 8 +++++++- doc/CC_hans.md | 9 ++++++++- doc/CD_hans.md | 7 +++++-- doc/CE.md | 5 ++++- doc/CE_hans.md | 5 +++++ doc/CF.md | 8 ++++++-- doc/CF_hans.md | 7 ++++++- doc/DA.md | 6 ++++-- doc/DA_hans.md | 6 +++++- doc/DB.md | 6 ++++-- doc/DB_hans.md | 5 ++++- doc/DC.md | 6 ++++-- doc/DC_hans.md | 5 ++++- doc/E1.md | 6 ++++-- doc/E1_hans.md | 6 +++++- doc/E2.md | 10 +++++++--- doc/E2_hans.md | 8 +++++++- doc/E3.md | 9 ++++++--- doc/E3_hans.md | 7 ++++++- doc/E6.md | 6 ++++-- doc/E6_hans.md | 5 ++++- doc/E8.md | 6 ++++-- doc/E8_hans.md | 6 ++++-- doc/EA.md | 5 ++++- doc/EA_hans.md | 5 ++++- doc/EC.md | 5 ++++- doc/EC_hans.md | 5 ++++- doc/ED.md | 5 ++++- doc/ED_hans.md | 3 +++ doc/FA.md | 11 ++++++++--- doc/FA_hans.md | 7 ++++++- doc/FB.md | 6 +++++- doc/FB_hans.md | 6 +++++- doc/FC.md | 6 ++++-- doc/FC_hans.md | 5 ++++- doc/FD.md | 8 ++++++-- doc/FD_hans.md | 7 ++++++- scripts/run.sh | 4 ++-- scripts/setup.sh | 6 +++--- 45 files changed, 219 insertions(+), 63 deletions(-) diff --git a/doc/C2.md b/doc/C2.md index 3366fdb1..eba2a354 100644 --- a/doc/C2.md +++ b/doc/C2.md @@ -11,8 +11,11 @@ ``` ## Entities + ### Default entity + No default entity + ### Extra entities | EntityID | Class | Description | @@ -51,6 +54,7 @@ Set the attribute of appliance. Service data: | value | 0 to max-level | Example + ```yaml service: midea_ac_lan.set_attribute data: @@ -65,4 +69,4 @@ data: device_id: XXXXXXXXXXXX attribute: max_dry_level value: 2 -``` \ No newline at end of file +``` diff --git a/doc/C2_hans.md b/doc/C2_hans.md index 79b18ba5..7010192d 100644 --- a/doc/C2_hans.md +++ b/doc/C2_hans.md @@ -11,8 +11,11 @@ ``` ## 实体 + ### 默认实体 + 无默认实体 + ### 扩展实体 | EntityID | Class | Description | 描述 | @@ -51,6 +54,7 @@ | value | 0 到 最大档位 | 示例 + ```yaml service: midea_ac_lan.set_attribute data: @@ -65,4 +69,4 @@ data: device_id: XXXXXXXXXXXX attribute: max_dry_level value: 2 -``` \ No newline at end of file +``` diff --git a/doc/C3.md b/doc/C3.md index 7fb75463..fc843704 100644 --- a/doc/C3.md +++ b/doc/C3.md @@ -1,10 +1,14 @@ # Heat Pump Wi-Fi Controller + ## Features + - Supports target temperature - Supports run mode ## Entities + ### Default entity + | EntityID | Class | Description | |--------------------------------------|--------------|----------------------| | climate.{DEVICEID}_climate_zone1 | climate | Zone1 climate entity | @@ -54,10 +58,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: zone1_curve value: true -``` \ No newline at end of file +``` diff --git a/doc/C3_hans.md b/doc/C3_hans.md index 1974af15..2feed9d2 100644 --- a/doc/C3_hans.md +++ b/doc/C3_hans.md @@ -1,10 +1,14 @@ # 热泵空调Wi-Fi线控器 + ## 特性 + - 支持目标温度设定 - 支持运行模式设定 ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |--------------------------------------|--------------|----------| | climate.{DEVICEID}_climate_zone1 | climate | 区域1恒温器实体 | @@ -54,10 +58,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: zone1_curve value: true -``` \ No newline at end of file +``` diff --git a/doc/CA.md b/doc/CA.md index 049243ae..6252f611 100644 --- a/doc/CA.md +++ b/doc/CA.md @@ -1,7 +1,9 @@ # Refrigerator ## Entities + ### Default entity + No default entity. ### Extra entities @@ -27,4 +29,5 @@ No default entity. | sensor.{DEVICEID}_right_flex_zone_setting_temp | sensor | Right Flex-zone Setting Temperature | ## Service -No services. \ No newline at end of file + +No services. diff --git a/doc/CA_hans.md b/doc/CA_hans.md index 37a46265..9c33f7b1 100644 --- a/doc/CA_hans.md +++ b/doc/CA_hans.md @@ -1,7 +1,9 @@ # 冰箱 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -27,4 +29,5 @@ | sensor.{DEVICEID}_right_flex_zone_setting_temp | sensor | Right Flex-zone Setting Temperature | 右变温区设置温度 | ## 服务 -无服务 \ No newline at end of file + +无服务 diff --git a/doc/CC.md b/doc/CC.md index 48b8df52..28effdf3 100644 --- a/doc/CC.md +++ b/doc/CC.md @@ -1,5 +1,7 @@ # MDV Wi-Fi Controller + ## Features + - Supports target temperature - Supports run mode - Supports fan mode @@ -7,11 +9,14 @@ - Supports auxiliary heating ### Supported Run-Modes + - Sleep Mode - ECO Mode ## Entities + ### Default entity + | EntityID | Class | Description | |----------------------------|---------|----------------| | climate.{DEVICEID}_climate | climate | Climate entity | @@ -43,10 +48,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: eco_mode value: true -``` \ No newline at end of file +``` diff --git a/doc/CC_hans.md b/doc/CC_hans.md index a3ed26b8..13059d17 100644 --- a/doc/CC_hans.md +++ b/doc/CC_hans.md @@ -1,5 +1,7 @@ # 中央空调Wi-Fi线控器 + ## 特性 + - 支持目标温度设定 - 支持运行模式设定 - 支持风扇模式设定 @@ -7,11 +9,14 @@ - 支持电辅热 ### 支持的模式 + - 睡眠模式 - 节能模式 ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |----------------------------|---------|-------| | climate.{DEVICEID}_climate | climate | 恒温器实体 | @@ -29,6 +34,7 @@ | switch.{DEVICEID}_swing | switch | Swing | 摆风 | ## 服务 + 生成以下扩展服务 ### midea_ac_lan.set_attribute @@ -44,10 +50,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: eco_mode value: true -``` \ No newline at end of file +``` diff --git a/doc/CD_hans.md b/doc/CD_hans.md index 98b1cfbc..05d92315 100644 --- a/doc/CD_hans.md +++ b/doc/CD_hans.md @@ -1,6 +1,7 @@ # 空气能热水器 ## 特性 + - 支持温度设定 设置温度调整步长 (默认为1). @@ -10,7 +11,9 @@ ``` ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |--------------------------------------|--------------|-------| | water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | @@ -25,7 +28,6 @@ | binary_sensor.{DEVICEID}_compressor_status | binary_sensor | Compressor Status | 压缩机状态 (可能在部分机型上无法正确显示状态) | | switch.{DEVICEID}_power | switch | Power | 电源 | - ## 服务 ### midea_ac_lan.set_attribute @@ -41,10 +43,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: power value: false -``` \ No newline at end of file +``` diff --git a/doc/CE.md b/doc/CE.md index afca06f3..11d120fc 100644 --- a/doc/CE.md +++ b/doc/CE.md @@ -3,6 +3,7 @@ ***Note: Some Midea appliance be named "Fresh Air Appliance", the protocol that actually uses the air conditioner. If your fresh air appliance is identified as an air conditioner, check out [Build-in fresh air system](CE.md#Build-in%20fresh%20air%20system)*** ## Features + - Supports fan speed - Supports preset mode @@ -15,7 +16,9 @@ Set the levels of fan except "Auto" (7 by default). ``` ## Entities + ### Default entity + | EntityID | Class | Description | |--------------------|-------|-------------| | fan.{DEVICEID}_fan | fan | Fan entity | @@ -41,7 +44,6 @@ Set the levels of fan except "Auto" (7 by default). ## Services - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -55,6 +57,7 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: diff --git a/doc/CE_hans.md b/doc/CE_hans.md index 4ee1e2da..0c043d47 100644 --- a/doc/CE_hans.md +++ b/doc/CE_hans.md @@ -3,6 +3,7 @@ ***注意:部分美的的"中央新风机"产品,其实使用了空调的协议。如果你的新风机被识别为空调,参阅[内置新风系统](AC_hans.md#%E5%86%85%E7%BD%AE%E6%96%B0%E9%A3%8E%E7%B3%BB%E7%BB%9F)*** ## 特性 + - 支持风速调节 - 支持预设模式 @@ -15,7 +16,9 @@ ``` ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |--------------------|-----|------| | fan.{DEVICEID}_fan | fan | 风扇实体 | @@ -40,6 +43,7 @@ | switch.{DEVICEID}_sleep_mode | switch | Sleep Mode | 睡眠模式开关 | ## 服务 + 生成以下扩展服务 ### midea_ac_lan.set_attribute @@ -55,6 +59,7 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: diff --git a/doc/CF.md b/doc/CF.md index dc89c24f..d8a063f6 100644 --- a/doc/CF.md +++ b/doc/CF.md @@ -1,11 +1,15 @@ # Heat Bump + ## Features + - Supports target temperature - Supports run mode - Supports auxiliary heating ## Entities + ### Default entity + | EntityID | Class | Description | |----------------------------|---------|----------------| | climate.{DEVICEID}_climate | climate | Climate entity | @@ -20,7 +24,6 @@ ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -34,10 +37,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: aux_heating value: true -``` \ No newline at end of file +``` diff --git a/doc/CF_hans.md b/doc/CF_hans.md index 28d74641..b4943532 100644 --- a/doc/CF_hans.md +++ b/doc/CF_hans.md @@ -1,11 +1,15 @@ # 中央空调暖家 + ## 特性 + - 支持目标温度设定 - 支持运行模式设定 - 支持电辅热 ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |----------------------------|---------|-------| | climate.{DEVICEID}_climate | climate | 恒温器实体 | @@ -33,10 +37,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: aux_heating value: true -``` \ No newline at end of file +``` diff --git a/doc/DA.md b/doc/DA.md index 0a41db9f..49799269 100644 --- a/doc/DA.md +++ b/doc/DA.md @@ -1,7 +1,9 @@ # Top Load Washer ## Entities + ### Default entity + No default entity ### Extra entities @@ -15,7 +17,6 @@ No default entity ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -29,10 +30,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: power value: true -``` \ No newline at end of file +``` diff --git a/doc/DA_hans.md b/doc/DA_hans.md index f8ea513e..bd567d2c 100644 --- a/doc/DA_hans.md +++ b/doc/DA_hans.md @@ -1,7 +1,9 @@ # 波轮洗衣机 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -14,6 +16,7 @@ | switch.{DEVICEID}_start | switch | Start | 启动暂停 | ## 服务 + 生成以下扩展服务 ### midea_ac_lan.set_attribute @@ -29,10 +32,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: power value: true -``` \ No newline at end of file +``` diff --git a/doc/DB.md b/doc/DB.md index 8be5a6a9..958c3216 100644 --- a/doc/DB.md +++ b/doc/DB.md @@ -1,7 +1,9 @@ # Front Load Washer ## Entities + ### Default entity + No default entity ### Extra entities @@ -15,7 +17,6 @@ No default entity ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -29,10 +30,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: power value: true -``` \ No newline at end of file +``` diff --git a/doc/DB_hans.md b/doc/DB_hans.md index 34ac0110..46d15aa9 100644 --- a/doc/DB_hans.md +++ b/doc/DB_hans.md @@ -1,7 +1,9 @@ # 滚筒洗衣机 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -28,10 +30,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: power value: true -``` \ No newline at end of file +``` diff --git a/doc/DC.md b/doc/DC.md index 66eb297b..3bd63e01 100644 --- a/doc/DC.md +++ b/doc/DC.md @@ -1,7 +1,9 @@ # Clothes Dryer ## Entities + ### Default entity + No default entity ### Extra entities @@ -15,7 +17,6 @@ No default entity ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -29,10 +30,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: power value: true -``` \ No newline at end of file +``` diff --git a/doc/DC_hans.md b/doc/DC_hans.md index 3ca63cff..32df0611 100644 --- a/doc/DC_hans.md +++ b/doc/DC_hans.md @@ -1,7 +1,9 @@ # 干衣机 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -28,10 +30,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: power value: true -``` \ No newline at end of file +``` diff --git a/doc/E1.md b/doc/E1.md index 1550a964..b35f140a 100644 --- a/doc/E1.md +++ b/doc/E1.md @@ -1,7 +1,9 @@ # Dishwasher ## Entities + ### Default entity + No default entity ### Extra entities @@ -27,7 +29,6 @@ No default entity ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -41,10 +42,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: power value: true -``` \ No newline at end of file +``` diff --git a/doc/E1_hans.md b/doc/E1_hans.md index 6ce6a45b..8d24d683 100644 --- a/doc/E1_hans.md +++ b/doc/E1_hans.md @@ -1,7 +1,9 @@ # 洗碗机 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -26,6 +28,7 @@ | switch.{DEVICEID}_storage | switch | Storage | 保管开关 | ## 服务 + 生成以下扩展服务 ### midea_ac_lan.set_attribute @@ -41,10 +44,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: power value: true -``` \ No newline at end of file +``` diff --git a/doc/E2.md b/doc/E2.md index 2ed23381..efb72367 100644 --- a/doc/E2.md +++ b/doc/E2.md @@ -1,9 +1,11 @@ # Electric Water Heater + ## Features -- Supports target temperature +- Supports target temperature ## Customize + - Set the protocol of device ("auto" by default). There are 2 different protocol version to control Electric Water Heater, "old protocol" or "new protocol". If you can't control your device, try change this item and see if it works. @@ -14,7 +16,9 @@ The options include true, false, and "auto". ``` ## Entities + ### Default entity + | EntityID | Class | Description | |--------------------------------------|--------------|---------------------| | water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | @@ -35,7 +39,6 @@ The options include true, false, and "auto". ## Services - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -49,10 +52,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: variable_heating value: true -``` \ No newline at end of file +``` diff --git a/doc/E2_hans.md b/doc/E2_hans.md index 90be96a3..1d539979 100644 --- a/doc/E2_hans.md +++ b/doc/E2_hans.md @@ -1,8 +1,11 @@ # 电热水器 + ## 特性 + - 支持温度设定 ## 自定义 + - 设置设备控制协议 (默认为"auto"). 美的使用了两种不同的协议来控制电热水器,"旧协议" 或 "新协议". 如果你无法控制你的热水器,可以尝试改变此项的值试试看。 @@ -13,7 +16,9 @@ ``` ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |--------------------------------------|--------------|-------| | water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | @@ -47,10 +52,11 @@ | value | true 或 false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: variable_heating value: true -``` \ No newline at end of file +``` diff --git a/doc/E3.md b/doc/E3.md index e9afee87..420f740b 100644 --- a/doc/E3.md +++ b/doc/E3.md @@ -1,7 +1,8 @@ # Gas Water Heater + ## Features -- Supports target temperature +- Supports target temperature ## Customize @@ -14,7 +15,9 @@ ``` ## Entities + ### Default entity + | EntityID | Class | Description | |--------------------------------------|--------------|---------------------| | water_heater.{DEVICEID}_water_heater | water_heater | Water heater entity | @@ -33,7 +36,6 @@ ## Services - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -47,10 +49,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: smart_volume value: true -``` \ No newline at end of file +``` diff --git a/doc/E3_hans.md b/doc/E3_hans.md index 53864e2a..35eeb79f 100644 --- a/doc/E3_hans.md +++ b/doc/E3_hans.md @@ -1,5 +1,7 @@ # 燃气热水器 + ## 特性 + - 支持温度设定 ## 自定义 @@ -13,7 +15,9 @@ ``` ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |--------------------------------------|--------------|-------| | water_heater.{DEVICEID}_water_heater | water_heater | 热水器实体 | @@ -45,10 +49,11 @@ | value | true or false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: smart_volume value: true -``` \ No newline at end of file +``` diff --git a/doc/E6.md b/doc/E6.md index 677f32f0..f54964e9 100644 --- a/doc/E6.md +++ b/doc/E6.md @@ -1,7 +1,9 @@ # Gas Boilers ## Entities + ### Default entity + | EntityID | Class | Description | |----------------------------------------------|--------------|-----------------------------| | water_heater.{DEVICEID}_water_heater_heating | water_heater | Heating water heater entity | @@ -20,7 +22,6 @@ ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -34,10 +35,11 @@ Set the attribute of appliance. Service data: | value | true or false | Example + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: main_power value: true -``` \ No newline at end of file +``` diff --git a/doc/E6_hans.md b/doc/E6_hans.md index 1f3ca6a8..6d481ab8 100644 --- a/doc/E6_hans.md +++ b/doc/E6_hans.md @@ -1,7 +1,9 @@ # 壁挂炉 ## 生成实体 + ### 默认实体 + | 实体ID | 类型 | 描述 | |----------------------------------------------|--------------|---------| | water_heater.{DEVICEID}_water_heater_heating | water_heater | 取暖热水器实体 | @@ -33,10 +35,11 @@ | value | true or false | 示例 + ```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: main_power value: true -``` \ No newline at end of file +``` diff --git a/doc/E8.md b/doc/E8.md index 0af848e3..e2c75c6b 100644 --- a/doc/E8.md +++ b/doc/E8.md @@ -1,7 +1,9 @@ # Electric Slow Cooker ## Entities + ### Default entity + No default entity. ### Extra entities @@ -17,6 +19,6 @@ No default entity. | sensor.{DEVICEID}_target_temperature | sensor | Target Temperature | | sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | - ## Service -No services. \ No newline at end of file + +No services. diff --git a/doc/E8_hans.md b/doc/E8_hans.md index 05755269..605603c3 100644 --- a/doc/E8_hans.md +++ b/doc/E8_hans.md @@ -1,7 +1,9 @@ # 慢炖锅 ## 实体 + ### 默认实体 + 无默认实体 ### 扩展实体 @@ -17,6 +19,6 @@ | sensor.{DEVICEID}_target_temperature | sensor | Target Temperature | 设定温度 | | sensor.{DEVICEID}_current_temperature | sensor | Current Temperature | 当前温度 | - ## 服务 -无服务 \ No newline at end of file + +无服务 diff --git a/doc/EA.md b/doc/EA.md index b07c5e6d..1e51d4d9 100644 --- a/doc/EA.md +++ b/doc/EA.md @@ -1,7 +1,9 @@ # Electric Rice Cooker ## Entities + ### Default entity + No default entity. ### Extra entities @@ -18,4 +20,5 @@ No default entity. | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | ## Service -No services. \ No newline at end of file + +No services. diff --git a/doc/EA_hans.md b/doc/EA_hans.md index 5e79971b..8fb91828 100644 --- a/doc/EA_hans.md +++ b/doc/EA_hans.md @@ -1,7 +1,9 @@ # 电饭煲 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -18,4 +20,5 @@ | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | ## 服务 -无服务 \ No newline at end of file + +无服务 diff --git a/doc/EC.md b/doc/EC.md index f4907aae..ed9d7bec 100644 --- a/doc/EC.md +++ b/doc/EC.md @@ -1,7 +1,9 @@ # Electric Pressure Cooker ## Entities + ### Default entity + No default entity. ### Extra entities @@ -18,4 +20,5 @@ No default entity. | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | ## Service -No services. \ No newline at end of file + +No services. diff --git a/doc/EC_hans.md b/doc/EC_hans.md index d65529b7..5dcf98f6 100644 --- a/doc/EC_hans.md +++ b/doc/EC_hans.md @@ -1,7 +1,9 @@ # 电压力锅 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -18,4 +20,5 @@ | sensor.{DEVICEID}_top_temperature | sensor | Top Temperature | 顶部温度 | ## 服务 -无服务 \ No newline at end of file + +无服务 diff --git a/doc/ED.md b/doc/ED.md index cfce3672..fffd2401 100644 --- a/doc/ED.md +++ b/doc/ED.md @@ -1,7 +1,9 @@ # Water Drinking Appliance ## Entities + ### Default entity + No default entity. ### Extra entities @@ -21,4 +23,5 @@ No default entity. | switch.{DEVICEID}_power | switch | Power | ## Service -No services. \ No newline at end of file + +No services. diff --git a/doc/ED_hans.md b/doc/ED_hans.md index 4bfd17c4..02316e96 100644 --- a/doc/ED_hans.md +++ b/doc/ED_hans.md @@ -1,7 +1,9 @@ # 饮用水设备 ## 生成实体 + ### 默认实体 + 无默认实体 ### 额外生成实体 @@ -21,4 +23,5 @@ | switch.{DEVICEID}_power | switch | Power | 电源开关 | ## 服务 + 无服务 \ No newline at end of file diff --git a/doc/FA.md b/doc/FA.md index e8f7323d..cfc8d86b 100644 --- a/doc/FA.md +++ b/doc/FA.md @@ -1,5 +1,7 @@ # Fan + ## Features + - Supports fan speed - Supports preset mode - Supports oscillation @@ -14,7 +16,9 @@ Set the levels of the fan device except "Off" (3 by default). ``` ## Entities + ### Default entity + | EntityID | Class | Description | |--------------------|-------|-------------| | fan.{DEVICEID}_fan | fan | Fan entity | @@ -63,7 +67,8 @@ Set the attribute of appliance. Service data: | value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | Example -``` + +```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX @@ -71,10 +76,10 @@ data: value: true ``` -``` +```yaml service: midea_ac_lan.set_attribute data: device_id: XXXXXXXXXXXX attribute: oscillation_angle value: "90" -``` \ No newline at end of file +``` diff --git a/doc/FA_hans.md b/doc/FA_hans.md index 962f14b2..33426dd0 100644 --- a/doc/FA_hans.md +++ b/doc/FA_hans.md @@ -1,5 +1,7 @@ # 电风扇 + ## 特性 + - 支持风速调节 - 支持预设模式 - 支持水平摆头 @@ -14,7 +16,9 @@ ``` ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |--------------------|-----|------| | fan.{DEVICEID}_fan | fan | 风扇实体 | @@ -63,6 +67,7 @@ | value | "Off"
"30"
"60"
"90"
"120"
"180"
"360"
"+60"
"-60"
"40" | 示例 + ```yaml service: midea_ac_lan.set_attribute data: @@ -77,4 +82,4 @@ data: device_id: XXXXXXXXXXXX attribute: oscillation_angle value: "90" -``` \ No newline at end of file +``` diff --git a/doc/FB.md b/doc/FB.md index 222b6d9c..a09062fc 100644 --- a/doc/FB.md +++ b/doc/FB.md @@ -1,11 +1,14 @@ # Electric Heater ## Features + - Supports target temperature - Supports preset mode ## Entities + ### Default entity + | EntityID | Class | Description | |----------------------------|---------|----------------| | climate.{DEVICEID}_climate | climate | Climate entity | @@ -40,6 +43,7 @@ Set the attribute of appliance. Service data: | value | 1 - 10 | Example + ```yaml service: midea_ac_lan.set_attribute data: @@ -54,4 +58,4 @@ data: device_id: XXXXXXXXXXXX attribute: heating_level value: 9 -``` \ No newline at end of file +``` diff --git a/doc/FB_hans.md b/doc/FB_hans.md index 217dd5eb..a5a84463 100644 --- a/doc/FB_hans.md +++ b/doc/FB_hans.md @@ -1,11 +1,14 @@ # 电取暖器 ## Features + - 支持目标温度设定 - 支持预设模式设定 ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |----------------------------|---------|-------| | climate.{DEVICEID}_climate | climate | 恒温器实体 | @@ -40,6 +43,7 @@ | value | 1 - 10 | 示例 + ```yaml service: midea_ac_lan.set_attribute data: @@ -54,4 +58,4 @@ data: device_id: XXXXXXXXXXXX attribute: heating_level value: 9 -``` \ No newline at end of file +``` diff --git a/doc/FC.md b/doc/FC.md index d02e20c6..e71b41bd 100644 --- a/doc/FC.md +++ b/doc/FC.md @@ -9,7 +9,9 @@ Set the high/low value of PM2.5 to automatically turn standby mode on or off. ``` ## Entities + ### Default entity + No default entity. ### Extra entities @@ -33,7 +35,6 @@ No default entity. ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -71,6 +72,7 @@ Set the attribute of appliance. Service data: | value | "Bright"
"Dim"
"Off" | Example + ```yaml service: midea_ac_lan.set_attribute data: @@ -85,4 +87,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Auto -``` \ No newline at end of file +``` diff --git a/doc/FC_hans.md b/doc/FC_hans.md index e1cdfc13..a01ee8ac 100644 --- a/doc/FC_hans.md +++ b/doc/FC_hans.md @@ -9,7 +9,9 @@ ``` ## 生成实体 + ### 默认生成实体 + 无默认实体 ### 额外生成实体 @@ -70,6 +72,7 @@ | value | "Bright"
"Dim"
"Off" | 示例 + ```yaml service: midea_ac_lan.set_attribute data: @@ -84,4 +87,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Auto -``` \ No newline at end of file +``` diff --git a/doc/FD.md b/doc/FD.md index c71371be..325d4728 100644 --- a/doc/FD.md +++ b/doc/FD.md @@ -1,11 +1,15 @@ # Humidifier + ## Features + - Supports preset mode - Supports fan mode - Supports humidity setting ## Entities + ### Default entity + | EntityID | Class | Description | |----------------------------------|------------|-------------------| | humidifier.{DEVICEID}_humidifier | humidifier | Humidifier entity | @@ -24,7 +28,6 @@ ## Service - ### midea_ac_lan.set_attribute [![Service](https://my.home-assistant.io/badges/developer_call_service.svg)](https://my.home-assistant.io/redirect/developer_call_service/?service=midea_ac_lan.set_attribute) @@ -50,6 +53,7 @@ Set the attribute of appliance. Service data: | value | "Bright"
"Dim"
"Off" | Example + ```yaml service: midea_ac_lan.set_attribute data: @@ -64,4 +68,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Medium -``` \ No newline at end of file +``` diff --git a/doc/FD_hans.md b/doc/FD_hans.md index ba6be127..3cd9f5fe 100644 --- a/doc/FD_hans.md +++ b/doc/FD_hans.md @@ -1,11 +1,15 @@ # 加湿器 + ## 特性 + - 支持运行模式 - 支持风扇模式设定 - 支持湿度设定 ## 生成实体 + ### 默认生成实体 + | 实体ID | 类型 | 描述 | |----------------------------------|------------|-------| | humidifier.{DEVICEID}_humidifier | humidifier | 加湿器实体 | @@ -49,6 +53,7 @@ | value | "Bright"
"Dim"
"Off" | 示例 + ```yaml service: midea_ac_lan.set_attribute data: @@ -63,4 +68,4 @@ data: device_id: XXXXXXXXXXXX attribute: fan_speed value: Medium -``` \ No newline at end of file +``` diff --git a/scripts/run.sh b/scripts/run.sh index 1c529a66..035649a3 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -6,8 +6,8 @@ cd "$(dirname "$0")/.." # Create config dir if not present if [[ ! -d "${PWD}/config" ]]; then - mkdir -p "${PWD}/config" - hass --config "${PWD}/config" --script ensure_config + mkdir -p "${PWD}/config" + hass --config "${PWD}/config" --script ensure_config fi # Set the path to custom_components diff --git a/scripts/setup.sh b/scripts/setup.sh index 692cc8b7..43421316 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -8,6 +8,6 @@ python3 -m pip install --requirement requirements.txt # Create config dir if not present if [[ ! -d "${PWD}/config" ]]; then - mkdir -p "${PWD}/config" - hass --config "${PWD}/config" --script ensure_config -fi \ No newline at end of file + mkdir -p "${PWD}/config" + hass --config "${PWD}/config" --script ensure_config +fi