diff --git a/README.md b/README.md index 32ae23a1..b67caff2 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,152 @@ -# 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) - -> :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/wuwentao/midea_ac_lan)](https://github.com/wuwentao/midea_ac_lan/releases/latest) + +> :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 353564b4..bcf132a7 100644 --- a/README_hans.md +++ b/README_hans.md @@ -1,147 +1,147 @@ -# 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) - -[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) \ No newline at end of file +# 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) + +[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) \ No newline at end of file diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py index 330f5e15..1125f120 100644 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -1,191 +1,191 @@ -import logging -import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from .const import ( - DOMAIN, - CONF_ACCOUNT, - CONF_KEY, - CONF_MODEL, - CONF_SUBTYPE, - CONF_REFRESH_INTERVAL, - DEVICES, - EXTRA_SENSOR, - EXTRA_SWITCH, - EXTRA_CONTROL, - 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 - -_LOGGER = logging.getLogger(__name__) - - -async def update_listener(hass, config_entry): - 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, 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: - attributes.append(attribute_name.value) - - def service_set_attribute(service): - 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): - 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): - 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): - 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 +import logging +import voluptuous as vol +import homeassistant.helpers.config_validation as cv +from .const import ( + DOMAIN, + CONF_ACCOUNT, + CONF_KEY, + CONF_MODEL, + CONF_SUBTYPE, + CONF_REFRESH_INTERVAL, + DEVICES, + EXTRA_SENSOR, + EXTRA_SWITCH, + EXTRA_CONTROL, + 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 + +_LOGGER = logging.getLogger(__name__) + + +async def update_listener(hass, config_entry): + 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, 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: + attributes.append(attribute_name.value) + + def service_set_attribute(service): + 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): + 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): + 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): + 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 a449970d..860ed591 100644 --- a/custom_components/midea_ac_lan/binary_sensor.py +++ b/custom_components/midea_ac_lan/binary_sensor.py @@ -1,32 +1,32 @@ -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 .midea_devices import MIDEA_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, [] - ) - 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): - @property - def device_class(self): - return self._config.get("device_class") - - @property - def is_on(self): - return self._device.get_attribute(self._entity_key) +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 .midea_devices import MIDEA_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, [] + ) + 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): + @property + def device_class(self): + return self._config.get("device_class") + + @property + def is_on(self): + 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 b5a6c9db..640e67bc 100644 --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -1,519 +1,519 @@ -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, - PRESET_ECO, - PRESET_NONE, - PRESET_SLEEP, - SWING_BOTH, - SWING_HORIZONTAL, - SWING_OFF, - SWING_ON, - SWING_VERTICAL, -) -from homeassistant.const import ( - MAJOR_VERSION, - MINOR_VERSION, - Platform, - UnitOfTemperature, - PRECISION_WHOLE, - PRECISION_HALVES, - ATTR_TEMPERATURE, - CONF_DEVICE_ID, - CONF_SWITCHES, -) - -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 -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 - -import logging -_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): - 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): - - # 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): - super().__init__(device, entity_key) - - @property - def supported_features(self): - 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): - return TEMPERATURE_MIN - - @property - def max_temp(self): - return TEMPERATURE_MAX - - @property - def temperature_unit(self): - return UnitOfTemperature.CELSIUS - - @property - def target_temperature_low(self): - return TEMPERATURE_MIN - - @property - def target_temperature_high(self): - return TEMPERATURE_MAX - - @property - def hvac_modes(self): - return self._modes - - @property - def swing_modes(self): - return self._swing_modes - - @property - def is_on(self) -> bool: - return self.hvac_mode != HVACMode.OFF - - @property - def hvac_mode(self) -> str: - if self._device.get_attribute("power"): - return self._modes[self._device.get_attribute("mode")] - else: - return HVACMode.OFF - - @property - def target_temperature(self): - return self._device.get_attribute("target_temperature") - - @property - def current_temperature(self): - return self._device.get_attribute("indoor_temperature") - - @property - def preset_modes(self): - return self._preset_modes - - @property - def preset_mode(self): - 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: - return self._device.attributes - - 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 set_temperature(self, **kwargs) -> None: - 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: - 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: - 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): - 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): - def __init__(self, device, entity_key): - 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): - return list(self._fan_speeds.keys()) - - @property - 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: - 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): - 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) - return self._swing_modes[swing_mode] - - @property - def outdoor_temperature(self): - return self._device.get_attribute(ACAttributes.outdoor_temperature) - - def set_fan_mode(self, fan_mode: str) -> None: - 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: - 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): - def __init__(self, device, entity_key): - 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): - return self._device.fan_modes - - @property - def fan_mode(self) -> str: - return self._device.get_attribute(CCAttributes.fan_speed) - - @property - def target_temperature_step(self): - 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() - - def set_fan_mode(self, fan_mode: str) -> None: - self._device.set_attribute(attr=CCAttributes.fan_speed, value=fan_mode) - - def set_swing_mode(self, swing_mode: str) -> None: - self._device.set_attribute( - attr=CCAttributes.swing, - value=swing_mode.capitalize() == SWING_ON.capitalize() - ) - - -class MideaCFClimate(MideaClimate): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] - - @property - def supported_features(self): - 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): - return PRECISION_WHOLE - - @property - def min_temp(self): - return self._device.get_attribute(CFAttributes.min_temperature) - - @property - def max_temp(self): - return self._device.get_attribute(CFAttributes.max_temperature) - - @property - def target_temperature_low(self): - return self._device.get_attribute(CFAttributes.min_temperature) - - @property - def target_temperature_high(self): - return self._device.get_attribute(CFAttributes.max_temperature) - - @property - def current_temperature(self): - return self._device.get_attribute(CFAttributes.current_temperature) - - -class MideaC3Climate(MideaClimate): - _powers = [ - C3Attributes.zone1_power, - C3Attributes.zone2_power, - ] - - def __init__(self, device, entity_key, zone): - 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): - 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): - return PRECISION_WHOLE if \ - self._device.get_attribute(C3Attributes.zone_temp_type)[self._zone] else PRECISION_HALVES - - @property - def min_temp(self): - return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] - - @property - def max_temp(self): - return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] - - @property - def target_temperature_low(self): - return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] - - @property - def target_temperature_high(self): - return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] - - 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) - - @property - 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 - - @property - def target_temperature(self): - return self._device.get_attribute(C3Attributes.target_temperature)[self._zone] - - @property - def current_temperature(self): - return None - - 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 - 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: - 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): - def __init__(self, device, entity_key): - 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 - if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): - features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON - return features - - @property - def target_temperature_step(self): - return PRECISION_WHOLE - - @property - def preset_modes(self): - return self._preset_modes - - @property - def preset_mode(self): - return self._device.get_attribute(attr=FBAttributes.mode) - - @property - def min_temp(self): - return 5 - - @property - def max_temp(self): - return 35 - - @property - def target_temperature_low(self): - return 5 - - @property - def target_temperature_high(self): - return 35 - - @property - def hvac_mode(self) -> str: - return HVACMode.HEAT if self._device.get_attribute(attr=FBAttributes.power) else HVACMode.OFF - - @property - def current_temperature(self): - return self._device.get_attribute(FBAttributes.current_temperature) - - 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 - 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: - 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: - self._device.set_attribute(attr=FBAttributes.mode,value=preset_mode) +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, + PRESET_ECO, + PRESET_NONE, + PRESET_SLEEP, + SWING_BOTH, + SWING_HORIZONTAL, + SWING_OFF, + SWING_ON, + SWING_VERTICAL, +) +from homeassistant.const import ( + MAJOR_VERSION, + MINOR_VERSION, + Platform, + UnitOfTemperature, + PRECISION_WHOLE, + PRECISION_HALVES, + ATTR_TEMPERATURE, + CONF_DEVICE_ID, + CONF_SWITCHES, +) + +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 +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 + +import logging +_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): + 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): + + # 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): + super().__init__(device, entity_key) + + @property + def supported_features(self): + 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): + return TEMPERATURE_MIN + + @property + def max_temp(self): + return TEMPERATURE_MAX + + @property + def temperature_unit(self): + return UnitOfTemperature.CELSIUS + + @property + def target_temperature_low(self): + return TEMPERATURE_MIN + + @property + def target_temperature_high(self): + return TEMPERATURE_MAX + + @property + def hvac_modes(self): + return self._modes + + @property + def swing_modes(self): + return self._swing_modes + + @property + def is_on(self) -> bool: + return self.hvac_mode != HVACMode.OFF + + @property + def hvac_mode(self) -> str: + if self._device.get_attribute("power"): + return self._modes[self._device.get_attribute("mode")] + else: + return HVACMode.OFF + + @property + def target_temperature(self): + return self._device.get_attribute("target_temperature") + + @property + def current_temperature(self): + return self._device.get_attribute("indoor_temperature") + + @property + def preset_modes(self): + return self._preset_modes + + @property + def preset_mode(self): + 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: + return self._device.attributes + + 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 set_temperature(self, **kwargs) -> None: + 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: + 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: + 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): + 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): + def __init__(self, device, entity_key): + 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): + return list(self._fan_speeds.keys()) + + @property + 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: + 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): + 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) + return self._swing_modes[swing_mode] + + @property + def outdoor_temperature(self): + return self._device.get_attribute(ACAttributes.outdoor_temperature) + + def set_fan_mode(self, fan_mode: str) -> None: + 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: + 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): + def __init__(self, device, entity_key): + 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): + return self._device.fan_modes + + @property + def fan_mode(self) -> str: + return self._device.get_attribute(CCAttributes.fan_speed) + + @property + def target_temperature_step(self): + 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() + + def set_fan_mode(self, fan_mode: str) -> None: + self._device.set_attribute(attr=CCAttributes.fan_speed, value=fan_mode) + + def set_swing_mode(self, swing_mode: str) -> None: + self._device.set_attribute( + attr=CCAttributes.swing, + value=swing_mode.capitalize() == SWING_ON.capitalize() + ) + + +class MideaCFClimate(MideaClimate): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + self._modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] + + @property + def supported_features(self): + 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): + return PRECISION_WHOLE + + @property + def min_temp(self): + return self._device.get_attribute(CFAttributes.min_temperature) + + @property + def max_temp(self): + return self._device.get_attribute(CFAttributes.max_temperature) + + @property + def target_temperature_low(self): + return self._device.get_attribute(CFAttributes.min_temperature) + + @property + def target_temperature_high(self): + return self._device.get_attribute(CFAttributes.max_temperature) + + @property + def current_temperature(self): + return self._device.get_attribute(CFAttributes.current_temperature) + + +class MideaC3Climate(MideaClimate): + _powers = [ + C3Attributes.zone1_power, + C3Attributes.zone2_power, + ] + + def __init__(self, device, entity_key, zone): + 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): + 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): + return PRECISION_WHOLE if \ + self._device.get_attribute(C3Attributes.zone_temp_type)[self._zone] else PRECISION_HALVES + + @property + def min_temp(self): + return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] + + @property + def max_temp(self): + return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] + + @property + def target_temperature_low(self): + return self._device.get_attribute(C3Attributes.temperature_min)[self._zone] + + @property + def target_temperature_high(self): + return self._device.get_attribute(C3Attributes.temperature_max)[self._zone] + + 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) + + @property + 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 + + @property + def target_temperature(self): + return self._device.get_attribute(C3Attributes.target_temperature)[self._zone] + + @property + def current_temperature(self): + return None + + 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 + 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: + 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): + def __init__(self, device, entity_key): + 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 + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + return features + + @property + def target_temperature_step(self): + return PRECISION_WHOLE + + @property + def preset_modes(self): + return self._preset_modes + + @property + def preset_mode(self): + return self._device.get_attribute(attr=FBAttributes.mode) + + @property + def min_temp(self): + return 5 + + @property + def max_temp(self): + return 35 + + @property + def target_temperature_low(self): + return 5 + + @property + def target_temperature_high(self): + return 35 + + @property + def hvac_mode(self) -> str: + return HVACMode.HEAT if self._device.get_attribute(attr=FBAttributes.power) else HVACMode.OFF + + @property + def current_temperature(self): + return self._device.get_attribute(FBAttributes.current_temperature) + + 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 + 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: + 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: + 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..16433b0a 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -1,490 +1,490 @@ -import voluptuous as vol -import os -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 -) -from homeassistant import config_entries -from homeassistant.core import callback -from homeassistant.const import ( - CONF_NAME, - CONF_DEVICE, - CONF_TOKEN, - CONF_DEVICE_ID, - CONF_TYPE, - CONF_IP_ADDRESS, - CONF_PROTOCOL, - CONF_PORT, - CONF_SWITCHES, - CONF_SENSORS, - CONF_CUSTOMIZE, - CONF_PASSWORD, -) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.aiohttp_client import async_create_clientsession -from .midea.core.discover import discover -from .midea.core.cloud import get_midea_cloud -from .midea.core.device import MiedaDevice -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): - 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): - 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): - 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): - 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): - 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): - 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): - 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): - 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): - 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): - 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"{'<font color=gree>YES</font>' if supported else '<font color=red>NO</font>'}" - 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): - 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): - 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(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 - ) - - async def async_step_manually(self, user_input=None, error=None): - 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): - return OptionsFlowHandler(config_entry) - - -class OptionsFlowHandler(config_entries.OptionsFlow): - def __init__(self, config_entry: config_entries.ConfigEntry): - 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): - 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 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"): - 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 - ) +import voluptuous as vol +import os +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 +) +from homeassistant import config_entries +from homeassistant.core import callback +from homeassistant.const import ( + CONF_NAME, + CONF_DEVICE, + CONF_TOKEN, + CONF_DEVICE_ID, + CONF_TYPE, + CONF_IP_ADDRESS, + CONF_PROTOCOL, + CONF_PORT, + CONF_SWITCHES, + CONF_SENSORS, + CONF_CUSTOMIZE, + CONF_PASSWORD, +) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.aiohttp_client import async_create_clientsession +from .midea.core.discover import discover +from .midea.core.cloud import get_midea_cloud +from .midea.core.device import MiedaDevice +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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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"{'<font color=gree>YES</font>' if supported else '<font color=red>NO</font>'}" + 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): + 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): + 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(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 + ) + + async def async_step_manually(self, user_input=None, error=None): + 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): + return OptionsFlowHandler(config_entry) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + def __init__(self, config_entry: config_entries.ConfigEntry): + 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): + 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 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"): + 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 e3897533..dc5439db 100644 --- a/custom_components/midea_ac_lan/const.py +++ b/custom_components/midea_ac_lan/const.py @@ -1,15 +1,15 @@ -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 +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 7fda6b32..bc49c8f4 100644 --- a/custom_components/midea_ac_lan/fan.py +++ b/custom_components/midea_ac_lan/fan.py @@ -1,202 +1,202 @@ -from typing import Any - -from homeassistant.components.fan import FanEntity, FanEntityFeature -from homeassistant.const import ( - Platform, - CONF_DEVICE_ID, - CONF_SWITCHES, - STATE_ON, - STATE_OFF -) -from .const import ( - DOMAIN, - DEVICES, -) -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): - 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): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - - def turn_on( - self, - percentage: int | None = None, - preset_mode: str | None = None, - **kwargs: Any, - ) -> None: - 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): - return self._device.preset_modes if hasattr(self._device, "preset_modes") else None - - @property - def is_on(self) -> bool: - return self._device.get_attribute("power") - - @property - def oscillating(self): - return self._device.get_attribute("oscillate") - - @property - def preset_mode(self): - return self._device.get_attribute("mode") - - @property - def fan_speed(self): - return self._device.get_attribute("fan_speed") - - def turn_off(self): - self._device.set_attribute(attr="power", value=False) - - def toggle(self): - toggle = not self.is_on - self._device.set_attribute(attr="power", value=toggle) - - def oscillate(self, oscillating: bool): - self._device.set_attribute(attr="oscillate", value=oscillating) - - def set_preset_mode(self, preset_mode: str): - self._device.set_attribute(attr="mode", value=preset_mode.capitalize()) - - @property - def percentage(self): - return round(self.fan_speed * self.percentage_step) - - def set_percentage(self, percentage: int): - 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): - 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): - 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): - def __init__(self, device, entity_key): - 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): - def __init__(self, device, entity_key): - 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): - def __init__(self, device, entity_key): - 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): - 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 - - @property - def is_on(self) -> bool: - return self.state == STATE_ON - - @property - def fan_speed(self): - return self._device.get_attribute(ACAttributes.fresh_air_fan_speed) - - def turn_on(self, percentage, preset_mode, **kwargs): - self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=True) - - def turn_off(self): - self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=False) - - def toggle(self): - toggle = not self.is_on - self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=toggle) - - def set_percentage(self, percentage: int): - 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): - self._device.set_attribute(attr=ACAttributes.fresh_air_mode, value=preset_mode) - - @property - def preset_mode(self): - return self._device.get_attribute(attr=ACAttributes.fresh_air_mode) - - -class MideaCEFan(MideaFan): - def __init__(self, device, entity_key): - 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): - self._device.set_attribute(attr=CEAttributes.power, value=True) - - async def async_set_percentage(self, percentage: int): - await self.hass.async_add_executor_job(self.set_percentage, percentage) - - -class Midea40Fan(MideaFan): - def __init__(self, device, entity_key): - super().__init__(device, entity_key) - 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 - - def turn_on(self, percentage, preset_mode, **kwargs): - self._device.set_attribute(attr=X40Attributes.fan_speed, value=1) - - def turn_off(self): - self._device.set_attribute(attr=X40Attributes.fan_speed, value=0) +from typing import Any + +from homeassistant.components.fan import FanEntity, FanEntityFeature +from homeassistant.const import ( + Platform, + CONF_DEVICE_ID, + CONF_SWITCHES, + STATE_ON, + STATE_OFF +) +from .const import ( + DOMAIN, + DEVICES, +) +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): + 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): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + + def turn_on( + self, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, + ) -> None: + 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): + return self._device.preset_modes if hasattr(self._device, "preset_modes") else None + + @property + def is_on(self) -> bool: + return self._device.get_attribute("power") + + @property + def oscillating(self): + return self._device.get_attribute("oscillate") + + @property + def preset_mode(self): + return self._device.get_attribute("mode") + + @property + def fan_speed(self): + return self._device.get_attribute("fan_speed") + + def turn_off(self): + self._device.set_attribute(attr="power", value=False) + + def toggle(self): + toggle = not self.is_on + self._device.set_attribute(attr="power", value=toggle) + + def oscillate(self, oscillating: bool): + self._device.set_attribute(attr="oscillate", value=oscillating) + + def set_preset_mode(self, preset_mode: str): + self._device.set_attribute(attr="mode", value=preset_mode.capitalize()) + + @property + def percentage(self): + return round(self.fan_speed * self.percentage_step) + + def set_percentage(self, percentage: int): + 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): + 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): + 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): + def __init__(self, device, entity_key): + 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): + def __init__(self, device, entity_key): + 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): + def __init__(self, device, entity_key): + 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): + 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 + + @property + def is_on(self) -> bool: + return self.state == STATE_ON + + @property + def fan_speed(self): + return self._device.get_attribute(ACAttributes.fresh_air_fan_speed) + + def turn_on(self, percentage, preset_mode, **kwargs): + self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=True) + + def turn_off(self): + self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=False) + + def toggle(self): + toggle = not self.is_on + self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=toggle) + + def set_percentage(self, percentage: int): + 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): + self._device.set_attribute(attr=ACAttributes.fresh_air_mode, value=preset_mode) + + @property + def preset_mode(self): + return self._device.get_attribute(attr=ACAttributes.fresh_air_mode) + + +class MideaCEFan(MideaFan): + def __init__(self, device, entity_key): + 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): + self._device.set_attribute(attr=CEAttributes.power, value=True) + + async def async_set_percentage(self, percentage: int): + await self.hass.async_add_executor_job(self.set_percentage, percentage) + + +class Midea40Fan(MideaFan): + def __init__(self, device, entity_key): + super().__init__(device, entity_key) + 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 + + def turn_on(self, percentage, preset_mode, **kwargs): + self._device.set_attribute(attr=X40Attributes.fan_speed, value=1) + + def turn_off(self): + 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..35ec5a0a 100644 --- a/custom_components/midea_ac_lan/humidifier.py +++ b/custom_components/midea_ac_lan/humidifier.py @@ -1,112 +1,112 @@ -from homeassistant.components.humidifier import ( - HumidifierDeviceClass, - HumidifierEntity, - HumidifierEntityFeature, -) -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, [] - ) - 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 +from homeassistant.components.humidifier import ( + HumidifierDeviceClass, + HumidifierEntity, + HumidifierEntityFeature, +) +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, [] + ) + 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 91592496..df43e084 100644 --- a/custom_components/midea_ac_lan/lock.py +++ b/custom_components/midea_ac_lan/lock.py @@ -1,42 +1,42 @@ -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, [] - ) - 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 .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, [] + ) + 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/myenum.py similarity index 97% rename from custom_components/midea_ac_lan/midea/backports/enum.py rename to custom_components/midea_ac_lan/midea/backports/myenum.py index 03f626fe..c256cb8d 100644 --- a/custom_components/midea_ac_lan/midea/backports/enum.py +++ b/custom_components/midea_ac_lan/midea/backports/myenum.py @@ -1,34 +1,34 @@ -"""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. - """ +"""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") \ 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 index e92d55c2..12e396c4 100644 --- a/custom_components/midea_ac_lan/midea/core/cloud.py +++ b/custom_components/midea_ac_lan/midea/core/cloud.py @@ -1,647 +1,647 @@ -import logging -import time -import datetime -import json -import base64 -from threading import Lock -from aiohttp import ClientSession -from secrets import token_hex -from .security import CloudSecurity, MeijuCloudSecurity, MSmartCloudSecurity, MideaAirSecurity - -_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 logging +import time +import datetime +import json +import base64 +from threading import Lock +from aiohttp import ClientSession +from secrets import token_hex +from .security import CloudSecurity, MeijuCloudSecurity, MSmartCloudSecurity, MideaAirSecurity + +_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 24fa56c2..6d87d33c 100644 --- a/custom_components/midea_ac_lan/midea/core/crc8.py +++ b/custom_components/midea_ac_lan/midea/core/crc8.py @@ -1,46 +1,46 @@ -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 dc364673..1eaa24f4 100644 --- a/custom_components/midea_ac_lan/midea/core/device.py +++ b/custom_components/midea_ac_lan/midea/core/device.py @@ -1,395 +1,395 @@ -import threading -try: - from enum import StrEnum -except ImportError: - from ..backports.enum import StrEnum -from enum import IntEnum -from .security import LocalSecurity, MSGTYPE_HANDSHAKE_REQUEST, MSGTYPE_ENCRYPTED_REQUEST -from .packet_builder import PacketBuilder -from .message import ( - MessageType, - MessageQuestCustom, - MessageQueryAppliance, - MessageApplianceResponse -) -import socket -import logging -import time - -_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 = {decrypted.hex()}") - 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.myenum import StrEnum +from enum import IntEnum +from .security import LocalSecurity, MSGTYPE_HANDSHAKE_REQUEST, MSGTYPE_ENCRYPTED_REQUEST +from .packet_builder import PacketBuilder +from .message import ( + MessageType, + MessageQuestCustom, + MessageQueryAppliance, + MessageApplianceResponse +) +import socket +import logging +import time + +_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 = {decrypted.hex()}") + 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 d3f808a7..a4be3e26 100644 --- a/custom_components/midea_ac_lan/midea/core/discover.py +++ b/custom_components/midea_ac_lan/midea/core/discover.py @@ -1,174 +1,174 @@ -import logging -import socket -import ifaddr -from ipaddress import IPv4Network -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 +import ifaddr +from ipaddress import IPv4Network +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 5e38a6d8..4c744a45 100644 --- a/custom_components/midea_ac_lan/midea/core/message.py +++ b/custom_components/midea_ac_lan/midea/core/message.py @@ -1,265 +1,265 @@ -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 3470cd05..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,60 +1,60 @@ -from .security import LocalSecurity -import datetime - - -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 +from .security import LocalSecurity +import datetime + + +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 fc31bda2..4f9ac85d 100644 --- a/custom_components/midea_ac_lan/midea/core/security.py +++ b/custom_components/midea_ac_lan/midea/core/security.py @@ -1,256 +1,256 @@ -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 -from hashlib import md5, sha256 -from typing import Any -import hmac - - -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 as e: - 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"" +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 +from hashlib import md5, sha256 +from typing import Any +import hmac + + +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 as e: + 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 a8700311..279e0a91 100644 --- a/custom_components/midea_ac_lan/midea/devices/__init__.py +++ b/custom_components/midea_ac_lan/midea/devices/__init__.py @@ -1,46 +1,46 @@ -from homeassistant.core import HomeAssistant -from importlib import import_module -from types import ModuleType - -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 homeassistant.core import HomeAssistant +from importlib import import_module +from types import ModuleType + +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 91b83938..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,172 +1,172 @@ -import logging -from .message import ( - MessageQuery, - MessageA1Response, - 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 ( + MessageQuery, + MessageA1Response, + MessageSet +) +try: + from enum import StrEnum +except ImportError: + from ...backports.myenum 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 cf675483..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,187 +1,187 @@ -from enum import IntEnum -from ...core.crc8 import calculate -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, - 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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, + 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 1effb0a5..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,352 +1,352 @@ -import logging -import json -from .message import ( - MessageQuery, - MessageToggleDisplay, - MessageNewProtocolQuery, - MessageACResponse, - MessageGeneralSet, - MessageNewProtocolSet, - MessagePowerQuery, - MessageSubProtocolQuery, - MessageSubProtocolSet -) -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 logging +import json +from .message import ( + MessageQuery, + MessageToggleDisplay, + MessageNewProtocolQuery, + MessageACResponse, + MessageGeneralSet, + MessageNewProtocolSet, + MessagePowerQuery, + MessageSubProtocolQuery, + MessageSubProtocolSet +) +try: + from enum import StrEnum +except ImportError: + from ...backports.myenum 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 37b10b3f..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,584 +1,584 @@ -from enum import IntEnum -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, - NewProtocolMessageBody -) -from ...core.crc8 import calculate - -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.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, + NewProtocolMessageBody +) +from ...core.crc8 import calculate + +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/b0/device.py b/custom_components/midea_ac_lan/midea/devices/b0/device.py index 4bfee5a6..c1f5f6ec 100644 --- a/custom_components/midea_ac_lan/midea/devices/b0/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b0/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 _LOGGER = logging.getLogger(__name__) 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..4f62e6a9 100644 --- a/custom_components/midea_ac_lan/midea/devices/b1/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b1/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 _LOGGER = logging.getLogger(__name__) 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..41fe63c9 100644 --- a/custom_components/midea_ac_lan/midea/devices/b3/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b3/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 _LOGGER = logging.getLogger(__name__) 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..56e53d67 100644 --- a/custom_components/midea_ac_lan/midea/devices/b4/device.py +++ b/custom_components/midea_ac_lan/midea/devices/b4/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 _LOGGER = logging.getLogger(__name__) 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..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,160 +1,160 @@ -import logging -import json -from .message import ( - MessageQuery, - MessageB6Response, - 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 logging +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__) + + +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 10561f99..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,220 +1,220 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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/bf/device.py b/custom_components/midea_ac_lan/midea/devices/bf/device.py index 2f0b5685..225cba87 100644 --- a/custom_components/midea_ac_lan/midea/devices/bf/device.py +++ b/custom_components/midea_ac_lan/midea/devices/bf/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 _LOGGER = logging.getLogger(__name__) 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..580f4b98 100644 --- a/custom_components/midea_ac_lan/midea/devices/c2/device.py +++ b/custom_components/midea_ac_lan/midea/devices/c2/device.py @@ -9,7 +9,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) 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..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,258 +1,258 @@ -import logging -from .message import ( - MessageQuery, - MessageSetSilent, - MessageSetECO, - MessageC3Response, - 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): - 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 ( + MessageQuery, + 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__) + + +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 ddfb8cc6..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,197 +1,197 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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)) +from ...core.message import ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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() \ 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 844584e0..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,100 +1,100 @@ -import logging -from .message import ( - MessageQuery, - MessageCAResponse -) -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 ( + MessageQuery, + MessageCAResponse +) +try: + from enum import StrEnum +except ImportError: + from ...backports.myenum 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 e9178e0a..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,116 +1,116 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 289baaf6..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,181 +1,181 @@ -import logging -from .message import ( - MessageQuery, - MessageSet, - MessageCCResponse -) -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 ( + MessageQuery, + MessageSet, + MessageCCResponse +) +try: + from enum import StrEnum +except ImportError: + from ...backports.myenum 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 f71496a8..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,126 +1,126 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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/cd/device.py b/custom_components/midea_ac_lan/midea/devices/cd/device.py index 917a00b0..8de65e50 100644 --- a/custom_components/midea_ac_lan/midea/devices/cd/device.py +++ b/custom_components/midea_ac_lan/midea/devices/cd/device.py @@ -8,7 +8,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) 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..f040e27d 100644 --- a/custom_components/midea_ac_lan/midea/devices/ce/device.py +++ b/custom_components/midea_ac_lan/midea/devices/ce/device.py @@ -8,7 +8,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) 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..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,134 +1,134 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 bbfad209..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,100 +1,100 @@ -import logging -from .message import ( - MessageQuery, - MessageCFResponse, - 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 ( + MessageQuery, + MessageCFResponse, + MessageSet +) +try: + from enum import StrEnum +except ImportError: + from ...backports.myenum 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 e05ecf3c..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,85 +1,85 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 9323bdbd..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,141 +1,141 @@ -import logging -from .message import ( - MessageQuery, - MessagePower, - MessageStart, - MessageDAResponse -) -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 ( + 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__) + + +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 3dba050f..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,108 +1,108 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 40957a04..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,89 +1,89 @@ -import logging -from .message import ( - MessageQuery, - MessagePower, - MessageStart, - MessageDBResponse -) -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 ( + 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__) + + +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 c581d1e3..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,101 +1,101 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 b99362a5..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,89 +1,89 @@ -import logging -from .message import ( - MessageQuery, - MessagePower, - MessageStart, - MessageDCResponse -) -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 ( + 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__) + + +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 ed36038a..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,96 +1,96 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 0064bfe4..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,168 +1,168 @@ -import logging -from .message import ( - MessageQuery, - MessagePower, - MessageStorage, - MessageLock, - MessageE1Response -) -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 ( + 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__) + + +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 9ec073c2..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,121 +1,121 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 386a5467..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,131 +1,131 @@ -import logging -import json -from .message import ( - MessageQuery, - MessageSet, - MessageE2Response, - MessagePower, - MessageNewProtocolSet -) -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 logging +import json +from .message import ( + 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__) + + +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 fb95363a..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,136 +1,136 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 7340a625..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,134 +1,134 @@ -import logging -import json -from .message import ( - MessageQuery, - MessageSet, - MessageNewProtocolSet, - MessagePower, - MessageE3Response -) -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 logging +import json +from .message import ( + 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__) + + +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 068644a3..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,145 +1,145 @@ -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 -} - - -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 ( + 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 +} + + +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/e6/device.py b/custom_components/midea_ac_lan/midea/devices/e6/device.py index 75109667..951125df 100644 --- a/custom_components/midea_ac_lan/midea/devices/e6/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e6/device.py @@ -7,7 +7,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) 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..abaaada3 100644 --- a/custom_components/midea_ac_lan/midea/devices/e8/device.py +++ b/custom_components/midea_ac_lan/midea/devices/e8/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 _LOGGER = logging.getLogger(__name__) 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..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,117 +1,117 @@ -import logging -from .message import ( - MessageQuery, - MessageEAResponse -) -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 ( + MessageQuery, + MessageEAResponse +) +try: + from enum import StrEnum +except ImportError: + from ...backports.myenum 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 b8e09712..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,116 +1,116 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 1b4429ed..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,119 +1,119 @@ -import logging -from .message import ( - MessageQuery, - MessageECResponse -) -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 ( + MessageQuery, + MessageECResponse +) +try: + from enum import StrEnum +except ImportError: + from ...backports.myenum 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 fb337114..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,81 +1,81 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 cd0ca7d7..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,109 +1,109 @@ -import logging -from .message import ( - MessageQuery, - MessageEDResponse, - MessageNewSet, - MessageOldSet -) -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 ( + 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__) + + +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 1f6accee..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,194 +1,194 @@ -from enum import IntEnum -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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, # power - value=0x01 if self.power else 0x00 - ) - ) - if self.lock is not None: - pack_count += 1 - payload.extend( - EDNewSetParamPack.pack( - param=NewSetTags.lock, # lock - value=0x01 if self.lock else 0x00 - ) - ) - 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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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, # power + value=0x01 if self.power else 0x00 + ) + ) + if self.lock is not None: + pack_count += 1 + payload.extend( + EDNewSetParamPack.pack( + param=NewSetTags.lock, # lock + value=0x01 if self.lock else 0x00 + ) + ) + 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 5462d689..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,278 +1,278 @@ -import logging -import json -from .message import ( - MessageQuery, - MessageFAResponse, - 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 logging +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__) + + +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 16cd2e6f..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,139 +1,139 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 4ac3cf1e..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,101 +1,101 @@ -import logging -from .message import ( - MessageQuery, - MessageFBResponse, - 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 ( + MessageQuery, + MessageFBResponse, + MessageSet +) +try: + from enum import StrEnum +except ImportError: + from ...backports.myenum 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 e6a75c6a..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,105 +1,105 @@ -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 8cdf3b09..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,215 +1,215 @@ -import logging -import json -from .message import ( - MessageQuery, - MessageFCResponse, - 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 logging +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__) + + +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 d124b880..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,179 +1,179 @@ -from ...core.crc8 import calculate -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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 9db35daa..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,179 +1,179 @@ -import logging -from .message import ( - MessageQuery, - MessageFDResponse, - 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 ( + MessageQuery, + MessageFDResponse, + MessageSet +) +try: + from enum import StrEnum +except ImportError: + from ...backports.myenum 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 c5fcfa55..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,139 +1,139 @@ -from ...core.crc8 import calculate -from ...core.message import ( - MessageType, - MessageRequest, - MessageResponse, - MessageBody, -) - - -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 ( + MessageType, + MessageRequest, + MessageResponse, + MessageBody, +) + + +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/x13/device.py b/custom_components/midea_ac_lan/midea/devices/x13/device.py index 2a4e1e11..50843ec7 100644 --- a/custom_components/midea_ac_lan/midea/devices/x13/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x13/device.py @@ -8,7 +8,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) 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..2230154e 100644 --- a/custom_components/midea_ac_lan/midea/devices/x26/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x26/device.py @@ -8,7 +8,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) 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..a1cb1ef7 100644 --- a/custom_components/midea_ac_lan/midea/devices/x34/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x34/device.py @@ -9,7 +9,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) 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..f7d33fd9 100644 --- a/custom_components/midea_ac_lan/midea/devices/x40/device.py +++ b/custom_components/midea_ac_lan/midea/devices/x40/device.py @@ -8,7 +8,7 @@ try: from enum import StrEnum except ImportError: - from ...backports.enum import StrEnum + from ...backports.myenum import StrEnum from ...core.device import MiedaDevice _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/midea_ac_lan/midea_devices.py b/custom_components/midea_ac_lan/midea_devices.py index 8cce8b94..fb46ab95 100644 --- a/custom_components/midea_ac_lan/midea_devices.py +++ b/custom_components/midea_ac_lan/midea_devices.py @@ -1,2243 +1,2243 @@ -from homeassistant.const import ( - Platform, - 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 -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 - - -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.const import ( + Platform, + 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 +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 + + +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 9ae901d8..d418b928 100644 --- a/custom_components/midea_ac_lan/midea_entity.py +++ b/custom_components/midea_ac_lan/midea_entity.py @@ -1,60 +1,60 @@ -from homeassistant.helpers.entity import Entity -from .const import DOMAIN -from .midea_devices import MIDEA_DEVICES - -import logging -_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}") +from homeassistant.helpers.entity import Entity +from .const import DOMAIN +from .midea_devices import MIDEA_DEVICES + +import logging +_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 34624507..94f5032e 100644 --- a/custom_components/midea_ac_lan/number.py +++ b/custom_components/midea_ac_lan/number.py @@ -1,62 +1,62 @@ -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, [] - ) - 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 (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 (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 (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): - return self._device.get_attribute(self._entity_key) - - def set_native_value(self, value): - self._device.set_attribute(self._entity_key, value) +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, [] + ) + 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 (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 (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 (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): + 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 b9336690..3e44b022 100644 --- a/custom_components/midea_ac_lan/select.py +++ b/custom_components/midea_ac_lan/select.py @@ -1,43 +1,43 @@ -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, [] - ) - 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 .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, [] + ) + 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 77d524de..956bcb90 100644 --- a/custom_components/midea_ac_lan/sensor.py +++ b/custom_components/midea_ac_lan/sensor.py @@ -1,48 +1,48 @@ -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, [] - ) - 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 .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, [] + ) + 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 34ef2fc9..f5e06d7a 100644 --- a/custom_components/midea_ac_lan/services.yaml +++ b/custom_components/midea_ac_lan/services.yaml @@ -1,17 +1,17 @@ -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: +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" \ No newline at end of file diff --git a/custom_components/midea_ac_lan/switch.py b/custom_components/midea_ac_lan/switch.py index e57042bf..6c91cadd 100644 --- a/custom_components/midea_ac_lan/switch.py +++ b/custom_components/midea_ac_lan/switch.py @@ -1,38 +1,38 @@ -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, [] - ) - 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 .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, [] + ) + 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 eea9bbcc..83fb04db 100644 --- a/custom_components/midea_ac_lan/water_heater.py +++ b/custom_components/midea_ac_lan/water_heater.py @@ -1,287 +1,287 @@ -import functools as ft - -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, - STATE_OFF, -) -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 import MIDEA_DEVICES -from .midea_entity import MideaEntity - -import logging -_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 + +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, + STATE_OFF, +) +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 import MIDEA_DEVICES +from .midea_entity import MideaEntity + +import logging +_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"<br/>"anion"<br/>"prompt_tone"<br/>"power"<br/>"swing" | -| value | true or false | - -| Name | Description | -|-----------|-----------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "fan_speed" | -| value | "Lowest"<br/>"Low"<br/>"Medium"<br/>"High"<br/>"Auto"<br/>"Off" | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "water_level_set" | -| value | "25"<br/>"50"<br/>"75"<br/>"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"<br/>"anion"<br/>"prompt_tone"<br/>"power"<br/>"swing" | +| value | true or false | + +| Name | Description | +|-----------|-----------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "fan_speed" | +| value | "Lowest"<br/>"Low"<br/>"Medium"<br/>"High"<br/>"Auto"<br/>"Off" | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "water_level_set" | +| value | "25"<br/>"50"<br/>"75"<br/>"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"<br/>"anion"<br/>"prompt_tone"<br/>"power"<br/>"swing" | -| value | true 或 false | - -| 名称 | 描述 | -|-----------|-----------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "fan_speed" | -| value | "Lowest"<br/>"Low"<br/>"Medium"<br/>"High"<br/>"Auto"<br/>"Off" | - -| 名称 | 描述 | -|-----------|----------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "water_level_set" | -| value | "25"<br/>"50"<br/>"75"<br/>"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"<br/>"anion"<br/>"prompt_tone"<br/>"power"<br/>"swing" | +| value | true 或 false | + +| 名称 | 描述 | +|-----------|-----------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "fan_speed" | +| value | "Lowest"<br/>"Low"<br/>"Medium"<br/>"High"<br/>"Auto"<br/>"Off" | + +| 名称 | 描述 | +|-----------|----------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "water_level_set" | +| value | "25"<br/>"50"<br/>"75"<br/>"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 d3bf130c..6a058270 100644 --- a/doc/AC.md +++ b/doc/AC.md @@ -1,106 +1,106 @@ -# 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"<br/>"breezeless"<br/>"comfort_mode"<br/>"dry"<br/>"eco_mode"<br/>"indirect_wind"<br/>"natural_wind"<br/>"prompt_tone"<br/>"power"<br/>"screen_display"<br/>"screen_display_2"<br/>"smart_eye"<br/>"swing_horizontal"<br/>"swing_vertical"<br/>"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"<br/>"breezeless"<br/>"comfort_mode"<br/>"dry"<br/>"eco_mode"<br/>"indirect_wind"<br/>"natural_wind"<br/>"prompt_tone"<br/>"power"<br/>"screen_display"<br/>"screen_display_2"<br/>"smart_eye"<br/>"swing_horizontal"<br/>"swing_vertical"<br/>"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 ``` \ No newline at end of file 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"<br/>"breezeless"<br/>"comfort_mode"<br/>"dry"<br/>"eco_mode"<br/>"indirect_wind"<br/>"natural_wind"<br/>"prompt_tone"<br/>"power"<br/>"screen_display"<br/>"screen_display_2"<br/>"smart_eye"<br/>"swing_horizontal"<br/>"swing_vertical"<br/>"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"<br/>"breezeless"<br/>"comfort_mode"<br/>"dry"<br/>"eco_mode"<br/>"indirect_wind"<br/>"natural_wind"<br/>"prompt_tone"<br/>"power"<br/>"screen_display"<br/>"screen_display_2"<br/>"smart_eye"<br/>"swing_horizontal"<br/>"swing_vertical"<br/>"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 03322a69..7fb75463 100644 --- a/doc/C3.md +++ b/doc/C3.md @@ -1,63 +1,63 @@ -# 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.</br>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"<br/>"dhw_power"<br/>"fast_dhw"<br/>"zone1_curve"<br/>"zone2_curve"<br/>"zone1_power"<br/>"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.</br>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"<br/>"dhw_power"<br/>"fast_dhw"<br/>"zone1_curve"<br/>"zone2_curve"<br/>"zone1_power"<br/>"zone2_power" | +| 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..1974af15 100644 --- a/doc/C3_hans.md +++ b/doc/C3_hans.md @@ -1,63 +1,63 @@ -# 热泵空调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 | 总能耗。</br>第一个值可能会延迟,因为更新仅在设备处于活动状态时发送 | -| 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"<br/>"dhw_power"<br/>"fast_dhw"<br/>"zone1_curve"<br/>"zone2_curve"<br/>"zone1_power"<br/>"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 | 总能耗。</br>第一个值可能会延迟,因为更新仅在设备处于活动状态时发送 | +| 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"<br/>"dhw_power"<br/>"fast_dhw"<br/>"zone1_curve"<br/>"zone2_curve"<br/>"zone1_power"<br/>"zone2_power" | +| 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 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"<br/>"eco_mode"<br/>"night_light"<br/>"power"<br />"sleep_mode"<br/>"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"<br/>"eco_mode"<br/>"night_light"<br/>"power"<br />"sleep_mode"<br/>"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"<br/>"eco_mode"<br/>"night_light"<br/>"power"<br />"sleep_mode"<br/>"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"<br/>"eco_mode"<br/>"night_light"<br/>"power"<br />"sleep_mode"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br/>"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"<br />"power"<br /> "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"<br />"power"<br /> "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"<br />"power"<br /> "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"<br />"power"<br /> "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 0f0ed336..2ed23381 100644 --- a/doc/E2.md +++ b/doc/E2.md @@ -1,58 +1,58 @@ -# 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"<br />"power"<br />"variable_heating"<br/>"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"<br />"power"<br />"variable_heating"<br/>"whole_tank_heating" | +| 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 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"<br />"power"<br />"variable_heating"<br/>"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"<br />"power"<br />"variable_heating"<br/>"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 58f09778..e9afee87 100644 --- a/doc/E3.md +++ b/doc/E3.md @@ -1,56 +1,56 @@ -# 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"<br/>"power"<br />"smart_volume"<br/>"zero_cold_water"<br/>"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"<br/>"power"<br />"smart_volume"<br/>"zero_cold_water"<br/>"zero_cold_pulse" | +| 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..53864e2a 100644 --- a/doc/E3_hans.md +++ b/doc/E3_hans.md @@ -1,54 +1,54 @@ -# 燃气热水器 -## 特性 -- 支持温度设定 - -## 自定义 - -- 设置热水器温度基数为整度还是半度 (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"<br/>"power"<br />"smart_volume"<br/>"zero_cold_water"<br/>"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"<br/>"power"<br />"smart_volume"<br/>"zero_cold_water"<br/>"zero_cold_pulse" | +| 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/EA.md b/doc/EA.md index 947aff00..b07c5e6d 100644 --- a/doc/EA.md +++ b/doc/EA.md @@ -1,21 +1,21 @@ -# 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 +# 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. \ No newline at end of file diff --git a/doc/EA_hans.md b/doc/EA_hans.md index 5475a332..5e79971b 100644 --- a/doc/EA_hans.md +++ b/doc/EA_hans.md @@ -1,21 +1,21 @@ -# 电饭煲 - -## 生成实体 -### 默认实体 -无默认实体 - -### 额外生成实体 - -| 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 | 顶部温度 | + +## 服务 无服务 \ No newline at end of file diff --git a/doc/EC.md b/doc/EC.md index 8fbec05e..f4907aae 100644 --- a/doc/EC.md +++ b/doc/EC.md @@ -1,21 +1,21 @@ -# 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 +# 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. \ No newline at end of file diff --git a/doc/EC_hans.md b/doc/EC_hans.md index d4776336..d65529b7 100644 --- a/doc/EC_hans.md +++ b/doc/EC_hans.md @@ -1,21 +1,21 @@ -# 电压力锅 - -## 生成实体 -### 默认实体 -无默认实体 - -### 额外生成实体 - -| 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 | 顶部温度 | + +## 服务 无服务 \ No newline at end of file diff --git a/doc/ED.md b/doc/ED.md index 1446afb2..cfce3672 100644 --- a/doc/ED.md +++ b/doc/ED.md @@ -1,24 +1,24 @@ -# 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 +# 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. \ No newline at end of file diff --git a/doc/ED_hans.md b/doc/ED_hans.md index 2eeda605..4bfd17c4 100644 --- a/doc/ED_hans.md +++ b/doc/ED_hans.md @@ -1,24 +1,24 @@ -# 饮用水设备 - -## 生成实体 -### 默认实体 -无默认实体 - -### 额外生成实体 - -| 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 | 电源开关 | + +## 服务 无服务 \ No newline at end of file diff --git a/doc/FA.md b/doc/FA.md index 6f7adc09..e8f7323d 100644 --- a/doc/FA.md +++ b/doc/FA.md @@ -1,80 +1,80 @@ -# 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"<br/>"oscillate" | -| value | true or false | - -| Name | Description | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "oscillation_mode" | -| value | "Off"<br/>"Oscillation"<br/>"Tilting"<br/>"Curve-W"<br/>"Curve-8"<br/>"Reserved"<br/>"Both" | - -| Name | Description | -|-----------|----------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "oscillation_angle" | -| value | "Off"<br/>"30"<br/>"60"<br/>"90"<br/>"120"<br/>"180"<br/>"360" | - -| Name | Description | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "tilting_angle" | -| value | "Off"<br/>"30"<br/>"60"<br/>"90"<br/>"120"<br/>"180"<br/>"360"<br/>"+60"<br/>"-60"<br/>"40" | - -Example -``` -service: midea_ac_lan.set_attribute -data: - device_id: XXXXXXXXXXXX - attribute: power - value: true -``` - -``` -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"<br/>"oscillate" | +| value | true or false | + +| Name | Description | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "oscillation_mode" | +| value | "Off"<br/>"Oscillation"<br/>"Tilting"<br/>"Curve-W"<br/>"Curve-8"<br/>"Reserved"<br/>"Both" | + +| Name | Description | +|-----------|----------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "oscillation_angle" | +| value | "Off"<br/>"30"<br/>"60"<br/>"90"<br/>"120"<br/>"180"<br/>"360" | + +| Name | Description | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "tilting_angle" | +| value | "Off"<br/>"30"<br/>"60"<br/>"90"<br/>"120"<br/>"180"<br/>"360"<br/>"+60"<br/>"-60"<br/>"40" | + +Example +``` +service: midea_ac_lan.set_attribute +data: + device_id: XXXXXXXXXXXX + attribute: power + value: true +``` + +``` +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 faf0cdb4..962f14b2 100644 --- a/doc/FA_hans.md +++ b/doc/FA_hans.md @@ -1,80 +1,80 @@ -# 电风扇 -## 特性 -- 支持风速调节 -- 支持预设模式 -- 支持水平摆头 -- 支持垂直摆头 - -## 自定义 - -设置风扇的挡位, 不包括"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"<br/>"oscillate" | -| value | true 或 false | - -| 名称 | 描述 | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "oscillation_mode" | -| value | "Off"<br/>"Oscillation"<br/>"Tilting"<br/>"Curve-W"<br/>"Curve-8"<br/>"Reserved"<br/>"Both" | - -| 名称 | 描述 | -|-----------|----------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "oscillation_angle" | -| value | "Off"<br/>"30"<br/>"60"<br/>"90"<br/>"120"<br/>"180"<br/>"360" | - -| 名称 | 描述 | -|-----------|---------------------------------------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "tilting_angle" | -| value | "Off"<br/>"30"<br/>"60"<br/>"90"<br/>"120"<br/>"180"<br/>"360"<br/>"+60"<br/>"-60"<br/>"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"<br/>"oscillate" | +| value | true 或 false | + +| 名称 | 描述 | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "oscillation_mode" | +| value | "Off"<br/>"Oscillation"<br/>"Tilting"<br/>"Curve-W"<br/>"Curve-8"<br/>"Reserved"<br/>"Both" | + +| 名称 | 描述 | +|-----------|----------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "oscillation_angle" | +| value | "Off"<br/>"30"<br/>"60"<br/>"90"<br/>"120"<br/>"180"<br/>"360" | + +| 名称 | 描述 | +|-----------|---------------------------------------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "tilting_angle" | +| value | "Off"<br/>"30"<br/>"60"<br/>"90"<br/>"120"<br/>"180"<br/>"360"<br/>"+60"<br/>"-60"<br/>"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" ``` \ No newline at end of file diff --git a/doc/FC.md b/doc/FC.md index e022934c..d02e20c6 100644 --- a/doc/FC.md +++ b/doc/FC.md @@ -1,88 +1,88 @@ -# 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"<br/>"anion"<br/>"prompt_tone"<br/>"power" | -| value | true or false | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "detect_mode" | -| value | "Off"<br/>"PM 2.5"<br/>"Methanal" | - -| Name | Description | -|-----------|--------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "mode" | -| value | "Auto"<br/>"Manual"<br/>"Sleep"<br/>"Fast"<br/>"Smoke" | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "fan_speed" | -| value | "Auto"<br/>"Low"<br/>"Medium"<br/>"High" | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "screen_display" | -| value | "Bright"<br/>"Dim"<br/>"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"<br/>"anion"<br/>"prompt_tone"<br/>"power" | +| value | true or false | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "detect_mode" | +| value | "Off"<br/>"PM 2.5"<br/>"Methanal" | + +| Name | Description | +|-----------|--------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "mode" | +| value | "Auto"<br/>"Manual"<br/>"Sleep"<br/>"Fast"<br/>"Smoke" | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "fan_speed" | +| value | "Auto"<br/>"Low"<br/>"Medium"<br/>"High" | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "screen_display" | +| value | "Bright"<br/>"Dim"<br/>"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 ``` \ No newline at end of file diff --git a/doc/FC_hans.md b/doc/FC_hans.md index 67e85cbc..e1cdfc13 100644 --- a/doc/FC_hans.md +++ b/doc/FC_hans.md @@ -1,87 +1,87 @@ -# 空气净化器 - -## 自定义 - -设置自动打开或关闭待机模式的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"<br/>"anion"<br/>"prompt_tone"<br/>"power" | -| value | true 或 false | - -| 名称 | 描述 | -|-----------|-----------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "detect_mode" | -| value | "Off"<br/>"PM 2.5"<br/>"Methanal" | - -| 名称 | 描述 | -|-----------|--------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "mode" | -| value | "Auto"<br/>"Manual"<br/>"Sleep"<br/>"Fast"<br/>"Smoke" | - -| 名称 | 描述 | -|-----------|------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "fan_speed" | -| value | "Auto"<br/>"Low"<br/>"Medium"<br/>"High" | - -| 名称 | 描述 | -|-----------|------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "screen_display" | -| value | "Bright"<br/>"Dim"<br/>"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"<br/>"anion"<br/>"prompt_tone"<br/>"power" | +| value | true 或 false | + +| 名称 | 描述 | +|-----------|-----------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "detect_mode" | +| value | "Off"<br/>"PM 2.5"<br/>"Methanal" | + +| 名称 | 描述 | +|-----------|--------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "mode" | +| value | "Auto"<br/>"Manual"<br/>"Sleep"<br/>"Fast"<br/>"Smoke" | + +| 名称 | 描述 | +|-----------|------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "fan_speed" | +| value | "Auto"<br/>"Low"<br/>"Medium"<br/>"High" | + +| 名称 | 描述 | +|-----------|------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "screen_display" | +| value | "Bright"<br/>"Dim"<br/>"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 ``` \ No newline at end of file diff --git a/doc/FD.md b/doc/FD.md index 58fa0149..c71371be 100644 --- a/doc/FD.md +++ b/doc/FD.md @@ -1,67 +1,67 @@ -# 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"<br/>"prompt_tone"<br/>"power" | -| value | true or false | - -| Name | Description | -|-----------|-----------------------------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "fan_speed" | -| value | "Lowest"<br/>"Low"<br/>"Medium"<br/>"High"<br/>"Auto"<br/>"Off" | - -| Name | Description | -|-----------|---------------------------------------------| -| device_id | The Appliance code (Device ID) of appliance | -| attribute | "screen_display" | -| value | "Bright"<br/>"Dim"<br/>"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"<br/>"prompt_tone"<br/>"power" | +| value | true or false | + +| Name | Description | +|-----------|-----------------------------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "fan_speed" | +| value | "Lowest"<br/>"Low"<br/>"Medium"<br/>"High"<br/>"Auto"<br/>"Off" | + +| Name | Description | +|-----------|---------------------------------------------| +| device_id | The Appliance code (Device ID) of appliance | +| attribute | "screen_display" | +| value | "Bright"<br/>"Dim"<br/>"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 ``` \ No newline at end of file diff --git a/doc/FD_hans.md b/doc/FD_hans.md index 56ea428f..ba6be127 100644 --- a/doc/FD_hans.md +++ b/doc/FD_hans.md @@ -1,66 +1,66 @@ -# 加湿器 -## 特性 -- 支持运行模式 -- 支持风扇模式设定 -- 支持湿度设定 - -## 生成实体 -### 默认生成实体 -| 实体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"<br/>"prompt_tone"<br/>"power" | -| value | true 或 false | - -| 名称 | 描述 | -|-----------|-----------------------------------------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "fan_speed" | -| value | "Lowest"<br/>"Low"<br/>"Medium"<br/>"High"<br/>"Auto"<br/>"Off" | - -| 名称 | 描述 | -|-----------|------------------------------| -| device_id | 设备的编号(Device ID) | -| attribute | "screen_display" | -| value | "Bright"<br/>"Dim"<br/>"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"<br/>"prompt_tone"<br/>"power" | +| value | true 或 false | + +| 名称 | 描述 | +|-----------|-----------------------------------------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "fan_speed" | +| value | "Lowest"<br/>"Low"<br/>"Medium"<br/>"High"<br/>"Auto"<br/>"Off" | + +| 名称 | 描述 | +|-----------|------------------------------| +| device_id | 设备的编号(Device ID) | +| attribute | "screen_display" | +| value | "Bright"<br/>"Dim"<br/>"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 ``` \ No newline at end of file