forked from HomeAssistant/midea-meiju-codec
Compare commits
29 Commits
v0.0.4-alp
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c58b8282cd | ||
|
|
b3be2a7cbd | ||
|
|
8e08974682 | ||
|
|
b81ef0e007 | ||
|
|
89f3e6e6f8 | ||
|
|
8cb30ebb3e | ||
|
|
a31c905fb5 | ||
|
|
167d767b28 | ||
|
|
848a086977 | ||
|
|
e4334cd936 | ||
|
|
8b93c58654 | ||
|
|
bbf4d168e7 | ||
|
|
273d4e41bf | ||
|
|
e9a6c65787 | ||
|
|
beaf837e3b | ||
|
|
a7efce2d26 | ||
|
|
9ff67da49b | ||
|
|
068d9a377d | ||
|
|
57342ad86c | ||
|
|
884c436c82 | ||
|
|
3c9f1275a0 | ||
|
|
4f5e434492 | ||
|
|
9d22859eec | ||
|
|
d87156d3e3 | ||
|
|
f3246eb779 | ||
|
|
3507671120 | ||
|
|
b6e7bcb729 | ||
|
|
c12158cd09 | ||
|
|
40ba24c32a |
11
README.md
11
README.md
@@ -8,25 +8,29 @@
|
||||
- 自动下载设备的协议文件
|
||||
- 将设备状态更新为设备可见的属性
|
||||
|
||||
## 非常初期的预览版
|
||||
## 版本说明
|
||||
|
||||
- 仅供技术实现验证以及评估
|
||||
- 所有设备默认可生成一个名为Status的二进制传感器,其属性中列出了设备可访问的所有属性,当然有些值不可设置
|
||||
- Status实体前几项列出了该设备的分类信息,供参考
|
||||
|
||||
## 目前支持的设备类型
|
||||
|
||||
- T0xAC 空调
|
||||
- T0xB2 电蒸箱
|
||||
- T0xB3 消毒碗柜
|
||||
- T0xB8 智能扫地机器人
|
||||
- T0xCA 对开门冰箱
|
||||
- T0xCE 新风机
|
||||
- T0xCF 中央空调暖家
|
||||
- T0xD9 复式洗衣机
|
||||
- T0xDB 滚筒洗衣机
|
||||
- T0xDC 干衣机
|
||||
- T0xE1 洗碗机
|
||||
- T0xE2 电热水器
|
||||
- T0xE3 恒温式燃气热水器
|
||||
- T0xEA 电饭锅
|
||||
- T0xED 软水机
|
||||
- T0xFA 电风扇
|
||||
- T0xFD 加湿器
|
||||
|
||||
欢迎合作开发添加更多设备支持。
|
||||
@@ -52,3 +56,6 @@
|
||||
|
||||
示例配置`22012227`演示了如何将设备属性映射成以上各种HomeAssistant中的实体。
|
||||
|
||||
## 致谢
|
||||
|
||||
感谢[midea-meiju-codec](https://github.com/MattedBroadSky/midea-meiju-codec)项目提供的先验知识。
|
||||
@@ -41,7 +41,8 @@ from .const import (
|
||||
CONF_SN8,
|
||||
CONF_SN,
|
||||
CONF_MODEL_NUMBER,
|
||||
CONF_SERVERS
|
||||
CONF_SERVERS, STORAGE_PATH, CONF_MANUFACTURER_CODE,
|
||||
CONF_SELECTED_HOMES
|
||||
)
|
||||
# 账号型:登录云端、获取设备列表,并为每台设备建立协调器(无本地控制)
|
||||
from .const import CONF_PASSWORD as CONF_PASSWORD_KEY, CONF_SERVER as CONF_SERVER_KEY
|
||||
@@ -131,16 +132,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType):
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
cjson = os.getcwd() + "/cjson.lua"
|
||||
bit = os.getcwd() + "/bit.lua"
|
||||
if not os.path.exists(cjson):
|
||||
from .const import CJSON_LUA
|
||||
cjson_lua = base64.b64decode(CJSON_LUA.encode("utf-8")).decode("utf-8")
|
||||
with open(cjson, "wt") as fp:
|
||||
fp.write(cjson_lua)
|
||||
if not os.path.exists(bit):
|
||||
from .const import BIT_LUA
|
||||
bit_lua = base64.b64decode(BIT_LUA.encode("utf-8")).decode("utf-8")
|
||||
with open(bit, "wt") as fp:
|
||||
fp.write(bit_lua)
|
||||
# if not os.path.exists(cjson):
|
||||
from .const import CJSON_LUA
|
||||
cjson_lua = base64.b64decode(CJSON_LUA.encode("utf-8")).decode("utf-8")
|
||||
with open(cjson, "wt") as fp:
|
||||
fp.write(cjson_lua)
|
||||
# if not os.path.exists(bit):
|
||||
from .const import BIT_LUA
|
||||
bit_lua = base64.b64decode(BIT_LUA.encode("utf-8")).decode("utf-8")
|
||||
with open(bit, "wt") as fp:
|
||||
fp.write(bit_lua)
|
||||
return True
|
||||
|
||||
|
||||
@@ -169,7 +170,26 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN].setdefault("accounts", {})
|
||||
bucket = {"device_list": {}, "coordinator_map": {}}
|
||||
home_ids = list(homes.keys())
|
||||
|
||||
# 获取用户选择的家庭ID列表
|
||||
selected_homes = config_entry.data.get(CONF_SELECTED_HOMES, [])
|
||||
MideaLogger.debug(f"Selected homes from config: {selected_homes}")
|
||||
MideaLogger.debug(f"Available homes keys: {list(homes.keys())}")
|
||||
if not selected_homes:
|
||||
# 如果没有选择,默认使用所有家庭
|
||||
home_ids = list(homes.keys())
|
||||
else:
|
||||
# 只处理用户选择的家庭,确保类型匹配
|
||||
home_ids = []
|
||||
for selected_home in selected_homes:
|
||||
# 尝试匹配字符串和数字类型的home_id
|
||||
if selected_home in homes:
|
||||
home_ids.append(selected_home)
|
||||
elif str(selected_home) in homes:
|
||||
home_ids.append(str(selected_home))
|
||||
elif int(selected_home) in homes:
|
||||
home_ids.append(int(selected_home))
|
||||
MideaLogger.debug(f"Final home_ids to process: {home_ids}")
|
||||
|
||||
for home_id in home_ids:
|
||||
appliances = await cloud.list_appliances(home_id)
|
||||
@@ -179,11 +199,21 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
# 为每台设备构建占位设备与协调器(不连接本地)
|
||||
for appliance_code, info in appliances.items():
|
||||
MideaLogger.debug(f"info={info} ")
|
||||
|
||||
os.makedirs(hass.config.path(STORAGE_PATH), exist_ok=True)
|
||||
path = hass.config.path(STORAGE_PATH)
|
||||
file = await cloud.download_lua(
|
||||
path=path,
|
||||
device_type=info.get(CONF_TYPE),
|
||||
sn=info.get(CONF_SN),
|
||||
model_number=info.get(CONF_MODEL_NUMBER),
|
||||
manufacturer_code=info.get(CONF_MANUFACTURER_CODE),
|
||||
)
|
||||
try:
|
||||
device = MiedaDevice(
|
||||
name=info.get(CONF_NAME) or info.get("name"),
|
||||
name=info.get(CONF_NAME),
|
||||
device_id=appliance_code,
|
||||
device_type=info.get(CONF_TYPE) or info.get("type"),
|
||||
device_type=info.get(CONF_TYPE),
|
||||
ip_address=None,
|
||||
port=None,
|
||||
token=None,
|
||||
@@ -192,8 +222,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
protocol=info.get(CONF_PROTOCOL) or 2,
|
||||
model=info.get(CONF_MODEL),
|
||||
subtype=info.get(CONF_MODEL_NUMBER),
|
||||
sn=info.get(CONF_SN) or info.get("sn"),
|
||||
sn8=info.get(CONF_SN8) or info.get("sn8"),
|
||||
sn=info.get(CONF_SN),
|
||||
sn8=info.get(CONF_SN8),
|
||||
lua_file=file,
|
||||
cloud=cloud,
|
||||
)
|
||||
# 加载并应用设备映射(queries/centralized/calculate),并预置 attributes 键
|
||||
try:
|
||||
@@ -206,7 +238,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
mapping = {}
|
||||
|
||||
try:
|
||||
device.set_queries(mapping.get("queries", []))
|
||||
device.set_queries(mapping.get("queries", [{}]))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
@@ -289,6 +321,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
bucket["coordinator_map"][appliance_code] = coordinator
|
||||
except Exception as e:
|
||||
MideaLogger.error(f"Init device failed: {appliance_code}, error: {e}")
|
||||
# break
|
||||
hass.data[DOMAIN]["accounts"][config_entry.entry_id] = bucket
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -13,7 +13,6 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .core.logger import MideaLogger
|
||||
from .midea_entity import MideaEntity
|
||||
from . import load_device_config
|
||||
|
||||
@@ -66,10 +65,6 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
rationale=rationale,
|
||||
config=config,
|
||||
)
|
||||
self._device = device
|
||||
self._manufacturer = manufacturer
|
||||
self._rationale = rationale
|
||||
self._config = config
|
||||
self._key_power = self._config.get("power")
|
||||
self._key_hvac_modes = self._config.get("hvac_modes")
|
||||
self._key_preset_modes = self._config.get("preset_modes")
|
||||
@@ -85,7 +80,9 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = 0
|
||||
features = ClimateEntityFeature(0)
|
||||
features |= ClimateEntityFeature.TURN_ON
|
||||
features |= ClimateEntityFeature.TURN_OFF
|
||||
if self._key_target_temperature is not None:
|
||||
features |= ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
if self._key_preset_modes is not None:
|
||||
@@ -100,18 +97,33 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
return self.device_attributes.get(self._key_current_temperature)
|
||||
temp = self._get_nested_value(self._key_current_temperature)
|
||||
if temp is not None:
|
||||
try:
|
||||
return float(temp)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
if isinstance(self._key_target_temperature, list):
|
||||
temp_int = self.device_attributes.get(self._key_target_temperature[0])
|
||||
tem_dec = self.device_attributes.get(self._key_target_temperature[1])
|
||||
temp_int = self._get_nested_value(self._key_target_temperature[0])
|
||||
tem_dec = self._get_nested_value(self._key_target_temperature[1])
|
||||
if temp_int is not None and tem_dec is not None:
|
||||
return temp_int + tem_dec
|
||||
try:
|
||||
return float(temp_int) + float(tem_dec)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return None
|
||||
else:
|
||||
return self.device_attributes.get(self._key_target_temperature)
|
||||
temp = self._get_nested_value(self._key_target_temperature)
|
||||
if temp is not None:
|
||||
try:
|
||||
return float(temp)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return None
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
@@ -149,7 +161,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
return self._dict_get_selected(self._key_fan_modes, "EQUALLY")
|
||||
return self._dict_get_selected(self._key_fan_modes)
|
||||
|
||||
@property
|
||||
def swing_modes(self):
|
||||
@@ -157,7 +169,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def swing_mode(self):
|
||||
return self._dict_get_selected(self._key_swing_modes, "EQUALLY")
|
||||
return self._dict_get_selected(self._key_swing_modes)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
@@ -165,7 +177,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
return self._dict_get_selected(self._key_hvac_modes, "EQUALLY")
|
||||
return self._dict_get_selected(self._key_hvac_modes)
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
@@ -225,7 +237,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
"""Get on/off status from device attributes."""
|
||||
if key is None:
|
||||
return False
|
||||
value = self.device_attributes.get(key)
|
||||
value = self._get_nested_value(key)
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return value == 1 or value == "on" or value == "true"
|
||||
@@ -234,35 +246,4 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
"""Set on/off status for device attribute."""
|
||||
if key is None:
|
||||
return
|
||||
await self.async_set_attribute(key, value)
|
||||
|
||||
def _dict_get_selected(self, dict_config, rationale="EQUALLY"):
|
||||
"""Get selected value from dictionary configuration."""
|
||||
if dict_config is None:
|
||||
return None
|
||||
|
||||
MideaLogger.debug(f"dict_config={dict_config}, rationale={rationale}, self.device_attributes={self.device_attributes} ")
|
||||
for key, config in dict_config.items():
|
||||
if isinstance(config, dict):
|
||||
# Check if all conditions match
|
||||
match = True
|
||||
for attr_key, attr_value in config.items():
|
||||
device_value = self.device_attributes.get(attr_key)
|
||||
if device_value is None:
|
||||
match = False
|
||||
break
|
||||
if rationale == "EQUALLY":
|
||||
if device_value != attr_value:
|
||||
match = False
|
||||
break
|
||||
elif rationale == "LESS":
|
||||
if device_value >= attr_value:
|
||||
match = False
|
||||
break
|
||||
elif rationale == "GREATER":
|
||||
if device_value <= attr_value:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
return key
|
||||
return None
|
||||
await self.async_set_attribute(key, value)
|
||||
@@ -8,11 +8,14 @@ from homeassistant.core import callback
|
||||
from homeassistant.const import (
|
||||
CONF_TYPE,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from .const import (
|
||||
CONF_ACCOUNT,
|
||||
CONF_PASSWORD,
|
||||
DOMAIN,
|
||||
CONF_SERVER, CONF_SERVERS
|
||||
CONF_SERVER, CONF_SERVERS,
|
||||
CONF_HOMES,
|
||||
CONF_SELECTED_HOMES
|
||||
)
|
||||
from .core.cloud import get_midea_cloud
|
||||
|
||||
@@ -20,6 +23,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
_session = None
|
||||
_cloud = None
|
||||
_homes = None
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
@@ -41,15 +46,19 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if await cloud.login():
|
||||
await self.async_set_unique_id(user_input[CONF_ACCOUNT])
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_ACCOUNT],
|
||||
data={
|
||||
CONF_TYPE: CONF_ACCOUNT,
|
||||
CONF_ACCOUNT: user_input[CONF_ACCOUNT],
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
CONF_SERVER: user_input[CONF_SERVER]
|
||||
},
|
||||
)
|
||||
|
||||
# 保存云实例和用户输入,用于后续步骤
|
||||
self._cloud = cloud
|
||||
self._user_input = user_input
|
||||
|
||||
# 获取家庭列表
|
||||
homes = await cloud.list_home()
|
||||
if homes and len(homes) > 0:
|
||||
_LOGGER.debug(f"Found homes: {homes}")
|
||||
self._homes = homes
|
||||
return await self.async_step_select_homes()
|
||||
else:
|
||||
errors["base"] = "no_homes"
|
||||
else:
|
||||
errors["base"] = "login_failed"
|
||||
except Exception as e:
|
||||
@@ -65,14 +74,116 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_select_homes(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
|
||||
"""家庭选择步骤"""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
selected_homes = user_input.get(CONF_SELECTED_HOMES, [])
|
||||
if not selected_homes:
|
||||
errors["base"] = "no_homes_selected"
|
||||
else:
|
||||
# 创建配置条目
|
||||
return self.async_create_entry(
|
||||
title=self._user_input[CONF_ACCOUNT],
|
||||
data={
|
||||
CONF_TYPE: CONF_ACCOUNT,
|
||||
CONF_ACCOUNT: self._user_input[CONF_ACCOUNT],
|
||||
CONF_PASSWORD: self._user_input[CONF_PASSWORD],
|
||||
CONF_SERVER: self._user_input[CONF_SERVER],
|
||||
CONF_SELECTED_HOMES: selected_homes
|
||||
},
|
||||
)
|
||||
|
||||
# 构建家庭选择选项
|
||||
home_options = {}
|
||||
for home_id, home_info in self._homes.items():
|
||||
_LOGGER.debug(f"Processing home_id: {home_id}, home_info: {home_info}, type: {type(home_info)}")
|
||||
# 确保home_id是字符串,因为multi_select需要字符串键
|
||||
home_id_str = str(home_id)
|
||||
if isinstance(home_info, dict):
|
||||
home_name = home_info.get("name", f"家庭 {home_id}")
|
||||
else:
|
||||
# 如果home_info是字符串,直接使用
|
||||
home_name = str(home_info) if home_info else f"家庭 {home_id}"
|
||||
home_options[home_id_str] = home_name
|
||||
|
||||
# 默认全选
|
||||
default_selected = list(home_options.keys())
|
||||
_LOGGER.debug(f"Home options: {home_options}")
|
||||
_LOGGER.debug(f"Default selected: {default_selected}")
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="select_homes",
|
||||
data_schema=vol.Schema({
|
||||
vol.Required(CONF_SELECTED_HOMES, default=default_selected): vol.All(
|
||||
cv.multi_select(home_options)
|
||||
)
|
||||
}),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry):
|
||||
self._config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None, error=None):
|
||||
# 账号型条目不支持配置项
|
||||
return self.async_abort(reason="account_unsupport_config")
|
||||
# 不再提供任何可配置项
|
||||
return self.async_abort(reason="account_unsupport_config")
|
||||
# 不提供 reset/configure 等选项步骤
|
||||
"""初始化选项流程"""
|
||||
if user_input is not None:
|
||||
if user_input["option"] == "change_credentials":
|
||||
return await self.async_step_change_credentials()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema({
|
||||
vol.Required("option", default="change_credentials"): vol.In({
|
||||
"change_credentials": "修改账号密码",
|
||||
})
|
||||
}),
|
||||
errors=error
|
||||
)
|
||||
|
||||
async def async_step_change_credentials(self, user_input=None, error=None):
|
||||
"""账号密码变更步骤"""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
# 验证新密码
|
||||
cloud = get_midea_cloud(
|
||||
session=async_create_clientsession(self.hass),
|
||||
cloud_name=CONF_SERVERS[user_input[CONF_SERVER]],
|
||||
account=user_input[CONF_ACCOUNT],
|
||||
password=user_input[CONF_PASSWORD]
|
||||
)
|
||||
try:
|
||||
if await cloud.login():
|
||||
# 更新配置条目
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._config_entry,
|
||||
data={
|
||||
CONF_TYPE: CONF_ACCOUNT,
|
||||
CONF_ACCOUNT: user_input[CONF_ACCOUNT],
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
CONF_SERVER: user_input[CONF_SERVER]
|
||||
}
|
||||
)
|
||||
return self.async_create_entry(title="", data={})
|
||||
else:
|
||||
errors["base"] = "login_failed"
|
||||
except Exception as e:
|
||||
_LOGGER.exception("Login error: %s", e)
|
||||
errors["base"] = "login_failed"
|
||||
|
||||
# 获取当前配置
|
||||
current_data = self._config_entry.data
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="change_credentials",
|
||||
data_schema=vol.Schema({
|
||||
vol.Required(CONF_ACCOUNT, default=current_data.get(CONF_ACCOUNT, "")): str,
|
||||
vol.Required(CONF_PASSWORD, default=""): str,
|
||||
vol.Required(CONF_SERVER, default=current_data.get(CONF_SERVER, 2)): vol.In(CONF_SERVERS)
|
||||
}),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,10 +3,13 @@ import time
|
||||
import datetime
|
||||
import json
|
||||
import base64
|
||||
import asyncio
|
||||
import requests
|
||||
from aiohttp import ClientSession
|
||||
from secrets import token_hex
|
||||
from .logger import MideaLogger
|
||||
from .security import CloudSecurity, MeijuCloudSecurity, MSmartCloudSecurity
|
||||
from .util import bytes_to_dec_string
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -100,6 +103,46 @@ class MideaCloud:
|
||||
|
||||
return None
|
||||
|
||||
def _api_request_sync(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._access_token is not None:
|
||||
header.update({
|
||||
"accesstoken": self._access_token
|
||||
})
|
||||
response:dict = {"code": -1}
|
||||
_LOGGER.debug(f"Midea cloud API header: {header}")
|
||||
_LOGGER.debug(f"Midea cloud API dump_data: {dump_data}")
|
||||
try:
|
||||
r = requests.post(url, headers=header, data=dump_data, timeout=5)
|
||||
raw = r.content
|
||||
_LOGGER.debug(f"Midea cloud API url: {url}, data: {data}, response: {raw}")
|
||||
response = json.loads(raw)
|
||||
except Exception as e:
|
||||
_LOGGER.debug(f"API request attempt failed: {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({
|
||||
@@ -115,27 +158,27 @@ class MideaCloud:
|
||||
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 send_cloud(self, appliance_id: int, data: bytearray):
|
||||
appliance_code = str(appliance_id)
|
||||
params = {
|
||||
'applianceCode': appliance_code,
|
||||
'order': self._security.aes_encrypt(bytes_to_dec_string(data)).hex(),
|
||||
'timestamp': 'true',
|
||||
"isFull": "false"
|
||||
}
|
||||
|
||||
if response := await self._api_request(
|
||||
endpoint='/v1/appliance/transparent/send',
|
||||
data=params,
|
||||
):
|
||||
if response and response.get('reply'):
|
||||
_LOGGER.debug("[%s] Cloud command response: %s", appliance_code, response)
|
||||
reply_data = self._security.aes_decrypt(bytes.fromhex(response['reply']))
|
||||
return reply_data
|
||||
else:
|
||||
_LOGGER.warning("[%s] Cloud command failed: %s", appliance_code, response)
|
||||
|
||||
return None
|
||||
|
||||
async def list_home(self) -> dict | None:
|
||||
return {1: "My home"}
|
||||
@@ -304,6 +347,7 @@ class MeijuCloud(MideaCloud):
|
||||
"applianceMFCode": manufacturer_code,
|
||||
'version': "0",
|
||||
"iotAppId": self.APP_ID,
|
||||
"modelNumber": model_number
|
||||
}
|
||||
fnm = None
|
||||
if response := await self._api_request(
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import threading
|
||||
import socket
|
||||
import traceback
|
||||
from enum import IntEnum
|
||||
|
||||
from .cloud import MideaCloud
|
||||
from .security import LocalSecurity, MSGTYPE_HANDSHAKE_REQUEST, MSGTYPE_ENCRYPTED_REQUEST
|
||||
from .packet_builder import PacketBuilder
|
||||
from .message import MessageQuestCustom
|
||||
from .logger import MideaLogger
|
||||
from .lua_runtime import MideaCodec
|
||||
from .util import dec_string_to_bytes
|
||||
|
||||
|
||||
class AuthException(Exception):
|
||||
@@ -39,7 +44,9 @@ class MiedaDevice(threading.Thread):
|
||||
subtype: int | None,
|
||||
connected: bool,
|
||||
sn: str | None,
|
||||
sn8: str | None):
|
||||
sn8: str | None,
|
||||
lua_file: str | None,
|
||||
cloud: MideaCloud | None):
|
||||
threading.Thread.__init__(self)
|
||||
self._socket = None
|
||||
self._ip_address = ip_address
|
||||
@@ -71,6 +78,8 @@ class MiedaDevice(threading.Thread):
|
||||
self._centralized = []
|
||||
self._calculate_get = []
|
||||
self._calculate_set = []
|
||||
self._lua_runtime = MideaCodec(lua_file, device_type=self._attributes.get("device_type"), sn=sn, subtype=subtype) if lua_file is not None else None
|
||||
self._cloud = cloud
|
||||
|
||||
@property
|
||||
def device_name(self):
|
||||
@@ -126,14 +135,51 @@ class MiedaDevice(threading.Thread):
|
||||
def get_attribute(self, attribute):
|
||||
return self._attributes.get(attribute)
|
||||
|
||||
def set_attribute(self, attribute, value):
|
||||
def _convert_to_nested_structure(self, attributes):
|
||||
"""Convert dot-notation attributes to nested structure."""
|
||||
nested = {}
|
||||
for key, value in attributes.items():
|
||||
if '.' in key:
|
||||
# Handle nested attributes with dot notation
|
||||
keys = key.split('.')
|
||||
current_dict = nested
|
||||
|
||||
# Navigate to the parent dictionary
|
||||
for k in keys[:-1]:
|
||||
if k not in current_dict:
|
||||
current_dict[k] = {}
|
||||
current_dict = current_dict[k]
|
||||
|
||||
# Set the final value
|
||||
current_dict[keys[-1]] = value
|
||||
else:
|
||||
# Handle flat attributes
|
||||
nested[key] = value
|
||||
return nested
|
||||
|
||||
async def set_attribute(self, attribute, value):
|
||||
if attribute in self._attributes.keys():
|
||||
new_status = {}
|
||||
for attr in self._centralized:
|
||||
new_status[attr] = self._attributes.get(attr)
|
||||
new_status[attribute] = value
|
||||
|
||||
# Convert dot-notation attributes to nested structure for transmission
|
||||
nested_status = self._convert_to_nested_structure(new_status)
|
||||
|
||||
try:
|
||||
if set_cmd := self._lua_runtime.build_control(nested_status, status=self._attributes):
|
||||
await self._build_send(set_cmd)
|
||||
return
|
||||
except Exception as e:
|
||||
MideaLogger.debug(f"LuaRuntimeError in set_attribute {nested_status}: {repr(e)}")
|
||||
traceback.print_exc()
|
||||
|
||||
def set_attributes(self, attributes):
|
||||
cloud = self._cloud
|
||||
if cloud and hasattr(cloud, "send_device_control"):
|
||||
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
|
||||
|
||||
async def set_attributes(self, attributes):
|
||||
new_status = {}
|
||||
for attr in self._centralized:
|
||||
new_status[attr] = self._attributes.get(attr)
|
||||
@@ -142,6 +188,22 @@ class MiedaDevice(threading.Thread):
|
||||
if attribute in self._attributes.keys():
|
||||
has_new = True
|
||||
new_status[attribute] = value
|
||||
|
||||
# Convert dot-notation attributes to nested structure for transmission
|
||||
nested_status = self._convert_to_nested_structure(new_status)
|
||||
|
||||
if has_new:
|
||||
try:
|
||||
if set_cmd := self._lua_runtime.build_control(nested_status, status=self._attributes):
|
||||
await self._build_send(set_cmd)
|
||||
return
|
||||
except Exception as e:
|
||||
MideaLogger.debug(f"LuaRuntimeError in set_attributes {nested_status}: {repr(e)}")
|
||||
traceback.print_exc()
|
||||
|
||||
cloud = self._cloud
|
||||
if cloud and hasattr(cloud, "send_device_control"):
|
||||
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
|
||||
|
||||
def set_ip_address(self, ip_address):
|
||||
MideaLogger.debug(f"Update IP address to {ip_address}")
|
||||
@@ -188,12 +250,6 @@ class MiedaDevice(threading.Thread):
|
||||
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)
|
||||
@@ -204,11 +260,128 @@ class MiedaDevice(threading.Thread):
|
||||
data = self._security.encode_8370(data, msg_type)
|
||||
self._send_message_v2(data)
|
||||
|
||||
def _build_send(self, cmd: str):
|
||||
async def _build_send(self, cmd: str):
|
||||
MideaLogger.debug(f"Sending: {cmd.lower()}")
|
||||
bytes_cmd = bytes.fromhex(cmd)
|
||||
msg = PacketBuilder(self._device_id, bytes_cmd).finalize()
|
||||
self._send_message(msg)
|
||||
await self._send_message(bytes_cmd)
|
||||
|
||||
async def refresh_status(self):
|
||||
for query in self._queries:
|
||||
if query_cmd := self._lua_runtime.build_query(query):
|
||||
await self._build_send(query_cmd)
|
||||
|
||||
def _parse_cloud_message(self, decrypted):
|
||||
# MideaLogger.debug(f"Received: {decrypted}")
|
||||
if status := self._lua_runtime.decode_status(dec_string_to_bytes(decrypted).hex()):
|
||||
MideaLogger.debug(f"Decoded: {status}")
|
||||
new_status = {}
|
||||
for single in status.keys():
|
||||
value = status.get(single)
|
||||
if single not in self._attributes or self._attributes[single] != value:
|
||||
self._attributes[single] = value
|
||||
new_status[single] = value
|
||||
if len(new_status) > 0:
|
||||
for c in self._calculate_get:
|
||||
lvalue = c.get("lvalue")
|
||||
rvalue = c.get("rvalue")
|
||||
if lvalue and rvalue:
|
||||
calculate = False
|
||||
for s, v in new_status.items():
|
||||
if rvalue.find(f"[{s}]") >= 0:
|
||||
calculate = True
|
||||
break
|
||||
if calculate:
|
||||
calculate_str1 = \
|
||||
(f"{lvalue.replace('[', 'self._attributes[')} = "
|
||||
f"{rvalue.replace('[', 'self._attributes[')}") \
|
||||
.replace("[", "[\"").replace("]", "\"]")
|
||||
calculate_str2 = \
|
||||
(f"{lvalue.replace('[', 'new_status[')} = "
|
||||
f"{rvalue.replace('[', 'self._attributes[')}") \
|
||||
.replace("[", "[\"").replace("]", "\"]")
|
||||
try:
|
||||
exec(calculate_str1)
|
||||
exec(calculate_str2)
|
||||
except Exception:
|
||||
MideaLogger.warning(
|
||||
f"Calculation Error: {lvalue} = {rvalue}", self._device_id
|
||||
)
|
||||
self._update_all(new_status)
|
||||
return ParseMessageResult.SUCCESS
|
||||
|
||||
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)
|
||||
MideaLogger.debug(f"Received: {decrypted.hex().lower()}")
|
||||
if status := self._lua_runtime.decode_status(decrypted.hex()):
|
||||
MideaLogger.debug(f"Decoded: {status}")
|
||||
new_status = {}
|
||||
for single in status.keys():
|
||||
value = status.get(single)
|
||||
if single not in self._attributes or self._attributes[single] != value:
|
||||
self._attributes[single] = value
|
||||
new_status[single] = value
|
||||
if len(new_status) > 0:
|
||||
for c in self._calculate_get:
|
||||
lvalue = c.get("lvalue")
|
||||
rvalue = c.get("rvalue")
|
||||
if lvalue and rvalue:
|
||||
calculate = False
|
||||
for s, v in new_status.items():
|
||||
if rvalue.find(f"[{s}]") >= 0:
|
||||
calculate = True
|
||||
break
|
||||
if calculate:
|
||||
calculate_str1 = \
|
||||
(f"{lvalue.replace('[', 'self._attributes[')} = "
|
||||
f"{rvalue.replace('[', 'self._attributes[')}") \
|
||||
.replace("[", "[\"").replace("]", "\"]")
|
||||
calculate_str2 = \
|
||||
(f"{lvalue.replace('[', 'new_status[')} = "
|
||||
f"{rvalue.replace('[', 'self._attributes[')}") \
|
||||
.replace("[", "[\"").replace("]", "\"]")
|
||||
try:
|
||||
exec(calculate_str1)
|
||||
exec(calculate_str2)
|
||||
except Exception:
|
||||
MideaLogger.warning(
|
||||
f"Calculation Error: {lvalue} = {rvalue}", self._device_id
|
||||
)
|
||||
self._update_all(new_status)
|
||||
return ParseMessageResult.SUCCESS
|
||||
|
||||
async def _send_message(self, data):
|
||||
if reply := await self._cloud.send_cloud(self._device_id, data):
|
||||
result = self._parse_cloud_message(reply)
|
||||
if result == ParseMessageResult.ERROR:
|
||||
MideaLogger.debug(f"Message 'ERROR' received")
|
||||
elif result == ParseMessageResult.SUCCESS:
|
||||
timeout_counter = 0
|
||||
|
||||
# if self._protocol == 3:
|
||||
# self._send_message_v3(data, msg_type=MSGTYPE_ENCRYPTED_REQUEST)
|
||||
# else:
|
||||
# self._send_message_v2(data)
|
||||
|
||||
async def _send_heartbeat(self):
|
||||
msg = PacketBuilder(self._device_id, bytearray([0x00])).finalize(msg_type=0)
|
||||
await self._send_message(msg)
|
||||
|
||||
def _device_connected(self, connected=True):
|
||||
self._connected = connected
|
||||
|
||||
108
custom_components/midea_auto_cloud/core/lua_runtime.py
Normal file
108
custom_components/midea_auto_cloud/core/lua_runtime.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import traceback
|
||||
|
||||
import lupa
|
||||
import threading
|
||||
import json
|
||||
from .logger import MideaLogger
|
||||
|
||||
|
||||
class LuaRuntime:
|
||||
def __init__(self, file):
|
||||
self._runtimes = lupa.lua51.LuaRuntime()
|
||||
string = f'dofile("{file}")'
|
||||
self._runtimes.execute(string)
|
||||
self._lock = threading.Lock()
|
||||
self._json_to_data = self._runtimes.eval("function(param) return jsonToData(param) end")
|
||||
self._data_to_json = self._runtimes.eval("function(param) return dataToJson(param) end")
|
||||
|
||||
def json_to_data(self, json_value):
|
||||
with self._lock:
|
||||
result = self._json_to_data(json_value)
|
||||
|
||||
return result
|
||||
|
||||
def data_to_json(self, data_value):
|
||||
with self._lock:
|
||||
result = self._data_to_json(data_value)
|
||||
return result
|
||||
|
||||
|
||||
class MideaCodec(LuaRuntime):
|
||||
def __init__(self, file, device_type=None, sn=None, subtype=None):
|
||||
super().__init__(file)
|
||||
self._device_type = device_type
|
||||
self._sn = sn
|
||||
self._subtype = subtype
|
||||
|
||||
def _build_base_dict(self):
|
||||
device_info ={}
|
||||
if self._sn is not None:
|
||||
device_info["deviceSN"] = self._sn
|
||||
if self._subtype is not None:
|
||||
device_info["deviceSubType"] = self._subtype
|
||||
base_dict = {
|
||||
"deviceinfo": device_info
|
||||
}
|
||||
return base_dict
|
||||
|
||||
def build_query(self, append=None):
|
||||
query_dict = self._build_base_dict()
|
||||
query_dict["query"] = {} if append is None else append
|
||||
json_str = json.dumps(query_dict)
|
||||
try:
|
||||
result = self.json_to_data(json_str)
|
||||
return result
|
||||
except lupa.LuaError as e:
|
||||
MideaLogger.error(f"LuaRuntimeError in build_query {json_str}: {repr(e)}")
|
||||
return None
|
||||
|
||||
def build_control(self, append=None, status=None):
|
||||
query_dict = self._build_base_dict()
|
||||
query_dict["control"] = {} if append is None else append
|
||||
query_dict["status"] = {} if status is None else status
|
||||
# 针对T0xD9复式洗衣机特殊处理
|
||||
if self._device_type == "T0xD9":
|
||||
control_keys = list(append.keys())
|
||||
if len(control_keys) > 0:
|
||||
# 从第一个键名中提取前缀,例如从 'db_power' 中提取 'db'
|
||||
first_key = control_keys[0]
|
||||
prefix = first_key.split("_")[0]
|
||||
query_dict["control"]["bucket"] = prefix
|
||||
else:
|
||||
query_dict["control"]["bucket"] = "db"
|
||||
json_str = json.dumps(query_dict)
|
||||
MideaLogger.debug(f"LuaRuntime json_str {json_str}")
|
||||
try:
|
||||
result = self.json_to_data(json_str)
|
||||
MideaLogger.debug(f"LuaRuntime Result {result}")
|
||||
return result
|
||||
except lupa.LuaError as e:
|
||||
traceback.print_exc()
|
||||
MideaLogger.error(f"LuaRuntimeError in build_control {json_str}: {repr(e)}")
|
||||
return None
|
||||
|
||||
def build_status(self, append=None):
|
||||
query_dict = self._build_base_dict()
|
||||
query_dict["status"] = {} if append is None else append
|
||||
json_str = json.dumps(query_dict)
|
||||
try:
|
||||
result = self.json_to_data(json_str)
|
||||
return result
|
||||
except lupa.LuaError as e:
|
||||
MideaLogger.error(f"LuaRuntimeError in build_status {json_str}: {repr(e)}")
|
||||
return None
|
||||
|
||||
def decode_status(self, data: str):
|
||||
data_dict = self._build_base_dict()
|
||||
data_dict["msg"] = {
|
||||
"data": data
|
||||
}
|
||||
json_str = json.dumps(data_dict)
|
||||
try:
|
||||
result = self.data_to_json(json_str)
|
||||
status = json.loads(result)
|
||||
return status.get("status")
|
||||
except lupa.LuaError as e:
|
||||
MideaLogger.error(f"LuaRuntimeError in decode_status {data}: {repr(e)}")
|
||||
return None
|
||||
|
||||
50
custom_components/midea_auto_cloud/core/util.py
Normal file
50
custom_components/midea_auto_cloud/core/util.py
Normal file
@@ -0,0 +1,50 @@
|
||||
def bytes_to_dec_string(data: bytearray) -> bytearray:
|
||||
"""
|
||||
将 bytearray 转换为逗号分隔的十进制字符串格式,然后返回 bytearray
|
||||
对应 Java 的 bytesToDecString 方法
|
||||
"""
|
||||
# 处理有符号字节(模拟 Java 的 byte 类型 -128 到 127)
|
||||
result = []
|
||||
for b in data:
|
||||
# 将无符号字节转换为有符号字节
|
||||
signed_byte = b if b < 128 else b - 256
|
||||
result.append(str(signed_byte))
|
||||
|
||||
decimal_string = ','.join(result)
|
||||
return bytearray(decimal_string, 'utf-8')
|
||||
|
||||
|
||||
def dec_string_to_bytes(dec_string: str) -> bytearray:
|
||||
"""
|
||||
将逗号分隔的十进制字符串转换为字节数组
|
||||
对应 Java 的 decStringToBytes 方法
|
||||
|
||||
Args:
|
||||
dec_string: 逗号分隔的十进制字符串,如 "1,2,-3,127"
|
||||
|
||||
Returns:
|
||||
bytearray: 转换后的字节数组
|
||||
"""
|
||||
if dec_string is None:
|
||||
return bytearray()
|
||||
|
||||
# 按逗号分割字符串
|
||||
split_values = dec_string.split(',')
|
||||
result = bytearray(len(split_values))
|
||||
|
||||
for i, value_str in enumerate(split_values):
|
||||
try:
|
||||
# 解析十进制字符串为整数,然后转换为字节
|
||||
int_value = int(value_str.strip())
|
||||
# 确保值在字节范围内 (-128 到 127)
|
||||
if int_value < -128:
|
||||
int_value = -128
|
||||
elif int_value > 127:
|
||||
int_value = 127
|
||||
result[i] = int_value & 0xFF # 转换为无符号字节
|
||||
except (ValueError, IndexError) as e:
|
||||
# 如果解析失败,记录错误并跳过该值
|
||||
print(f"dec_string_to_bytes() error: {e}")
|
||||
result[i] = 0 # 默认值
|
||||
|
||||
return result
|
||||
@@ -90,16 +90,17 @@ class MideaDataUpdateCoordinator(DataUpdateCoordinator[MideaDeviceData]):
|
||||
return self.data
|
||||
|
||||
try:
|
||||
# 使用传入的 cloud 实例(若可用)
|
||||
cloud = self._cloud
|
||||
if cloud and hasattr(cloud, "get_device_status"):
|
||||
try:
|
||||
status = await cloud.get_device_status(self._device_id)
|
||||
if isinstance(status, dict) and len(status) > 0:
|
||||
for k, v in status.items():
|
||||
self.device.attributes[k] = v
|
||||
except Exception as e:
|
||||
MideaLogger.debug(f"Cloud status fetch failed: {e}")
|
||||
await self.device.refresh_status()
|
||||
# # 使用传入的 cloud 实例(若可用)
|
||||
# cloud = self._cloud
|
||||
# if cloud and hasattr(cloud, "get_device_status"):
|
||||
# try:
|
||||
# status = await cloud.get_device_status(self._device_id)
|
||||
# if isinstance(status, dict) and len(status) > 0:
|
||||
# for k, v in status.items():
|
||||
# self.device.attributes[k] = v
|
||||
# except Exception as e:
|
||||
# MideaLogger.debug(f"Cloud status fetch failed: {e}")
|
||||
|
||||
# 返回并推送当前状态
|
||||
updated = MideaDeviceData(
|
||||
@@ -120,26 +121,15 @@ class MideaDataUpdateCoordinator(DataUpdateCoordinator[MideaDeviceData]):
|
||||
async def async_set_attribute(self, attribute: str, value) -> None:
|
||||
"""Set a device attribute."""
|
||||
# 云端控制:构造 control 与 status(携带当前状态作为上下文)
|
||||
cloud = self._cloud
|
||||
control = {attribute: value}
|
||||
status = dict(self.device.attributes)
|
||||
if cloud and hasattr(cloud, "send_device_control"):
|
||||
ok = await cloud.send_device_control(self._device_id, control=control, status=status)
|
||||
if ok:
|
||||
# 本地先行更新,随后依赖轮询或设备事件校正
|
||||
self.device.attributes[attribute] = value
|
||||
await self.device.set_attribute(attribute, value)
|
||||
self.device.attributes[attribute] = value
|
||||
self.mute_state_update_for_a_while()
|
||||
self.async_update_listeners()
|
||||
|
||||
async def async_set_attributes(self, attributes: dict) -> None:
|
||||
"""Set multiple device attributes."""
|
||||
cloud = self._cloud
|
||||
control = dict(attributes)
|
||||
status = dict(self.device.attributes)
|
||||
if cloud and hasattr(cloud, "send_device_control"):
|
||||
ok = await cloud.send_device_control(self._device_id, control=control, status=status)
|
||||
if ok:
|
||||
self.device.attributes.update(attributes)
|
||||
await self.device.set_attributes(attributes)
|
||||
self.device.attributes.update(attributes)
|
||||
self.mute_state_update_for_a_while()
|
||||
self.async_update_listeners()
|
||||
|
||||
|
||||
60
custom_components/midea_auto_cloud/decrypt_lua.py
Normal file
60
custom_components/midea_auto_cloud/decrypt_lua.py
Normal file
File diff suppressed because one or more lines are too long
@@ -6,13 +6,51 @@ from homeassistant.components.switch import SwitchDeviceClass
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}, {"query_type": "prevent_straight_wind"}],
|
||||
"centralized": [
|
||||
"power", "temperature", "small_temperature", "mode", "eco",
|
||||
"comfort_power_save", "comfort_sleep", "strong_wind",
|
||||
"wind_swing_lr", "wind_swing_lr", "wind_speed","ptc", "dry"
|
||||
],
|
||||
"queries": [{}, {"query_type":"run_status"}],
|
||||
"centralized": [],
|
||||
"entities": {
|
||||
Platform.FAN: {
|
||||
"fan": {
|
||||
"power": "new_wind_machine",
|
||||
"speeds": [
|
||||
{"fresh_air_fan_speed": 20},
|
||||
{"fresh_air_fan_speed": 40},
|
||||
{"fresh_air_fan_speed": 60},
|
||||
{"fresh_air_fan_speed": 80},
|
||||
{"fresh_air_fan_speed": 100},
|
||||
],
|
||||
"preset_modes": {
|
||||
"heat_exchange": {
|
||||
"fresh_air_mode": 1,
|
||||
"wind_strength": 0
|
||||
},
|
||||
"smooth_in": {
|
||||
"fresh_air_mode": 2,
|
||||
"wind_strength": 0
|
||||
},
|
||||
"rough_in": {
|
||||
"fresh_air_mode": 2,
|
||||
"wind_strength": 1
|
||||
},
|
||||
"smooth_out": {
|
||||
"fresh_air_mode": 3,
|
||||
"wind_strength": 0
|
||||
},
|
||||
"rough_out": {
|
||||
"fresh_air_mode": 3,
|
||||
"wind_strength": 1
|
||||
},
|
||||
"auto": {
|
||||
"fresh_air_mode": 4,
|
||||
"wind_strength": 0
|
||||
},
|
||||
"innercycle": {
|
||||
"fresh_air_mode": 5,
|
||||
"wind_strength": 0
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.CLIMATE: {
|
||||
"thermostat": {
|
||||
"power": "power",
|
||||
@@ -28,12 +66,13 @@ DEVICE_MAPPING = {
|
||||
"none": {
|
||||
"eco": "off",
|
||||
"comfort_power_save": "off",
|
||||
"comfort_sleep": "off",
|
||||
"cool_power_saving": 0,
|
||||
# "comfort_sleep": "off",
|
||||
"strong_wind": "off"
|
||||
},
|
||||
"eco": {"eco": "on"},
|
||||
"eco": {"eco": "on", "cool_power_saving": 1},
|
||||
"comfort": {"comfort_power_save": "on"},
|
||||
"sleep": {"comfort_sleep": "on"},
|
||||
# "sleep": {"comfort_sleep": "on"},
|
||||
"boost": {"strong_wind": "on"}
|
||||
},
|
||||
"swing_modes": {
|
||||
@@ -60,6 +99,10 @@ DEVICE_MAPPING = {
|
||||
}
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"fresh_air_remove_odor": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [0, 1],
|
||||
},
|
||||
"dry": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
@@ -77,19 +120,19 @@ DEVICE_MAPPING = {
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"outdoor_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"indoor_humidity": {
|
||||
"device_class": SensorDeviceClass.HUMIDITY,
|
||||
"unit_of_measurement": "%",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"22012227": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}, {"query_type": "prevent_straight_wind"}],
|
||||
"queries": [{}],
|
||||
"centralized": ["power", "temperature", "small_temperature", "mode", "eco", "comfort_power_save",
|
||||
"comfort_sleep", "strong_wind", "wind_swing_lr", "wind_swing_ud", "wind_speed",
|
||||
"strong_wind", "wind_swing_lr", "wind_swing_ud", "wind_speed",
|
||||
"ptc", "dry"],
|
||||
|
||||
"entities": {
|
||||
@@ -108,12 +151,12 @@ DEVICE_MAPPING = {
|
||||
"none": {
|
||||
"eco": "off",
|
||||
"comfort_power_save": "off",
|
||||
"comfort_sleep": "off",
|
||||
# "comfort_sleep": "off",
|
||||
"strong_wind": "off"
|
||||
},
|
||||
"eco": {"eco": "on"},
|
||||
"comfort": {"comfort_power_save": "on"},
|
||||
"sleep": {"comfort_sleep": "on"},
|
||||
# "sleep": {"comfort_sleep": "on"},
|
||||
"boost": {"strong_wind": "on"}
|
||||
},
|
||||
"swing_modes": {
|
||||
@@ -155,8 +198,12 @@ DEVICE_MAPPING = {
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"indoor_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"outdoor_temperature": {
|
||||
"name": "室外机温度",
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
|
||||
183
custom_components/midea_auto_cloud/device_mapping/T0xB2.py
Normal file
183
custom_components/midea_auto_cloud/device_mapping/T0xB2.py
Normal file
@@ -0,0 +1,183 @@
|
||||
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}],
|
||||
"centralized": [
|
||||
"work_status", "work_mode", "lock", "furnace_light",
|
||||
"dissipate_heat", "pre_heat", "door_open", "lack_water"
|
||||
],
|
||||
"entities": {
|
||||
Platform.BINARY_SENSOR: {
|
||||
"lock": {
|
||||
"device_class": BinarySensorDeviceClass.LOCK,
|
||||
},
|
||||
"furnace_light": {
|
||||
"device_class": BinarySensorDeviceClass.LIGHT,
|
||||
},
|
||||
"dissipate_heat": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"pre_heat": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"door_open": {
|
||||
"device_class": BinarySensorDeviceClass.DOOR,
|
||||
},
|
||||
"lack_water": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
},
|
||||
"high_temperature_work": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"lack_box": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
},
|
||||
"clean_sink_ponding": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"clean_scale": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"flip_side": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"reaction": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"ramadan": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"change_water": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"execute": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"work_status": {
|
||||
"options": {
|
||||
"standby": {"work_status": "standby"},
|
||||
"working": {"work_status": "working"},
|
||||
"pause": {"work_status": "pause"},
|
||||
"finish": {"work_status": "finish"},
|
||||
"error": {"work_status": "error"}
|
||||
}
|
||||
},
|
||||
"work_mode": {
|
||||
"options": {
|
||||
"off": {"work_mode": "0"},
|
||||
"steam": {"work_mode": "1"},
|
||||
"cook": {"work_mode": "2"},
|
||||
"fry": {"work_mode": "3"},
|
||||
"bake": {"work_mode": "4"},
|
||||
"roast": {"work_mode": "5"},
|
||||
"stew": {"work_mode": "6"},
|
||||
"soup": {"work_mode": "7"},
|
||||
"rice": {"work_mode": "8"},
|
||||
"porridge": {"work_mode": "9"},
|
||||
"yogurt": {"work_mode": "10"},
|
||||
"ferment": {"work_mode": "11"},
|
||||
"defrost": {"work_mode": "12"},
|
||||
"keep_warm": {"work_mode": "13"},
|
||||
"clean": {"work_mode": "14"},
|
||||
"custom": {"work_mode": "ff"}
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"work_hour": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.HOURS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"work_minute": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"work_second": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.SECONDS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"cur_temperature_above": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"cur_temperature_underside": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"weight": {
|
||||
"device_class": SensorDeviceClass.WEIGHT,
|
||||
"unit_of_measurement": "g",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"people_number": {
|
||||
"device_class": SensorDeviceClass.DATA_RATE,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"steam_quantity": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"totalstep": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"stepnum": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"hour_set": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.HOURS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"minute_set": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"second_set": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.SECONDS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"ota": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"version": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"cbs_version": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"cloudmenuid": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,10 +188,6 @@ DEVICE_MAPPING = {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.SECONDS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"version": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +105,7 @@ DEVICE_MAPPING = {
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"dust_count": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"area": {
|
||||
"device_class": SensorDeviceClass.AREA,
|
||||
@@ -119,12 +118,10 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"switch_status": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"water_station_status": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"work_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
@@ -137,8 +134,7 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"planner_status": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"sweep_then_mop_mode_progress": {
|
||||
"device_class": SensorDeviceClass.BATTERY,
|
||||
@@ -146,12 +142,10 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"error_desc": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"station_error_desc": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
309
custom_components/midea_auto_cloud/device_mapping/T0xCA.py
Normal file
309
custom_components/midea_auto_cloud/device_mapping/T0xCA.py
Normal file
@@ -0,0 +1,309 @@
|
||||
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, PERCENTAGE, PRECISION_HALVES
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}],
|
||||
"centralized": [
|
||||
"freezing_mode", "intelligent_mode", "energy_saving_mode", "holiday_mode",
|
||||
"moisturize_mode", "preservation_mode", "acme_freezing_mode", "variable_mode",
|
||||
"storage_power", "left_flexzone_power", "right_flexzone_power", "freezing_power",
|
||||
"function_zone_power", "storage_temperature", "freezing_temperature",
|
||||
"left_flexzone_temperature", "right_flexzone_temperature"
|
||||
],
|
||||
"entities": {
|
||||
Platform.SWITCH: {
|
||||
"freezing_mode": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"intelligent_mode": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"holiday_mode": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"moisturize_mode": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"preservation_mode": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"acme_freezing_mode": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"storage_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"left_flexzone_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"right_flexzone_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"freezing_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"function_zone_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"cross_peak_electricity": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"all_refrigeration_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"remove_dew_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"humidify_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"unfreeze_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"floodlight_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"radar_mode_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"milk_mode_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"icea_mode_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"plasma_aseptic_mode_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"acquire_icea_mode_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"brash_icea_mode_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"acquire_water_mode_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"freezing_ice_machine_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"microcrystal_fresh": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"dry_zone": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"electronic_smell": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"eradicate_pesticide_residue": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"performance_mode": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"ice_mouth_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
}
|
||||
},
|
||||
Platform.BINARY_SENSOR: {
|
||||
"storage_door_state": {
|
||||
"device_class": BinarySensorDeviceClass.DOOR,
|
||||
},
|
||||
"freezer_door_state": {
|
||||
"device_class": BinarySensorDeviceClass.DOOR,
|
||||
},
|
||||
"flexzone_door_state": {
|
||||
"device_class": BinarySensorDeviceClass.DOOR,
|
||||
},
|
||||
"storage_ice_home_door_state": {
|
||||
"device_class": BinarySensorDeviceClass.DOOR,
|
||||
},
|
||||
"bar_door_state": {
|
||||
"device_class": BinarySensorDeviceClass.DOOR,
|
||||
},
|
||||
"is_error": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
}
|
||||
},
|
||||
Platform.CLIMATE: {
|
||||
"storage_zone": {
|
||||
"power": "storage_power",
|
||||
"hvac_modes": {
|
||||
"off": {"storage_power": "off"},
|
||||
"heat": {"storage_power": "on"}
|
||||
},
|
||||
"target_temperature": "storage_temperature",
|
||||
"current_temperature": "refrigeration_real_temperature",
|
||||
"min_temp": -10,
|
||||
"max_temp": 10,
|
||||
"temperature_unit": UnitOfTemperature.CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
},
|
||||
"freezing_zone": {
|
||||
"power": "freezing_power",
|
||||
"hvac_modes": {
|
||||
"off": {"freezing_power": "off"},
|
||||
"heat": {"freezing_power": "on"}
|
||||
},
|
||||
"target_temperature": "freezing_temperature",
|
||||
"current_temperature": "freezing_real_temperature",
|
||||
"min_temp": -30,
|
||||
"max_temp": -10,
|
||||
"temperature_unit": UnitOfTemperature.CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
},
|
||||
"left_flexzone": {
|
||||
"power": "left_flexzone_power",
|
||||
"hvac_modes": {
|
||||
"off": {"left_flexzone_power": "off"},
|
||||
"heat": {"left_flexzone_power": "on"}
|
||||
},
|
||||
"target_temperature": "left_flexzone_temperature",
|
||||
"current_temperature": "left_variable_real_temperature",
|
||||
"min_temp": -30,
|
||||
"max_temp": 10,
|
||||
"temperature_unit": UnitOfTemperature.CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
},
|
||||
"right_flexzone": {
|
||||
"power": "right_flexzone_power",
|
||||
"hvac_modes": {
|
||||
"off": {"right_flexzone_power": "off"},
|
||||
"heat": {"right_flexzone_power": "on"}
|
||||
},
|
||||
"target_temperature": "right_flexzone_temperature",
|
||||
"current_temperature": "right_variable_real_temperature",
|
||||
"min_temp": -30,
|
||||
"max_temp": 10,
|
||||
"temperature_unit": UnitOfTemperature.CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"variable_mode": {
|
||||
"options": {
|
||||
"none_mode": {"variable_mode": "none_mode"},
|
||||
"freezing": {"variable_mode": "freezing"},
|
||||
"refrigeration": {"variable_mode": "refrigeration"},
|
||||
"wine": {"variable_mode": "wine"},
|
||||
"vegetable": {"variable_mode": "vegetable"}
|
||||
}
|
||||
},
|
||||
"icea_bar_function_switch": {
|
||||
"options": {
|
||||
"default": {"icea_bar_function_switch": "default"},
|
||||
"ice": {"icea_bar_function_switch": "ice"},
|
||||
"water": {"icea_bar_function_switch": "water"},
|
||||
"off": {"icea_bar_function_switch": "off"}
|
||||
}
|
||||
},
|
||||
"food_site": {
|
||||
"options": {
|
||||
"left_freezing_room": {"food_site": "left_freezing_room"},
|
||||
"right_freezing_room": {"food_site": "right_freezing_room"},
|
||||
"storage_room": {"food_site": "storage_room"},
|
||||
"function_zone": {"food_site": "function_zone"}
|
||||
}
|
||||
},
|
||||
"temperature_unit": {
|
||||
"options": {
|
||||
"celsius": {"temperature_unit": "celsius"},
|
||||
"fahrenheit": {"temperature_unit": "fahrenheit"}
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"storage_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"freezing_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"left_flexzone_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"right_flexzone_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"refrigeration_real_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"freezing_real_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"left_variable_real_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"right_variable_real_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"interval_room_humidity_level": {
|
||||
"device_class": SensorDeviceClass.HUMIDITY,
|
||||
"unit_of_measurement": PERCENTAGE,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"normal_zone_level": {
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"function_zone_level": {
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"freeze_fahrenheit_level": {
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"refrigeration_fahrenheit_level": {
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"leach_expire_day": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.DAYS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"power_consumption_low": {
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
"unit_of_measurement": "W",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"power_consumption_high": {
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
"unit_of_measurement": "W",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"fast_cold_minute": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"fast_freeze_minute": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
214
custom_components/midea_auto_cloud/device_mapping/T0xCC.py
Normal file
214
custom_components/midea_auto_cloud/device_mapping/T0xCC.py
Normal file
@@ -0,0 +1,214 @@
|
||||
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_HALVES
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}, {"query_type":"run_status"}],
|
||||
"centralized": [],
|
||||
"entities": {
|
||||
Platform.CLIMATE: {
|
||||
"thermostat": {
|
||||
"power": "power",
|
||||
"hvac_modes": {
|
||||
"off": {"power": "off"},
|
||||
"heat": {"power": "on", "mode.current": "heat"},
|
||||
"cool": {"power": "on", "mode.current": "cool"},
|
||||
"dry": {"power": "on", "mode.current": "dry"},
|
||||
"fan_only": {"power": "on", "mode.current": "fan"}
|
||||
},
|
||||
"preset_modes": {
|
||||
"none": {
|
||||
"eco.status": "off",
|
||||
"strong.status": "off",
|
||||
"sterilize.status": "off",
|
||||
"selfclean.status": "off",
|
||||
"humidification.value": "0"
|
||||
},
|
||||
"eco": {"eco.status": "on"},
|
||||
"boost": {"strong.status": "on"},
|
||||
"sterilize": {"sterilize.status": "on"},
|
||||
"selfclean": {"selfclean.status": "on"},
|
||||
"humidify": {"humidification.value": "1"}
|
||||
},
|
||||
"swing_modes": {
|
||||
"off": {"swing.multiple": "false"},
|
||||
"both": {"swing.multiple": "true"},
|
||||
"horizontal": {"swing.louver_horizontal.enable": "true"},
|
||||
"vertical": {"swing.louver_vertical.enable": "true"}
|
||||
},
|
||||
"fan_modes": {
|
||||
"silent": {"wind_speed.level": 1},
|
||||
"low": {"wind_speed.level": 2},
|
||||
"medium": {"wind_speed.level": 3},
|
||||
"high": {"wind_speed.level": 4},
|
||||
"full": {"wind_speed.level": 5},
|
||||
"auto": {"wind_speed.level": 6}
|
||||
},
|
||||
"target_temperature": "temperature.current",
|
||||
"current_temperature": "temperature.room",
|
||||
"min_temp": 17,
|
||||
"max_temp": 30,
|
||||
"temperature_unit": UnitOfTemperature.CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"eco": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "eco.status"
|
||||
},
|
||||
"strong": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "strong.status"
|
||||
},
|
||||
"selfclean": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "selfclean.status"
|
||||
},
|
||||
"diagnose": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "diagnose.status"
|
||||
},
|
||||
"idu_silent": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "idu_silent.status"
|
||||
},
|
||||
"idu_light": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "idu_light"
|
||||
},
|
||||
"idu_sleep": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "idu_sleep.status"
|
||||
},
|
||||
"filter_notification": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "filter_notification.status"
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"room_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "temperature.room"
|
||||
},
|
||||
"outside_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "temperature.outside"
|
||||
},
|
||||
"co2_value": {
|
||||
"device_class": SensorDeviceClass.CO2,
|
||||
"unit_of_measurement": "ppm",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "co2.value"
|
||||
},
|
||||
"hcho_value": {
|
||||
"device_class": SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
"unit_of_measurement": "μg/m³",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "hcho.value"
|
||||
},
|
||||
"pm25_value": {
|
||||
"device_class": SensorDeviceClass.PM25,
|
||||
"unit_of_measurement": "μg/m³",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "pm2_5.value"
|
||||
},
|
||||
"wind_speed_level": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"attribute": "wind_speed.level"
|
||||
},
|
||||
"timer_on_timeout": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": "min",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "timer.on.timeout"
|
||||
},
|
||||
"timer_off_timeout": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": "min",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "timer.off.timeout"
|
||||
},
|
||||
"selfclean_time_left": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": "min",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "selfclean.time_left"
|
||||
},
|
||||
"backup_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": "min",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "backup.time"
|
||||
},
|
||||
"cur_fault_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"attribute": "cur_fault.code"
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"mode": {
|
||||
"options": {
|
||||
"cool": {"mode.current": "cool"},
|
||||
"dry": {"mode.current": "dry"},
|
||||
"fan": {"mode.current": "fan"},
|
||||
"heat": {"mode.current": "heat"}
|
||||
},
|
||||
"attribute": "mode.current"
|
||||
},
|
||||
"ptc": {
|
||||
"options": {
|
||||
"auto": {"ptc.status": "auto"},
|
||||
"on": {"ptc.status": "on"},
|
||||
"off": {"ptc.status": "off"},
|
||||
"separate": {"ptc.status": "separate"}
|
||||
},
|
||||
"attribute": "ptc.status"
|
||||
},
|
||||
"wind_feeling_mode": {
|
||||
"options": {
|
||||
"close": {"wind_feeling.current": "close"},
|
||||
"soft": {"wind_feeling.current": "soft"}
|
||||
},
|
||||
"attribute": "wind_feeling.current"
|
||||
},
|
||||
"swing_louver": {
|
||||
"options": {
|
||||
"1": {"swing.louver1": "1"},
|
||||
"2": {"swing.louver1": "2"},
|
||||
"3": {"swing.louver1": "3"},
|
||||
"4": {"swing.louver1": "4"},
|
||||
"5": {"swing.louver1": "5"}
|
||||
},
|
||||
"attribute": "swing.louver1"
|
||||
},
|
||||
"swing_horizontal": {
|
||||
"options": {
|
||||
"1": {"swing.louver_horizontal.level": "1"},
|
||||
"2": {"swing.louver_horizontal.level": "2"},
|
||||
"3": {"swing.louver_horizontal.level": "3"},
|
||||
"4": {"swing.louver_horizontal.level": "4"},
|
||||
"5": {"swing.louver_horizontal.level": "5"}
|
||||
},
|
||||
"attribute": "swing.louver_horizontal.level"
|
||||
},
|
||||
"swing_vertical": {
|
||||
"options": {
|
||||
"1": {"swing.louver_vertical.level": "1"},
|
||||
"2": {"swing.louver_vertical.level": "2"},
|
||||
"3": {"swing.louver_vertical.level": "3"},
|
||||
"4": {"swing.louver_vertical.level": "4"},
|
||||
"5": {"swing.louver_vertical.level": "5"}
|
||||
},
|
||||
"attribute": "swing.louver_vertical.level"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,8 +135,7 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"humidity_set": {
|
||||
"device_class": SensorDeviceClass.HUMIDITY,
|
||||
@@ -178,8 +177,7 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"machine_type": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,8 +109,7 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"heat_max_set_temp": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
|
||||
@@ -5,7 +5,8 @@ from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": [0, 1],
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}],
|
||||
"calculate": {
|
||||
"get": [
|
||||
{
|
||||
@@ -17,110 +18,135 @@ DEVICE_MAPPING = {
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
Platform.SWITCH: {
|
||||
Platform.BINARY_SENSOR: {
|
||||
"db_power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"db_clean_notification": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"db_softener_needed": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"db_detergent_needed": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"db_nightly_wash": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [0, 1],
|
||||
},
|
||||
"db_baby_lock": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [0, 1],
|
||||
},
|
||||
"db_light": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [0, 1],
|
||||
},
|
||||
"db_steam_wash": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [0, 1],
|
||||
},
|
||||
"db_fast_clean_wash": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [0, 1],
|
||||
},
|
||||
"db_wash_dry_link": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [0, 1],
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"db_running_status": {
|
||||
"options": {
|
||||
"stop": {"db_running_status": "stop"},
|
||||
"start": {"db_running_status": "start"},
|
||||
"pause": {"db_running_status": "pause"},
|
||||
"finish": {"db_running_status": "finish"},
|
||||
"error": {"db_running_status": "error"}
|
||||
"off": {"db_power": "off", "db_running_status": "off"},
|
||||
"standby": {"db_power": "on", "db_running_status": "standby"},
|
||||
"start": {"db_power": "on", "db_running_status": "start"},
|
||||
"pause": {"db_power": "on", "db_running_status": "pause"},
|
||||
"end": {"db_power": "on", "db_running_status": "end"},
|
||||
"fault": {"db_power": "on", "db_running_status": "fault"},
|
||||
"delay": {"db_power": "on", "db_running_status": "delay"}
|
||||
}
|
||||
},
|
||||
"db_program": {
|
||||
"options": {
|
||||
"fast_wash_30": {"db_program": "fast_wash_30"},
|
||||
"normal_wash": {"db_program": "normal_wash"},
|
||||
"heavy_wash": {"db_program": "heavy_wash"},
|
||||
"delicate_wash": {"db_program": "delicate_wash"},
|
||||
"cotton": {"db_program": "cotton"},
|
||||
"eco": {"db_program": "eco"},
|
||||
"fast_wash": {"db_program": "fast_wash"},
|
||||
"mixed_wash": {"db_program": "mixed_wash"},
|
||||
"wool": {"db_program": "wool"},
|
||||
"ssp": {"db_program": "ssp"},
|
||||
"sport_clothes": {"db_program": "sport_clothes"},
|
||||
"single_dehytration": {"db_program": "single_dehytration"},
|
||||
"rinsing_dehydration": {"db_program": "rinsing_dehydration"},
|
||||
"big": {"db_program": "big"},
|
||||
"baby_clothes": {"db_program": "baby_clothes"},
|
||||
"down_jacket": {"db_program": "down_jacket"},
|
||||
"color": {"db_program": "color"},
|
||||
"intelligent": {"db_program": "intelligent"},
|
||||
"quick_wash": {"db_program": "quick_wash"},
|
||||
"eco_wash": {"db_program": "eco_wash"}
|
||||
}
|
||||
},
|
||||
"db_water_level": {
|
||||
"options": {
|
||||
"low": {"db_water_level": "1"},
|
||||
"medium": {"db_water_level": "2"},
|
||||
"high": {"db_water_level": "3"},
|
||||
"extra_high": {"db_water_level": "4"}
|
||||
}
|
||||
},
|
||||
"db_temperature": {
|
||||
"options": {
|
||||
"cold": {"db_temperature": "1"},
|
||||
"warm": {"db_temperature": "2"},
|
||||
"hot": {"db_temperature": "3"},
|
||||
"extra_hot": {"db_temperature": "4"}
|
||||
}
|
||||
},
|
||||
"dehydration_speed": {
|
||||
"options": {
|
||||
"low": {"dehydration_speed": "1"},
|
||||
"medium": {"dehydration_speed": "2"},
|
||||
"high": {"dehydration_speed": "3"},
|
||||
"extra_high": {"dehydration_speed": "4"}
|
||||
}
|
||||
},
|
||||
"db_detergent": {
|
||||
"options": {
|
||||
"none": {"db_detergent": "1"},
|
||||
"little": {"db_detergent": "2"},
|
||||
"normal": {"db_detergent": "3"},
|
||||
"more": {"db_detergent": "4"}
|
||||
}
|
||||
},
|
||||
"db_softener": {
|
||||
"options": {
|
||||
"none": {"db_softener": "1"},
|
||||
"little": {"db_softener": "2"},
|
||||
"normal": {"db_softener": "3"},
|
||||
"more": {"db_softener": "4"}
|
||||
}
|
||||
},
|
||||
"db_position": {
|
||||
"options": {
|
||||
"position_1": {"db_position": "1"},
|
||||
"position_2": {"db_position": "2"},
|
||||
"position_3": {"db_position": "3"}
|
||||
}
|
||||
},
|
||||
"db_location": {
|
||||
"options": {
|
||||
"location_1": {"db_location": "1"},
|
||||
"location_2": {"db_location": "2"},
|
||||
"location_3": {"db_location": "3"}
|
||||
"shirt": {"db_program": "shirt"},
|
||||
"fiber": {"db_program": "fiber"},
|
||||
"enzyme": {"db_program": "enzyme"},
|
||||
"underwear": {"db_program": "underwear"},
|
||||
"outdoor": {"db_program": "outdoor"},
|
||||
"air_wash": {"db_program": "air_wash"},
|
||||
"single_drying": {"db_program": "single_drying"},
|
||||
"steep": {"db_program": "steep"},
|
||||
"kids": {"db_program": "kids"},
|
||||
"water_baby_clothes": {"db_program": "water_baby_clothes"},
|
||||
"fast_wash_30": {"db_program": "fast_wash_30"},
|
||||
"water_shirt": {"db_program": "water_shirt"},
|
||||
"water_mixed_wash": {"db_program": "water_mixed_wash"},
|
||||
"water_fiber": {"db_program": "water_fiber"},
|
||||
"water_kids": {"db_program": "water_kids"},
|
||||
"water_underwear": {"db_program": "water_underwear"},
|
||||
"specialist": {"db_program": "specialist"},
|
||||
"love": {"db_program": "love"},
|
||||
"water_intelligent": {"db_program": "water_intelligent"},
|
||||
"water_steep": {"db_program": "water_steep"},
|
||||
"water_fast_wash_30": {"db_program": "water_fast_wash_30"},
|
||||
"new_water_cotton": {"db_program": "new_water_cotton"},
|
||||
"water_eco": {"db_program": "water_eco"},
|
||||
"wash_drying_60": {"db_program": "wash_drying_60"},
|
||||
"self_wash_5": {"db_program": "self_wash_5"},
|
||||
"fast_wash_min": {"db_program": "fast_wash_min"},
|
||||
"mixed_wash_min": {"db_program": "mixed_wash_min"},
|
||||
"dehydration_min": {"db_program": "dehydration_min"},
|
||||
"self_wash_min": {"db_program": "self_wash_min"},
|
||||
"baby_clothes_min": {"db_program": "baby_clothes_min"},
|
||||
"silk_wash": {"db_program": "silk_wash"},
|
||||
"prevent_allergy": {"db_program": "prevent_allergy"},
|
||||
"cold_wash": {"db_program": "cold_wash"},
|
||||
"soft_wash": {"db_program": "soft_wash"},
|
||||
"remove_mite_wash": {"db_program": "remove_mite_wash"},
|
||||
"water_intense_wash": {"db_program": "water_intense_wash"},
|
||||
"fast_dry": {"db_program": "fast_dry"},
|
||||
"water_outdoor": {"db_program": "water_outdoor"},
|
||||
"spring_autumn_wash": {"db_program": "spring_autumn_wash"},
|
||||
"summer_wash": {"db_program": "summer_wash"},
|
||||
"winter_wash": {"db_program": "winter_wash"},
|
||||
"jean": {"db_program": "jean"},
|
||||
"new_clothes_wash": {"db_program": "new_clothes_wash"},
|
||||
"silk": {"db_program": "silk"},
|
||||
"insight_wash": {"db_program": "insight_wash"},
|
||||
"fitness_clothes": {"db_program": "fitness_clothes"},
|
||||
"mink": {"db_program": "mink"},
|
||||
"fresh_air": {"db_program": "fresh_air"},
|
||||
"bucket_dry": {"db_program": "bucket_dry"},
|
||||
"jacket": {"db_program": "jacket"},
|
||||
"bath_towel": {"db_program": "bath_towel"},
|
||||
"night_fresh_wash": {"db_program": "night_fresh_wash"},
|
||||
"degerm": {"db_program": "degerm"},
|
||||
"heart_wash": {"db_program": "heart_wash"},
|
||||
"water_cold_wash": {"db_program": "water_cold_wash"},
|
||||
"water_prevent_allergy": {"db_program": "water_prevent_allergy"},
|
||||
"water_remove_mite_wash": {"db_program": "water_remove_mite_wash"},
|
||||
"water_ssp": {"db_program": "water_ssp"},
|
||||
"standard": {"db_program": "standard"},
|
||||
"green_wool": {"db_program": "green_wool"},
|
||||
"cook_wash": {"db_program": "cook_wash"},
|
||||
"fresh_remove_wrinkle": {"db_program": "fresh_remove_wrinkle"},
|
||||
"steam_sterilize_wash": {"db_program": "steam_sterilize_wash"},
|
||||
"sterilize_wash": {"db_program": "sterilize_wash"},
|
||||
"white_clothes_clean": {"db_program": "white_clothes_clean"},
|
||||
"clean_stains": {"db_program": "clean_stains"},
|
||||
"prevent_cross_color": {"db_program": "prevent_cross_color"},
|
||||
"quick_dry_clothes": {"db_program": "quick_dry_clothes"},
|
||||
"yoga_clothes": {"db_program": "yoga_clothes"}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -136,8 +162,7 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"db_error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"db_set_dewater_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
@@ -149,13 +174,8 @@ DEVICE_MAPPING = {
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"db_device_software_version": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"db_rinse_count": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"db_wash_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
@@ -168,8 +188,7 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"db_appointment": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"db_dehydration_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
@@ -177,8 +196,7 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"db_cycle_memory": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": [0, 1],
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}],
|
||||
"calculate": {
|
||||
"get": [
|
||||
{
|
||||
@@ -17,10 +18,12 @@ DEVICE_MAPPING = {
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
Platform.SWITCH: {
|
||||
Platform.BINARY_SENSOR: {
|
||||
"power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"softener_lack": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
@@ -65,6 +68,7 @@ DEVICE_MAPPING = {
|
||||
},
|
||||
"silent": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": ["0", "1"],
|
||||
},
|
||||
"speedy": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
@@ -107,151 +111,218 @@ DEVICE_MAPPING = {
|
||||
},
|
||||
"ultraviolet_lamp": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": ["0", "1"],
|
||||
},
|
||||
"eye_wash": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": ["0", "1"],
|
||||
},
|
||||
"microbubble": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": ["0", "1"],
|
||||
},
|
||||
"wind_dispel": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": ["0", "1"],
|
||||
},
|
||||
"cycle_memory": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
}
|
||||
},
|
||||
"disinfectant": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": ["0", "1"],
|
||||
},
|
||||
"add_rinse": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": ["0", "1"],
|
||||
},
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"running_status": {
|
||||
"options": {
|
||||
"standby": {"running_status": "standby"},
|
||||
"running": {"running_status": "running"},
|
||||
"pause": {"running_status": "pause"},
|
||||
"finish": {"running_status": "finish"},
|
||||
"error": {"running_status": "error"}
|
||||
"off": {"power": "off", "running_status": "off"},
|
||||
"standby": {"power": "on", "running_status": "standby"},
|
||||
"start": {"power": "on", "running_status": "start"},
|
||||
"pause": {"power": "on", "running_status": "pause"},
|
||||
"end": {"power": "on", "running_status": "end"},
|
||||
"fault": {"power": "on", "running_status": "fault"},
|
||||
"delay": {"power": "on", "running_status": "delay"}
|
||||
}
|
||||
},
|
||||
"db_dehydration_speed": {
|
||||
"dehydration_speed": {
|
||||
"options": {
|
||||
"low": {"db_dehydration_speed": "1"},
|
||||
"medium": {"db_dehydration_speed": "2"},
|
||||
"high": {"db_dehydration_speed": "3"},
|
||||
"extra_high": {"db_dehydration_speed": "4"}
|
||||
"0": {"dehydration_speed": "0"},
|
||||
"400": {"dehydration_speed": "400"},
|
||||
"600": {"dehydration_speed": "600"},
|
||||
"800": {"dehydration_speed": "800"},
|
||||
"1000": {"dehydration_speed": "1000"},
|
||||
"1200": {"dehydration_speed": "1200"},
|
||||
"1400": {"dehydration_speed": "1400"},
|
||||
"1600": {"dehydration_speed": "1600"},
|
||||
"1300": {"dehydration_speed": "1300"}
|
||||
}
|
||||
},
|
||||
"mode": {
|
||||
"options": {
|
||||
"normal": {"mode": "normal"},
|
||||
"eco": {"mode": "eco"},
|
||||
"quick": {"mode": "quick"},
|
||||
"heavy": {"mode": "heavy"},
|
||||
"delicate": {"mode": "delicate"}
|
||||
"factory_test": {"mode": "factory_test"},
|
||||
"service": {"mode": "service"},
|
||||
"normal_continus": {"mode": "normal_continus"}
|
||||
}
|
||||
},
|
||||
"water_level": {
|
||||
"options": {
|
||||
"low": {"water_level": "low"},
|
||||
"medium": {"water_level": "medium"},
|
||||
"medium": {"water_level": "mid"},
|
||||
"high": {"water_level": "high"},
|
||||
"extra_high": {"water_level": "extra_high"}
|
||||
"auto": {"water_level": "auto"}
|
||||
}
|
||||
},
|
||||
"program": {
|
||||
"options": {
|
||||
"ssp": {"program": "ssp"},
|
||||
"cotton": {"program": "cotton"},
|
||||
"synthetic": {"program": "synthetic"},
|
||||
"eco": {"program": "eco"},
|
||||
"fast_wash": {"program": "fast_wash"},
|
||||
"mixed_wash": {"program": "mixed_wash"},
|
||||
"wool": {"program": "wool"},
|
||||
"delicate": {"program": "delicate"},
|
||||
"quick": {"program": "quick"}
|
||||
"ssp": {"program": "ssp"},
|
||||
"sport_clothes": {"program": "sport_clothes"},
|
||||
"single_dehytration": {"program": "single_dehytration"},
|
||||
"rinsing_dehydration": {"program": "rinsing_dehydration"},
|
||||
"big": {"program": "big"},
|
||||
"baby_clothes": {"program": "baby_clothes"},
|
||||
"down_jacket": {"program": "down_jacket"},
|
||||
"color": {"program": "color"},
|
||||
"intelligent": {"program": "intelligent"},
|
||||
"quick_wash": {"program": "quick_wash"},
|
||||
"shirt": {"program": "shirt"},
|
||||
"fiber": {"program": "fiber"},
|
||||
"enzyme": {"program": "enzyme"},
|
||||
"underwear": {"program": "underwear"},
|
||||
"outdoor": {"program": "outdoor"},
|
||||
"air_wash": {"program": "air_wash"},
|
||||
"single_drying": {"program": "single_drying"},
|
||||
"steep": {"program": "steep"},
|
||||
"kids": {"program": "kids"},
|
||||
"water_cotton": {"program": "water_cotton"},
|
||||
"fast_wash_30": {"program": "fast_wash_30"},
|
||||
"fast_wash_60": {"program": "fast_wash_60"},
|
||||
"water_mixed_wash": {"program": "water_mixed_wash"},
|
||||
"water_fiber": {"program": "water_fiber"},
|
||||
"water_kids": {"program": "water_kids"},
|
||||
"water_underwear": {"program": "water_underwear"},
|
||||
"specialist": {"program": "specialist"},
|
||||
"love": {"program": "love"},
|
||||
"water_intelligent": {"program": "water_intelligent"},
|
||||
"water_steep": {"program": "water_steep"},
|
||||
"water_fast_wash_30": {"program": "water_fast_wash_30"},
|
||||
"new_water_cotton": {"program": "new_water_cotton"},
|
||||
"water_eco": {"program": "water_eco"},
|
||||
"wash_drying_60": {"program": "wash_drying_60"},
|
||||
"self_wash_5": {"program": "self_wash_5"},
|
||||
"fast_wash_min": {"program": "fast_wash_min"},
|
||||
"mixed_wash_min": {"program": "mixed_wash_min"},
|
||||
"dehydration_min": {"program": "dehydration_min"},
|
||||
"self_wash_min": {"program": "self_wash_min"},
|
||||
"baby_clothes_min": {"program": "baby_clothes_min"},
|
||||
"diy0": {"program": "diy0"},
|
||||
"diy1": {"program": "diy1"},
|
||||
"diy2": {"program": "diy2"},
|
||||
"silk_wash": {"program": "silk_wash"},
|
||||
"prevent_allergy": {"program": "prevent_allergy"},
|
||||
"cold_wash": {"program": "cold_wash"},
|
||||
"soft_wash": {"program": "soft_wash"},
|
||||
"remove_mite_wash": {"program": "remove_mite_wash"},
|
||||
"water_intense_wash": {"program": "water_intense_wash"},
|
||||
"fast_dry": {"program": "fast_dry"},
|
||||
"water_outdoor": {"program": "water_outdoor"},
|
||||
"spring_autumn_wash": {"program": "spring_autumn_wash"},
|
||||
"summer_wash": {"program": "summer_wash"},
|
||||
"winter_wash": {"program": "winter_wash"},
|
||||
"jean": {"program": "jean"},
|
||||
"new_clothes_wash": {"program": "new_clothes_wash"},
|
||||
"silk": {"program": "silk"},
|
||||
"insight_wash": {"program": "insight_wash"},
|
||||
"fitness_clothes": {"program": "fitness_clothes"},
|
||||
"mink": {"program": "mink"},
|
||||
"fresh_air": {"program": "fresh_air"},
|
||||
"bucket_dry": {"program": "bucket_dry"},
|
||||
"jacket": {"program": "jacket"},
|
||||
"bath_towel": {"program": "bath_towel"},
|
||||
"night_fresh_wash": {"program": "night_fresh_wash"},
|
||||
"heart_wash": {"program": "heart_wash"},
|
||||
"water_cold_wash": {"program": "water_cold_wash"},
|
||||
"water_prevent_allergy": {"program": "water_prevent_allergy"},
|
||||
"water_remove_mite_wash": {"program": "water_remove_mite_wash"},
|
||||
"water_ssp": {"program": "water_ssp"},
|
||||
"standard": {"program": "standard"},
|
||||
"green_wool": {"program": "green_wool"},
|
||||
"cook_wash": {"program": "cook_wash"},
|
||||
"fresh_remove_wrinkle": {"program": "fresh_remove_wrinkle"},
|
||||
"steam_sterilize_wash": {"program": "steam_sterilize_wash"},
|
||||
"aromatherapy": {"program": "aromatherapy"},
|
||||
"sterilize_wash": {"program": "sterilize_wash"},
|
||||
"white_clothes_clean": {"program": "white_clothes_clean"},
|
||||
"clean_stains": {"program": "clean_stains"},
|
||||
"tube_clean_all": {"program": "tube_clean_all"},
|
||||
"no_channeling_color": {"program": "no_channeling_color"},
|
||||
"scald_wash": {"program": "scald_wash"},
|
||||
"hanfu_spring_summer": {"program": "hanfu_spring_summer"},
|
||||
"hanfu_autumn_winter": {"program": "hanfu_autumn_winter"},
|
||||
"skin_care_wash": {"program": "skin_care_wash"},
|
||||
"hanfu_wash": {"program": "hanfu_wash"}
|
||||
}
|
||||
},
|
||||
"temperature": {
|
||||
"options": {
|
||||
"cold": {"temperature": "cold"},
|
||||
"warm": {"temperature": "warm"},
|
||||
"hot": {"temperature": "hot"},
|
||||
"extra_hot": {"temperature": "extra_hot"}
|
||||
}
|
||||
},
|
||||
"detergent_density": {
|
||||
"options": {
|
||||
"low": {"detergent_density": "low"},
|
||||
"medium": {"detergent_density": "medium"},
|
||||
"high": {"detergent_density": "high"},
|
||||
"extra_high": {"detergent_density": "extra_high"}
|
||||
}
|
||||
},
|
||||
"softener_density": {
|
||||
"options": {
|
||||
"low": {"softener_density": "low"},
|
||||
"medium": {"softener_density": "medium"},
|
||||
"high": {"softener_density": "high"},
|
||||
"extra_high": {"softener_density": "extra_high"}
|
||||
"0": {"temperature": "0"},
|
||||
"20": {"temperature": "20"},
|
||||
"30": {"temperature": "30"},
|
||||
"40": {"temperature": "40"},
|
||||
"50": {"temperature": "50"},
|
||||
"60": {"temperature": "60"},
|
||||
"70": {"temperature": "70"},
|
||||
"90": {"temperature": "90"},
|
||||
"95": {"temperature": "95"}
|
||||
}
|
||||
},
|
||||
"detergent": {
|
||||
"options": {
|
||||
"none": {"detergent": "none"},
|
||||
"little": {"detergent": "little"},
|
||||
"normal": {"detergent": "normal"},
|
||||
"more": {"detergent": "more"}
|
||||
"0": {"detergent": "0"},
|
||||
"1": {"detergent": "1"},
|
||||
"2": {"detergent": "2"},
|
||||
"3": {"detergent": "3"},
|
||||
"4": {"detergent": "4"},
|
||||
"5": {"detergent": "5"}
|
||||
}
|
||||
},
|
||||
"softener": {
|
||||
"options": {
|
||||
"none": {"softener": "none"},
|
||||
"little": {"softener": "little"},
|
||||
"normal": {"softener": "normal"},
|
||||
"more": {"softener": "more"}
|
||||
}
|
||||
},
|
||||
"season": {
|
||||
"options": {
|
||||
"spring": {"season": "spring"},
|
||||
"summer": {"season": "summer"},
|
||||
"autumn": {"season": "autumn"},
|
||||
"winter": {"season": "winter"}
|
||||
}
|
||||
},
|
||||
"disinfectant": {
|
||||
"options": {
|
||||
"none": {"disinfectant": "none"},
|
||||
"light": {"disinfectant": "light"},
|
||||
"medium": {"disinfectant": "medium"},
|
||||
"strong": {"disinfectant": "strong"}
|
||||
"0": {"softener": "0"},
|
||||
"1": {"softener": "1"},
|
||||
"2": {"softener": "2"},
|
||||
"3": {"softener": "3"},
|
||||
"4": {"softener": "4"},
|
||||
"5": {"softener": "5"}
|
||||
}
|
||||
},
|
||||
"dirty_degree": {
|
||||
"options": {
|
||||
"light": {"dirty_degree": "light"},
|
||||
"medium": {"dirty_degree": "medium"},
|
||||
"heavy": {"dirty_degree": "heavy"},
|
||||
"extra_heavy": {"dirty_degree": "extra_heavy"}
|
||||
}
|
||||
},
|
||||
"stains": {
|
||||
"options": {
|
||||
"none": {"stains": "none"},
|
||||
"light": {"stains": "light"},
|
||||
"medium": {"stains": "medium"},
|
||||
"heavy": {"stains": "heavy"}
|
||||
}
|
||||
},
|
||||
"add_rinse": {
|
||||
"options": {
|
||||
"none": {"add_rinse": "none"},
|
||||
"one": {"add_rinse": "one"},
|
||||
"two": {"add_rinse": "two"},
|
||||
"three": {"add_rinse": "three"}
|
||||
"0": {"dirty_degree": "0"},
|
||||
"1": {"dirty_degree": "1"},
|
||||
"2": {"dirty_degree": "2"},
|
||||
"3": {"dirty_degree": "3"},
|
||||
"4": {"dirty_degree": "4"}
|
||||
}
|
||||
},
|
||||
"soak_count": {
|
||||
"options": {
|
||||
"none": {"soak_count": "none"},
|
||||
"one": {"soak_count": "one"},
|
||||
"two": {"soak_count": "two"},
|
||||
"three": {"soak_count": "three"}
|
||||
"0": {"soak_count": "0"},
|
||||
"1": {"soak_count": "1"},
|
||||
"2": {"soak_count": "2"},
|
||||
"3": {"soak_count": "3"},
|
||||
"4": {"soak_count": "4"},
|
||||
"5": {"soak_count": "5"}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -272,12 +343,10 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"dryer": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"remote_control_flag": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"progress": {
|
||||
"device_class": SensorDeviceClass.BATTERY,
|
||||
@@ -285,28 +354,22 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"cloud_cycle_low": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"cloud_cycle_high": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"cloud_cycle_jiepai1": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"cloud_cycle_jiepai2": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"cloud_cycle_jiepai3": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"cloud_cycle_jiepai4": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"cloud_cycle_jiepai_time1": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
@@ -329,24 +392,19 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"customize_machine_cycle": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"detergent_global": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"softener_global": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"detergent_density_global": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"softener_density_global": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"fresh_air_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
@@ -359,24 +417,19 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"device_software_version": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"expert_step": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"flocks_wash_count": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"active_oxygen": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"dehydration_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
|
||||
@@ -131,16 +131,10 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"progress": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"version": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"dry_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
|
||||
@@ -65,6 +65,31 @@ DEVICE_MAPPING = {
|
||||
"power_on": {"work_status": "power_on" },
|
||||
}
|
||||
},
|
||||
"wash_mode": {
|
||||
"options": {
|
||||
"neutral_gear": {"mode": "neutral_gear"},
|
||||
"auto_wash": {"mode": "auto_wash"},
|
||||
"strong_wash": {"mode": "strong_wash"},
|
||||
"standard_wash": {"mode": "standard_wash"},
|
||||
"eco_wash": {"mode": "eco_wash"},
|
||||
"glass_wash": {"mode": "glass_wash"},
|
||||
"hour_wash": {"mode": "hour_wash"},
|
||||
"fast_wash": {"mode": "fast_wash"},
|
||||
"soak_wash": {"mode": "soak_wash"},
|
||||
"90min_wash": {"mode": "90min_wash"},
|
||||
"self_clean": {"mode": "self_clean"},
|
||||
"fruit_wash": {"mode": "fruit_wash"},
|
||||
"self_define": {"mode": "self_define"},
|
||||
"germ": {"mode": "germ"},
|
||||
"bowl_wash": {"mode": "bowl_wash"},
|
||||
"kill_germ": {"mode": "kill_germ"},
|
||||
"seafood_wash": {"mode": "seafood_wash"},
|
||||
"hotpot_wash": {"mode": "hotpot_wash"},
|
||||
"quietnight_wash": {"mode": "quietnight_wash"},
|
||||
"less_wash": {"mode": "less_wash"},
|
||||
"oilnet_wash": {"mode": "oilnet_wash"}
|
||||
}
|
||||
},
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"bright": {
|
||||
|
||||
236
custom_components/midea_auto_cloud/device_mapping/T0xE2.py
Normal file
236
custom_components/midea_auto_cloud/device_mapping/T0xE2.py
Normal file
@@ -0,0 +1,236 @@
|
||||
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, PERCENTAGE, PRECISION_HALVES
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"manufacturer": "美的",
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}],
|
||||
"centralized": [],
|
||||
"entities": {
|
||||
Platform.WATER_HEATER: {
|
||||
"water_heater": {
|
||||
"power": "power",
|
||||
"operation_list": {
|
||||
"off": {"power": "off"},
|
||||
"on": {"power": "on"},
|
||||
},
|
||||
"target_temperature": "temperature",
|
||||
"current_temperature": "cur_temperature",
|
||||
"min_temp": 30,
|
||||
"max_temp": 75,
|
||||
"temperature_unit": UnitOfTemperature.CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"ti_protect": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"auto_off": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"protect": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"sleep": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"memory": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"safe": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"water_flow": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"water_quality": {
|
||||
"options": {
|
||||
"0": {"water_quality": 0},
|
||||
"1": {"water_quality": 1},
|
||||
"2": {"water_quality": 2},
|
||||
"3": {"water_quality": 3}
|
||||
}
|
||||
},
|
||||
"func_select": {
|
||||
"options": {
|
||||
"low": {"func_select": "low"},
|
||||
"medium": {"func_select": "medium"}
|
||||
}
|
||||
},
|
||||
"type_select": {
|
||||
"options": {
|
||||
"normal": {"type_select": "normal"},
|
||||
"valve": {"type_select": "valve"},
|
||||
}
|
||||
},
|
||||
"machine": {
|
||||
"options": {
|
||||
"real_machine": {"machine": "real_machine"},
|
||||
"virtual_machine": {"machine": "virtual_machine"}
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"cur_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"top_temp": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"bottom_temp": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"in_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"passwater_lowbyte": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "L",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"passwater_highbyte": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "L",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"rate": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "L/min",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"cur_rate": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "L/min",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"sterilize_left_days": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.DAYS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"uv_sterilize_minute": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"uv_sterilize_second": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.SECONDS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"screen_light": {
|
||||
"device_class": SensorDeviceClass.ILLUMINANCE,
|
||||
"unit_of_measurement": "lx",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"morning_night_bash": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"tds_value": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "ppm",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"heat_water_level": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "%",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"flow": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "L/min",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"end_time_hour": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.HOURS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"end_time_minute": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"wash_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"sterilize_cycle_index": {
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"discharge_status": {
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"water_system": {
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"discharge_left_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"mg_remain": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "mg",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"waterday_lowbyte": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "L",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"waterday_highbyte": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"unit_of_measurement": "L",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
}
|
||||
},
|
||||
Platform.BINARY_SENSOR: {
|
||||
"door_status": {
|
||||
"device_class": BinarySensorDeviceClass.DOOR,
|
||||
},
|
||||
"limit_error": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
},
|
||||
"sensor_error": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
},
|
||||
"communication_error": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
},
|
||||
"ele_exception": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
},
|
||||
"elec_warning": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
182
custom_components/midea_auto_cloud/device_mapping/T0xE3.py
Normal file
182
custom_components/midea_auto_cloud/device_mapping/T0xE3.py
Normal file
@@ -0,0 +1,182 @@
|
||||
from homeassistant.const import Platform, UnitOfTemperature, UnitOfVolume, UnitOfTime, PERCENTAGE, PRECISION_HALVES
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}],
|
||||
"centralized": [
|
||||
"power", "bubble", "cold_water", "bathtub", "safe", "cold_water_dot",
|
||||
"change_litre_switch", "cold_water_ai", "cold_water_pressure",
|
||||
"person_mode_one", "person_mode_two", "person_mode_three", "gesture_function",
|
||||
"mode", "power_level", "type_machine", "capacity", "temperature"
|
||||
],
|
||||
"entities": {
|
||||
Platform.SWITCH: {
|
||||
"bubble": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"cold_water": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"bathtub": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"safe": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"cold_water_dot": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"change_litre_switch": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"cold_water_ai": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"cold_water_pressure": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"person_mode_one": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"person_mode_two": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"person_mode_three": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"gesture_function": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
}
|
||||
},
|
||||
Platform.CLIMATE: {
|
||||
"water_heater": {
|
||||
"power": "power",
|
||||
"hvac_modes": {
|
||||
"off": {"power": "off"},
|
||||
"heat": {"power": "on", "mode": "shower"}
|
||||
},
|
||||
"target_temperature": "temperature",
|
||||
"current_temperature": "out_water_tem",
|
||||
"min_temp": 20,
|
||||
"max_temp": 60,
|
||||
"temperature_unit": UnitOfTemperature.CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"mode": {
|
||||
"options": {
|
||||
"shower": {"mode": "shower"},
|
||||
"bath": {"mode": "bath"},
|
||||
"mixed": {"mode": "mixed"},
|
||||
"eco": {"mode": "eco"}
|
||||
}
|
||||
},
|
||||
"power_level": {
|
||||
"options": {
|
||||
"low": {"power_level": "0"},
|
||||
"medium": {"power_level": "1"},
|
||||
"high": {"power_level": "2"},
|
||||
"max": {"power_level": "3"}
|
||||
}
|
||||
},
|
||||
"type_machine": {
|
||||
"options": {
|
||||
"standard": {"type_machine": "20"},
|
||||
"premium": {"type_machine": "21"},
|
||||
"deluxe": {"type_machine": "22"}
|
||||
}
|
||||
},
|
||||
"capacity": {
|
||||
"options": {
|
||||
"small": {"capacity": "1"},
|
||||
"medium": {"capacity": "2"},
|
||||
"large": {"capacity": "3"}
|
||||
}
|
||||
},
|
||||
"gesture_function_type": {
|
||||
"options": {
|
||||
"none": {"gesture_function_type": "0"},
|
||||
"wave": {"gesture_function_type": "1"},
|
||||
"touch": {"gesture_function_type": "2"},
|
||||
"proximity": {"gesture_function_type": "3"}
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"out_water_tem": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"water_volume": {
|
||||
"device_class": SensorDeviceClass.VOLUME,
|
||||
"unit_of_measurement": UnitOfVolume.LITERS,
|
||||
"state_class": SensorStateClass.TOTAL_INCREASING
|
||||
},
|
||||
"zero_cold_tem": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"bath_out_volume": {
|
||||
"device_class": SensorDeviceClass.VOLUME,
|
||||
"unit_of_measurement": UnitOfVolume.LITERS,
|
||||
"state_class": SensorStateClass.TOTAL_INCREASING
|
||||
},
|
||||
"return_water_tem": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"change_litre": {
|
||||
"device_class": SensorDeviceClass.VOLUME,
|
||||
"unit_of_measurement": UnitOfVolume.LITERS,
|
||||
"state_class": SensorStateClass.TOTAL_INCREASING
|
||||
},
|
||||
"bathtub_water_level": {
|
||||
"device_class": SensorDeviceClass.VOLUME,
|
||||
"unit_of_measurement": UnitOfVolume.LITERS,
|
||||
"state_class": SensorStateClass.TOTAL_INCREASING
|
||||
},
|
||||
"temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"gas_lift_percent": {
|
||||
"device_class": SensorDeviceClass.POWER_FACTOR,
|
||||
"unit_of_measurement": PERCENTAGE,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"person_tem_one": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"person_tem_two": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"person_tem_three": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"in_water_tem": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": [0, 1],
|
||||
"queries": [{}],
|
||||
"calculate": {
|
||||
"get": [
|
||||
{
|
||||
@@ -96,6 +97,7 @@ DEVICE_MAPPING = {
|
||||
},
|
||||
"61001527": {
|
||||
"rationale": [0, 1],
|
||||
"queries": [{}],
|
||||
"calculate": {
|
||||
"get": [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_HALVES, UnitOfTime, UnitOfElectricPotential, UnitOfVolume, UnitOfMass
|
||||
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_HALVES, UnitOfTime, UnitOfElectricPotential, \
|
||||
UnitOfVolume, UnitOfMass
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
@@ -10,61 +11,44 @@ DEVICE_MAPPING = {
|
||||
"centralized": [],
|
||||
"entities": {
|
||||
Platform.SWITCH: {
|
||||
"holiday_mode": {
|
||||
"power": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"water_way": {
|
||||
"heat_start": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [0, 1],
|
||||
},
|
||||
"lock": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"soften": {
|
||||
"sleep": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"leak_water_protection": {
|
||||
"keep_warm": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"cl_sterilization": {
|
||||
"vacation": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"micro_leak": {
|
||||
"germicidal": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"low_salt": {
|
||||
"lack_water": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"no_salt": {
|
||||
"drainage": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"low_battery": {
|
||||
"wash_enable": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"salt_level_sensor_error": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"flowmeter_error": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"leak_water": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"micro_leak_protection": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"maintenance_reminder_switch": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"rsj_stand_by": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"regeneration": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"pre_regeneration": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
}
|
||||
},
|
||||
Platform.BINARY_SENSOR: {
|
||||
"maintenance_remind": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
"heat_status": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"standby_status": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"chlorine_sterilization_error": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
@@ -74,161 +58,31 @@ DEVICE_MAPPING = {
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"micro_leak_protection_value": {
|
||||
"device_class": SensorDeviceClass.PRESSURE,
|
||||
"current_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"regeneration_current_stages": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"cool_target_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"water_hardness": {
|
||||
"device_class": SensorDeviceClass.WATER,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"water_consumption_ml": {
|
||||
"device_class": SensorDeviceClass.VOLUME,
|
||||
"unit_of_measurement": UnitOfVolume.LITERS,
|
||||
"state_class": SensorStateClass.TOTAL_INCREASING
|
||||
},
|
||||
"timing_regeneration_hour": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.HOURS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"real_time_setting_hour": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.HOURS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"timing_regeneration_min": {
|
||||
"keep_warm_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"regeneration_left_seconds": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.SECONDS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"maintenance_reminder_setting": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"mixed_water_gear": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"use_days": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.DAYS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"days_since_last_regeneration": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.DAYS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"velocity": {
|
||||
"device_class": SensorDeviceClass.SPEED,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"supply_voltage": {
|
||||
"device_class": SensorDeviceClass.VOLTAGE,
|
||||
"unit_of_measurement": UnitOfElectricPotential.VOLT,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"left_salt": {
|
||||
"device_class": SensorDeviceClass.WEIGHT,
|
||||
"unit_of_measurement": UnitOfMass.KILOGRAMS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"pre_regeneration_days": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.DAYS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"flushing_days": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.DAYS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"salt_setting": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"regeneration_count": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"battery_voltage": {
|
||||
"device_class": SensorDeviceClass.VOLTAGE,
|
||||
"unit_of_measurement": UnitOfElectricPotential.VOLT,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"error": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"days_since_last_two_regeneration": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.DAYS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"remind_maintenance_days": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.DAYS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"real_date_setting_year": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"real_date_setting_month": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"real_date_setting_day": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"category": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"real_time_setting_min": {
|
||||
"warm_left_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"regeneration_stages": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"soft_available_big": {
|
||||
"device_class": SensorDeviceClass.VOLUME,
|
||||
"unit_of_measurement": UnitOfVolume.LITERS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"water_consumption_big": {
|
||||
"device_class": SensorDeviceClass.VOLUME,
|
||||
"unit_of_measurement": UnitOfVolume.LITERS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"water_consumption_today": {
|
||||
"device_class": SensorDeviceClass.VOLUME,
|
||||
"unit_of_measurement": UnitOfVolume.LITERS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"water_consumption_average": {
|
||||
"device_class": SensorDeviceClass.VOLUME,
|
||||
"unit_of_measurement": UnitOfVolume.LITERS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"salt_alarm_threshold": {
|
||||
"device_class": SensorDeviceClass.WEIGHT,
|
||||
"unit_of_measurement": UnitOfMass.KILOGRAMS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"leak_water_protection_value": {
|
||||
"device_class": SensorDeviceClass.PRESSURE,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
201
custom_components/midea_auto_cloud/device_mapping/T0xFA.py
Normal file
201
custom_components/midea_auto_cloud/device_mapping/T0xFA.py
Normal file
@@ -0,0 +1,201 @@
|
||||
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, PERCENTAGE
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}],
|
||||
"centralized": [
|
||||
"power", "humidify", "swing", "anion", "display_on_off",
|
||||
"dust_reset", "temp_wind_switch", "filter_reset"
|
||||
],
|
||||
"entities": {
|
||||
Platform.BINARY_SENSOR: {
|
||||
"power": {
|
||||
"device_class": BinarySensorDeviceClass.POWER,
|
||||
},
|
||||
"humidify": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"swing": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"anion": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"display_on_off": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"dust_reset": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"temp_wind_switch": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"filter_reset": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"voice": {
|
||||
"options": {
|
||||
"open_buzzer": {"voice": "open_buzzer"},
|
||||
"close_buzzer": {"voice": "close_buzzer"},
|
||||
"mute": {"voice": "mute"}
|
||||
}
|
||||
},
|
||||
"swing_angle": {
|
||||
"options": {
|
||||
"unknown": {"swing_angle": "unknown"},
|
||||
"30": {"swing_angle": "30"},
|
||||
"60": {"swing_angle": "60"},
|
||||
"90": {"swing_angle": "90"},
|
||||
"120": {"swing_angle": "120"},
|
||||
"150": {"swing_angle": "150"},
|
||||
"180": {"swing_angle": "180"}
|
||||
}
|
||||
},
|
||||
"swing_direction": {
|
||||
"options": {
|
||||
"unknown": {"swing_direction": "unknown"},
|
||||
"horizontal": {"swing_direction": "horizontal"},
|
||||
"vertical": {"swing_direction": "vertical"},
|
||||
"both": {"swing_direction": "both"}
|
||||
}
|
||||
},
|
||||
"scene": {
|
||||
"options": {
|
||||
"none": {"scene": "none"},
|
||||
"auto": {"scene": "auto"},
|
||||
"sleep": {"scene": "sleep"},
|
||||
"work": {"scene": "work"},
|
||||
"study": {"scene": "study"},
|
||||
"party": {"scene": "party"}
|
||||
}
|
||||
},
|
||||
"sleep_sensor": {
|
||||
"options": {
|
||||
"none": {"sleep_sensor": "none"},
|
||||
"light": {"sleep_sensor": "light"},
|
||||
"sound": {"sleep_sensor": "sound"},
|
||||
"both": {"sleep_sensor": "both"}
|
||||
}
|
||||
},
|
||||
"mode": {
|
||||
"options": {
|
||||
"normal": {"mode": "normal"},
|
||||
"auto": {"mode": "auto"},
|
||||
"manual": {"mode": "manual"},
|
||||
"sleep": {"mode": "sleep"},
|
||||
"turbo": {"mode": "turbo"},
|
||||
"quiet": {"mode": "quiet"}
|
||||
}
|
||||
},
|
||||
"gear": {
|
||||
"options": {
|
||||
"1": {"gear": "1"},
|
||||
"2": {"gear": "2"},
|
||||
"3": {"gear": "3"},
|
||||
"4": {"gear": "4"},
|
||||
"5": {"gear": "5"},
|
||||
"6": {"gear": "6"},
|
||||
"auto": {"gear": "auto"}
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"real_gear": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"dust_life_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.HOURS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"filter_life_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.HOURS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"battery_status": {
|
||||
"device_class": SensorDeviceClass.BATTERY,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"battery_level": {
|
||||
"device_class": SensorDeviceClass.BATTERY,
|
||||
"unit_of_measurement": PERCENTAGE,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"temperature_feedback": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"water_feedback": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"timer_off_hour": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.HOURS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"timer_off_minute": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"timer_on_hour": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.HOURS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"timer_on_minute": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": UnitOfTime.MINUTES,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"version": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"pm25": {
|
||||
"device_class": SensorDeviceClass.PM25,
|
||||
"unit_of_measurement": "µg/m³",
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"ud_swing_angle": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"lr_diy_down_percent": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"unit_of_measurement": PERCENTAGE,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"lr_diy_up_percent": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"unit_of_measurement": PERCENTAGE,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"ud_diy_down_percent": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"unit_of_measurement": PERCENTAGE,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"ud_diy_up_percent": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"unit_of_measurement": PERCENTAGE,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,8 +114,7 @@ DEVICE_MAPPING = {
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"error_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
"device_class": SensorDeviceClass.ENUM
|
||||
},
|
||||
"cur_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from homeassistant.const import Platform, UnitOfTemperature
|
||||
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_HALVES
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.climate.const import PRECISION_HALVES
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .midea_entity import MideaEntity
|
||||
from . import load_device_config
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
account_bucket = hass.data.get(DOMAIN, {}).get("accounts", {}).get(config_entry.entry_id)
|
||||
if not account_bucket:
|
||||
async_add_entities([])
|
||||
@@ -44,10 +52,6 @@ class MideaFanEntity(MideaEntity, FanEntity):
|
||||
rationale=rationale,
|
||||
config=config,
|
||||
)
|
||||
self._device = device
|
||||
self._manufacturer = manufacturer
|
||||
self._rationale = rationale
|
||||
self._config = config
|
||||
self._key_power = self._config.get("power")
|
||||
self._key_preset_modes = self._config.get("preset_modes")
|
||||
self._key_speeds = self._config.get("speeds")
|
||||
@@ -57,7 +61,9 @@ class MideaFanEntity(MideaEntity, FanEntity):
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = 0
|
||||
features = FanEntityFeature(0)
|
||||
features |= FanEntityFeature.TURN_ON
|
||||
features |= FanEntityFeature.TURN_OFF
|
||||
if self._key_preset_modes is not None and len(self._key_preset_modes) > 0:
|
||||
features |= FanEntityFeature.PRESET_MODE
|
||||
if self._key_speeds is not None and len(self._key_speeds) > 0:
|
||||
@@ -104,8 +110,7 @@ class MideaFanEntity(MideaEntity, FanEntity):
|
||||
index = round(percentage * self._attr_speed_count / 100) - 1
|
||||
index = max(0, min(index, len(self._key_speeds) - 1))
|
||||
new_status.update(self._key_speeds[index])
|
||||
if self._key_power is not None:
|
||||
new_status[self._key_power] = True
|
||||
await self._async_set_status_on_off(self._key_power, True)
|
||||
if new_status:
|
||||
await self.async_set_attributes(new_status)
|
||||
|
||||
@@ -131,8 +136,3 @@ class MideaFanEntity(MideaEntity, FanEntity):
|
||||
if self.oscillating != oscillating:
|
||||
await self._async_set_status_on_off(self._key_oscillate, oscillating)
|
||||
|
||||
def update_state(self, status):
|
||||
try:
|
||||
self.schedule_update_ha_state()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -61,11 +61,6 @@ class MideaHumidifierEntity(MideaEntity, HumidifierEntity):
|
||||
rationale=rationale,
|
||||
config=config,
|
||||
)
|
||||
self._device = device
|
||||
self._manufacturer = manufacturer
|
||||
self._rationale = rationale
|
||||
self._entity_key = entity_key
|
||||
self._config = config
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"documentation": "https://github.com/sususweet/midea-meiju-codec#readme",
|
||||
"iot_class": "cloud_push",
|
||||
"issue_tracker": "https://github.com/sususweet/midea-meiju-codec/issues",
|
||||
"requirements": [],
|
||||
"version": "v0.0.4"
|
||||
"requirements": ["lupa>=2.0"],
|
||||
"version": "v0.1.3"
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from enum import IntEnum
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
@@ -16,6 +17,10 @@ from .data_coordinator import MideaDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
class Rationale(IntEnum):
|
||||
EQUALLY = 0
|
||||
GREATER = 1
|
||||
LESS = 2
|
||||
|
||||
class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
||||
"""Base class for Midea entities."""
|
||||
@@ -61,6 +66,7 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
||||
self._attr_unique_id = f"{DOMAIN}.{self._device_id}_{self._entity_key}"
|
||||
self.entity_id_base = f"midea_{self._device_id}"
|
||||
manu = "Midea" if manufacturer is None else manufacturer
|
||||
self.manufacturer = manu
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, str(self._device_id))},
|
||||
model=self._model,
|
||||
@@ -124,8 +130,10 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
MideaLogger.debug(f"available available={self.coordinator.data} ")
|
||||
return self.coordinator.data.available
|
||||
if self.coordinator.data:
|
||||
return self.coordinator.data.available
|
||||
else:
|
||||
return False
|
||||
|
||||
async def _publish_command(self) -> None:
|
||||
"""Publish commands to the device."""
|
||||
@@ -133,83 +141,122 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
||||
pass
|
||||
|
||||
# ===== Unified helpers migrated from legacy entity base =====
|
||||
def _get_nested_value(self, attribute_key: str | None) -> Any:
|
||||
"""Get nested value from device attributes using dot notation.
|
||||
|
||||
Supports both flat and nested attribute access.
|
||||
Examples: 'power', 'eco.status', 'temperature.room'
|
||||
"""
|
||||
if attribute_key is None:
|
||||
return None
|
||||
|
||||
# Handle nested attributes with dot notation
|
||||
if '.' in attribute_key:
|
||||
keys = attribute_key.split('.')
|
||||
value = self.device_attributes
|
||||
try:
|
||||
for key in keys:
|
||||
if isinstance(value, dict):
|
||||
value = value.get(key)
|
||||
else:
|
||||
return None
|
||||
return value
|
||||
except (KeyError, TypeError):
|
||||
return None
|
||||
else:
|
||||
# Handle flat attributes
|
||||
return self.device_attributes.get(attribute_key)
|
||||
|
||||
def _get_status_on_off(self, attribute_key: str | None) -> bool:
|
||||
"""Return boolean value from device attributes for given key.
|
||||
|
||||
Accepts common truthy representations: True/1/"on"/"true".
|
||||
Supports nested attributes with dot notation.
|
||||
"""
|
||||
result = False
|
||||
if attribute_key is None:
|
||||
return result
|
||||
status = self._get_nested_value(attribute_key)
|
||||
if status is not None:
|
||||
try:
|
||||
result = bool(self._rationale.index(status))
|
||||
except ValueError:
|
||||
MideaLogger.error(f"The value of attribute {attribute_key} ('{status}') "
|
||||
f"is not in rationale {self._rationale}")
|
||||
return result
|
||||
|
||||
def _set_nested_value(self, attribute_key: str, value: Any) -> None:
|
||||
"""Set nested value in device attributes using dot notation.
|
||||
|
||||
Supports both flat and nested attribute setting.
|
||||
Examples: 'power', 'eco.status', 'temperature.room'
|
||||
"""
|
||||
if attribute_key is None:
|
||||
return False
|
||||
value = self.device_attributes.get(attribute_key)
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return value in (1, "1", "on", "ON", "true", "TRUE")
|
||||
return
|
||||
|
||||
# Handle nested attributes with dot notation
|
||||
if '.' in attribute_key:
|
||||
keys = attribute_key.split('.')
|
||||
current_dict = self.device_attributes
|
||||
|
||||
# Navigate to the parent dictionary
|
||||
for key in keys[:-1]:
|
||||
if key not in current_dict:
|
||||
current_dict[key] = {}
|
||||
current_dict = current_dict[key]
|
||||
|
||||
# Set the final value
|
||||
current_dict[keys[-1]] = value
|
||||
else:
|
||||
# Handle flat attributes
|
||||
self.device_attributes[attribute_key] = value
|
||||
|
||||
async def _async_set_status_on_off(self, attribute_key: str | None, turn_on: bool) -> None:
|
||||
"""Set boolean attribute via coordinator, no-op if key is None."""
|
||||
if attribute_key is None:
|
||||
return
|
||||
await self.async_set_attribute(attribute_key, bool(turn_on))
|
||||
await self.async_set_attribute(attribute_key, self._rationale[int(turn_on)])
|
||||
|
||||
def _list_get_selected(self, options: list[dict] | None, rationale: object = None) -> int | None:
|
||||
"""Select index from a list of dict conditions matched against attributes.
|
||||
|
||||
The optional rationale supports equality/greater/less matching. It can be
|
||||
a string name ("EQUALLY"/"GREATER"/"LESS") or an Enum with .name.
|
||||
"""
|
||||
if not options:
|
||||
return None
|
||||
|
||||
rationale_name = getattr(rationale, "name", None) or rationale or "EQUALLY"
|
||||
for index in range(0, len(options)):
|
||||
def _list_get_selected(self, key_of_list: list, rationale: Rationale = Rationale.EQUALLY):
|
||||
for index in range(0, len(key_of_list)):
|
||||
match = True
|
||||
for attr, expected in options[index].items():
|
||||
current = self.device_attributes.get(attr)
|
||||
if current is None:
|
||||
for attr, value in key_of_list[index].items():
|
||||
state_value = self._get_nested_value(attr)
|
||||
if state_value is None:
|
||||
match = False
|
||||
break
|
||||
if rationale_name == "EQUALLY" and current != expected:
|
||||
if rationale is Rationale.EQUALLY and state_value != value:
|
||||
match = False
|
||||
break
|
||||
if rationale_name == "GREATER" and current < expected:
|
||||
if rationale is Rationale.GREATER and state_value < value:
|
||||
match = False
|
||||
break
|
||||
if rationale_name == "LESS" and current > expected:
|
||||
if rationale is Rationale.LESS and state_value > value:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
return index
|
||||
return None
|
||||
|
||||
def _dict_get_selected(self, mapping: dict | None, rationale: object = None):
|
||||
"""Return key from a dict whose value (a condition dict) matches attributes.
|
||||
|
||||
The optional rationale supports equality/greater/less matching. It can be
|
||||
a string name ("EQUALLY"/"GREATER"/"LESS") or an Enum with .name.
|
||||
"""
|
||||
if not mapping:
|
||||
return None
|
||||
rationale_name = getattr(rationale, "name", None) or rationale or "EQUALLY"
|
||||
for key, conditions in mapping.items():
|
||||
if not isinstance(conditions, dict):
|
||||
continue
|
||||
def _dict_get_selected(self, key_of_dict: dict, rationale: Rationale = Rationale.EQUALLY):
|
||||
for mode, status in key_of_dict.items():
|
||||
match = True
|
||||
for attr, expected in conditions.items():
|
||||
current = self.device_attributes.get(attr)
|
||||
if current is None:
|
||||
for attr, value in status.items():
|
||||
state_value = self._get_nested_value(attr)
|
||||
if state_value is None:
|
||||
match = False
|
||||
break
|
||||
if rationale_name == "EQUALLY" and current != expected:
|
||||
if rationale is Rationale.EQUALLY and state_value != value:
|
||||
match = False
|
||||
break
|
||||
if rationale_name == "GREATER" and current <= expected:
|
||||
if rationale is Rationale.GREATER and state_value < value:
|
||||
match = False
|
||||
break
|
||||
if rationale_name == "LESS" and current >= expected:
|
||||
if rationale is Rationale.LESS and state_value > value:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
return key
|
||||
return mode
|
||||
return None
|
||||
|
||||
async def publish_command_from_current_state(self) -> None:
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .midea_entity import MideaEntity
|
||||
from . import load_device_config
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
account_bucket = hass.data.get(DOMAIN, {}).get("accounts", {}).get(config_entry.entry_id)
|
||||
if not account_bucket:
|
||||
async_add_entities([])
|
||||
@@ -44,10 +52,6 @@ class MideaSelectEntity(MideaEntity, SelectEntity):
|
||||
rationale=rationale,
|
||||
config=config,
|
||||
)
|
||||
self._device = device
|
||||
self._manufacturer = manufacturer
|
||||
self._rationale = rationale
|
||||
self._config = config
|
||||
self._key_options = self._config.get("options")
|
||||
|
||||
@property
|
||||
@@ -56,7 +60,14 @@ class MideaSelectEntity(MideaEntity, SelectEntity):
|
||||
|
||||
@property
|
||||
def current_option(self):
|
||||
return self._dict_get_selected(self._key_options)
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
if attribute and attribute != self._entity_key:
|
||||
# For simple attribute access, get the value directly
|
||||
return self._get_nested_value(attribute)
|
||||
else:
|
||||
# For complex mapping, use the existing logic
|
||||
return self._dict_get_selected(self._key_options)
|
||||
|
||||
async def async_select_option(self, option: str):
|
||||
new_status = self._key_options.get(option)
|
||||
|
||||
@@ -5,7 +5,6 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .core.logger import MideaLogger
|
||||
from .midea_entity import MideaEntity
|
||||
from . import load_device_config
|
||||
|
||||
@@ -58,12 +57,32 @@ class MideaSensorEntity(MideaEntity, SensorEntity):
|
||||
rationale=rationale,
|
||||
config=config,
|
||||
)
|
||||
self._device = device
|
||||
self._manufacturer = manufacturer
|
||||
self._rationale = rationale
|
||||
self._config = config
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the native value of the sensor."""
|
||||
return self.device_attributes.get(self._entity_key)
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
value = self._get_nested_value(attribute)
|
||||
|
||||
# Handle invalid string values
|
||||
if isinstance(value, str) and value.lower() in ['invalid', 'none', 'null', '']:
|
||||
return None
|
||||
|
||||
# Try to convert to number if it's a string that looks like a number
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
# Try integer first
|
||||
if '.' not in value:
|
||||
return int(value)
|
||||
# Then float
|
||||
return float(value)
|
||||
except (ValueError, TypeError):
|
||||
# If conversion fails, return None for numeric sensors
|
||||
# or return the original string for enum sensors
|
||||
device_class = self._config.get("device_class")
|
||||
if device_class and "enum" not in device_class.lower():
|
||||
return None
|
||||
return value
|
||||
|
||||
return value
|
||||
|
||||
@@ -5,7 +5,6 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .core.logger import MideaLogger
|
||||
from .midea_entity import MideaEntity
|
||||
from . import load_device_config
|
||||
|
||||
@@ -58,24 +57,22 @@ class MideaSwitchEntity(MideaEntity, SwitchEntity):
|
||||
rationale=rationale,
|
||||
config=config,
|
||||
)
|
||||
self._device = device
|
||||
self._manufacturer = manufacturer
|
||||
self._rationale = rationale
|
||||
self._config = config
|
||||
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return if the switch is on."""
|
||||
value = self.device_attributes.get(self._entity_key)
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return value == 1 or value == "on" or value == "true"
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
return self._get_status_on_off(attribute)
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn the switch on."""
|
||||
await self.async_set_attribute(self._entity_key, True)
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
await self._async_set_status_on_off(attribute, True)
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn the switch off."""
|
||||
await self.async_set_attribute(self._entity_key, False)
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
await self._async_set_status_on_off(attribute, False)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,23 @@
|
||||
from homeassistant.components.water_heater import WaterHeaterEntity, WaterHeaterEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
Platform,
|
||||
ATTR_TEMPERATURE
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .midea_entity import MideaEntity
|
||||
from . import load_device_config
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up water heater entities for Midea devices."""
|
||||
account_bucket = hass.data.get(DOMAIN, {}).get("accounts", {}).get(config_entry.entry_id)
|
||||
if not account_bucket:
|
||||
async_add_entities([])
|
||||
@@ -47,34 +56,6 @@ class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
||||
rationale=rationale,
|
||||
config=config,
|
||||
)
|
||||
self._device = device
|
||||
self._manufacturer = manufacturer
|
||||
self._rationale = rationale
|
||||
self._config = config
|
||||
# Legacy compatibility: register update and restore display attributes
|
||||
if self._device:
|
||||
self._device.register_update(self.update_state)
|
||||
if (rationale_local := self._config.get("rationale")) is not None:
|
||||
self._rationale = rationale_local
|
||||
if self._rationale is None:
|
||||
self._rationale = ["off", "on"]
|
||||
self._attr_native_unit_of_measurement = self._config.get("unit_of_measurement")
|
||||
self._attr_device_class = self._config.get("device_class")
|
||||
self._attr_state_class = self._config.get("state_class")
|
||||
self._attr_icon = self._config.get("icon")
|
||||
from .const import DOMAIN as _DOMAIN
|
||||
self._attr_unique_id = f"{_DOMAIN}.{self._device.device_id}_{self._entity_key}"
|
||||
self._attr_device_info = {
|
||||
"manufacturer": "Midea" if self._manufacturer is None else self._manufacturer,
|
||||
"model": f"{self._device.model}",
|
||||
"identifiers": {( _DOMAIN, self._device.device_id)},
|
||||
"name": self._device.device_name
|
||||
}
|
||||
name = self._config.get("name")
|
||||
if name is None:
|
||||
name = self._entity_key.replace("_", " ").title()
|
||||
self._attr_name = f"{self._device.device_name} {name}"
|
||||
self.entity_id = self._attr_unique_id
|
||||
self._key_power = self._config.get("power")
|
||||
self._key_operation_list = self._config.get("operation_list")
|
||||
self._key_min_temp = self._config.get("min_temp")
|
||||
@@ -86,7 +67,7 @@ class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = 0
|
||||
features = WaterHeaterEntityFeature(0)
|
||||
if self._key_target_temperature is not None:
|
||||
features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
if self._key_operation_list is not None:
|
||||
@@ -167,9 +148,3 @@ class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
||||
if new_status:
|
||||
await self.async_set_attributes(new_status)
|
||||
|
||||
def update_state(self, status):
|
||||
try:
|
||||
self.schedule_update_ha_state()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user