9 Commits

Author SHA1 Message Date
sususweet
068d9a377d feat: fix lua query and download logic 2025-09-28 21:47:15 +08:00
sususweet
57342ad86c Merge remote-tracking branch 'origin/master' 2025-09-28 21:17:39 +08:00
sususweet
884c436c82 feat: version 0.1.0 2025-09-28 21:17:01 +08:00
sususweet
3c9f1275a0 feat: version 0.1.0 2025-09-28 21:09:18 +08:00
sususweet
4f5e434492 feat: add device support for T0xFA 2025-09-28 21:09:10 +08:00
sususweet
9d22859eec feat: add device support for T0xB2 2025-09-28 21:03:25 +08:00
sususweet
d87156d3e3 feat: add credentials change feature. Fix #6. 2025-09-28 20:51:14 +08:00
sususweet
f3246eb779 feat: add transparent protocol. 2025-09-28 20:24:15 +08:00
sususweet
3507671120 feat: modify to cloud api 2025-09-26 23:15:44 +08:00
20 changed files with 2261 additions and 81 deletions

View File

@@ -8,15 +8,15 @@
- 自动下载设备的协议文件
- 将设备状态更新为设备可见的属性
## 非常初期的预览
## 版本说明
- 仅供技术实现验证以及评估
- 所有设备默认可生成一个名为Status的二进制传感器其属性中列出了设备可访问的所有属性当然有些值不可设置
- Status实体前几项列出了该设备的分类信息供参考
## 目前支持的设备类型
- T0xAC 空调
- T0xB2 电蒸箱
- T0xB3 消毒碗柜
- T0xB8 智能扫地机器人
- T0xCA 对开门冰箱
@@ -26,9 +26,11 @@
- T0xDB 滚筒洗衣机
- T0xDC 干衣机
- T0xE1 洗碗机
- T0xE2 电热水器
- T0xE3 恒温式燃气热水器
- T0xEA 电饭锅
- T0xED 软水机
- T0xFA 电风扇
- T0xFD 加湿器
欢迎合作开发添加更多设备支持。
@@ -54,3 +56,6 @@
示例配置`22012227`演示了如何将设备属性映射成以上各种HomeAssistant中的实体。
## 致谢
感谢[midea-meiju-codec](https://github.com/MattedBroadSky/midea-meiju-codec)项目提供的先验知识。

View File

@@ -41,7 +41,7 @@ from .const import (
CONF_SN8,
CONF_SN,
CONF_MODEL_NUMBER,
CONF_SERVERS
CONF_SERVERS, STORAGE_PATH, CONF_MANUFACTURER_CODE
)
# 账号型:登录云端、获取设备列表,并为每台设备建立协调器(无本地控制)
from .const import CONF_PASSWORD as CONF_PASSWORD_KEY, CONF_SERVER as CONF_SERVER_KEY
@@ -179,11 +179,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 +202,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:
@@ -289,6 +301,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:

View File

@@ -256,7 +256,6 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
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

View File

@@ -71,8 +71,61 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
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

View File

@@ -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()
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"
}
result.update(default_keys)
return result
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(

View File

@@ -1,10 +1,14 @@
import threading
import socket
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 +43,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 +77,8 @@ class MiedaDevice(threading.Thread):
self._centralized = []
self._calculate_get = []
self._calculate_set = []
self._lua_runtime = MideaCodec(lua_file, sn=sn, subtype=subtype) if lua_file is not None else None
self._cloud = cloud
@property
def device_name(self):
@@ -126,14 +134,16 @@ class MiedaDevice(threading.Thread):
def get_attribute(self, attribute):
return self._attributes.get(attribute)
def set_attribute(self, attribute, value):
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
if set_cmd := self._lua_runtime.build_control(new_status):
await self._build_send(set_cmd)
def set_attributes(self, attributes):
async def set_attributes(self, attributes):
new_status = {}
for attr in self._centralized:
new_status[attr] = self._attributes.get(attr)
@@ -142,6 +152,9 @@ class MiedaDevice(threading.Thread):
if attribute in self._attributes.keys():
has_new = True
new_status[attribute] = value
if has_new:
if set_cmd := self._lua_runtime.build_control(new_status):
await self._build_send(set_cmd)
def set_ip_address(self, ip_address):
MideaLogger.debug(f"Update IP address to {ip_address}")
@@ -188,12 +201,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 +211,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

View File

@@ -0,0 +1,91 @@
import lupa
import threading
import json
from .logger import MideaLogger
class LuaRuntime:
def __init__(self, file):
self._runtimes = lupa.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, sn=None, subtype=None):
super().__init__(file)
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):
query_dict = self._build_base_dict()
query_dict["control"] = {} 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_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

View 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

View File

@@ -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,25 +121,14 @@ 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:
# 本地先行更新,随后依赖轮询或设备事件校正
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:
await self.device.set_attributes(attributes)
self.device.attributes.update(attributes)
self.mute_state_update_for_a_while()
self.async_update_listeners()

View File

@@ -6,10 +6,10 @@ from homeassistant.components.switch import SwitchDeviceClass
DEVICE_MAPPING = {
"default": {
"rationale": ["off", "on"],
"queries": [{}, {"query_type": "prevent_straight_wind"}],
"queries": [{}],
"centralized": [
"power", "temperature", "small_temperature", "mode", "eco",
"comfort_power_save", "comfort_sleep", "strong_wind",
"comfort_power_save", "strong_wind",
"wind_swing_lr", "wind_swing_lr", "wind_speed","ptc", "dry"
],
"entities": {
@@ -28,12 +28,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": {
@@ -87,9 +87,9 @@ DEVICE_MAPPING = {
},
"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 +108,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": {

View 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.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"steam_quantity": {
"device_class": SensorDeviceClass.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"totalstep": {
"device_class": SensorDeviceClass.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"stepnum": {
"device_class": SensorDeviceClass.NONE,
"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.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"error_code": {
"device_class": SensorDeviceClass.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"version": {
"device_class": SensorDeviceClass.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"cbs_version": {
"device_class": SensorDeviceClass.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"cloudmenuid": {
"device_class": SensorDeviceClass.NONE,
"state_class": SensorStateClass.MEASUREMENT
}
}
}
}
}

View File

@@ -6,6 +6,7 @@ from homeassistant.components.switch import SwitchDeviceClass
DEVICE_MAPPING = {
"default": {
"rationale": [0, 1],
"queries": [{}],
"calculate": {
"get": [
{

View File

@@ -6,6 +6,7 @@ from homeassistant.components.switch import SwitchDeviceClass
DEVICE_MAPPING = {
"default": {
"rationale": [0, 1],
"queries": [{}],
"calculate": {
"get": [
{

View File

@@ -0,0 +1,465 @@
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": [
"power", "temperature", "mode", "heat", "music", "ti_protect", "fast_wash",
"ali_manager", "water_quality", "rate", "ele_exception", "communication_error",
"cur_rate", "sterilize_left_days", "uv_sterilize_minute", "uv_sterilize_second",
"eplus", "summer", "winter", "efficient", "night", "bath_person", "cloud",
"bath", "half_heat", "whole_heat", "sterilization", "frequency_hot", "scene",
"big_water", "wash", "negative_ions", "screen_off", "t_hot", "baby_wash",
"dad_wash", "mom_wash", "wash_with_temp", "single_wash", "people_wash",
"wash_temperature", "one_egg", "two_egg", "always_fell", "smart_sterilize",
"sterilize_cycle_index", "sound_dad", "screen_light", "morning_night_bash",
"version", "tds_value", "door_status", "limit_error", "sensor_error",
"scene_id", "auto_off", "clean", "volume", "passwater_lowbyte", "cloud_appoint",
"protect", "midea_manager", "sleep", "memory", "shower", "scroll_hot",
"fast_hot_power", "hot_power", "safe", "water_flow", "heat_water_level",
"flow", "appoint_wash", "now_wash", "end_time_hour", "end_time_minute",
"get_time", "get_temp", "func_select", "warm_power", "type_select",
"cur_temperature", "sterilize_high_temp", "discharge_status", "top_temp",
"bottom_heat", "top_heat", "show_h", "uv_sterilize", "machine", "error_code",
"need_discharge", "elec_warning", "bottom_temp", "water_cyclic", "water_system",
"discharge_left_time", "in_temperature", "mg_remain", "waterday_lowbyte",
"waterday_highbyte", "tech_water", "protect_show", "appoint_power"
],
"entities": {
Platform.WATER_HEATER: {
"water_heater": {
"power": "power",
"operation_list": {
"off": {"power": "off"},
"heat": {"power": "on", "mode": "heat"},
"auto": {"power": "on", "mode": "auto"},
"eco": {"power": "on", "mode": "eco"},
"fast": {"power": "on", "mode": "fast"}
},
"target_temperature": "temperature",
"current_temperature": "cur_temperature",
"min_temp": 30,
"max_temp": 75,
"temperature_unit": UnitOfTemperature.CELSIUS,
"precision": PRECISION_HALVES,
}
},
Platform.SWITCH: {
"music": {
"device_class": SwitchDeviceClass.SWITCH,
},
"ti_protect": {
"device_class": SwitchDeviceClass.SWITCH,
},
"fast_wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"ali_manager": {
"device_class": SwitchDeviceClass.SWITCH,
},
"heat": {
"device_class": SwitchDeviceClass.SWITCH,
},
"ele_exception": {
"device_class": SwitchDeviceClass.SWITCH,
},
"communication_error": {
"device_class": SwitchDeviceClass.SWITCH,
},
"eplus": {
"device_class": SwitchDeviceClass.SWITCH,
},
"summer": {
"device_class": SwitchDeviceClass.SWITCH,
},
"winter": {
"device_class": SwitchDeviceClass.SWITCH,
},
"efficient": {
"device_class": SwitchDeviceClass.SWITCH,
},
"night": {
"device_class": SwitchDeviceClass.SWITCH,
},
"bath_person": {
"device_class": SwitchDeviceClass.SWITCH,
},
"cloud": {
"device_class": SwitchDeviceClass.SWITCH,
},
"bath": {
"device_class": SwitchDeviceClass.SWITCH,
},
"half_heat": {
"device_class": SwitchDeviceClass.SWITCH,
},
"whole_heat": {
"device_class": SwitchDeviceClass.SWITCH,
},
"sterilization": {
"device_class": SwitchDeviceClass.SWITCH,
},
"frequency_hot": {
"device_class": SwitchDeviceClass.SWITCH,
},
"scene": {
"device_class": SwitchDeviceClass.SWITCH,
},
"big_water": {
"device_class": SwitchDeviceClass.SWITCH,
},
"wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"negative_ions": {
"device_class": SwitchDeviceClass.SWITCH,
},
"screen_off": {
"device_class": SwitchDeviceClass.SWITCH,
},
"t_hot": {
"device_class": SwitchDeviceClass.SWITCH,
},
"baby_wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"dad_wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"mom_wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"wash_with_temp": {
"device_class": SwitchDeviceClass.SWITCH,
},
"single_wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"people_wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"one_egg": {
"device_class": SwitchDeviceClass.SWITCH,
},
"two_egg": {
"device_class": SwitchDeviceClass.SWITCH,
},
"always_fell": {
"device_class": SwitchDeviceClass.SWITCH,
},
"smart_sterilize": {
"device_class": SwitchDeviceClass.SWITCH,
},
"sound_dad": {
"device_class": SwitchDeviceClass.SWITCH,
},
"door_status": {
"device_class": SwitchDeviceClass.SWITCH,
},
"limit_error": {
"device_class": SwitchDeviceClass.SWITCH,
},
"sensor_error": {
"device_class": SwitchDeviceClass.SWITCH,
},
"auto_off": {
"device_class": SwitchDeviceClass.SWITCH,
},
"clean": {
"device_class": SwitchDeviceClass.SWITCH,
},
"cloud_appoint": {
"device_class": SwitchDeviceClass.SWITCH,
},
"protect": {
"device_class": SwitchDeviceClass.SWITCH,
},
"midea_manager": {
"device_class": SwitchDeviceClass.SWITCH,
},
"sleep": {
"device_class": SwitchDeviceClass.SWITCH,
},
"memory": {
"device_class": SwitchDeviceClass.SWITCH,
},
"shower": {
"device_class": SwitchDeviceClass.SWITCH,
},
"scroll_hot": {
"device_class": SwitchDeviceClass.SWITCH,
},
"fast_hot_power": {
"device_class": SwitchDeviceClass.SWITCH,
},
"hot_power": {
"device_class": SwitchDeviceClass.SWITCH,
},
"safe": {
"device_class": SwitchDeviceClass.SWITCH,
},
"water_flow": {
"device_class": SwitchDeviceClass.SWITCH,
},
"appoint_wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"now_wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"get_time": {
"device_class": SwitchDeviceClass.SWITCH,
},
"get_temp": {
"device_class": SwitchDeviceClass.SWITCH,
},
"warm_power": {
"device_class": SwitchDeviceClass.SWITCH,
},
"sterilize_high_temp": {
"device_class": SwitchDeviceClass.SWITCH,
},
"bottom_heat": {
"device_class": SwitchDeviceClass.SWITCH,
},
"top_heat": {
"device_class": SwitchDeviceClass.SWITCH,
},
"show_h": {
"device_class": SwitchDeviceClass.SWITCH,
},
"uv_sterilize": {
"device_class": SwitchDeviceClass.SWITCH,
},
"need_discharge": {
"device_class": SwitchDeviceClass.SWITCH,
},
"elec_warning": {
"device_class": SwitchDeviceClass.SWITCH,
},
"water_cyclic": {
"device_class": SwitchDeviceClass.SWITCH,
},
"tech_water": {
"device_class": SwitchDeviceClass.SWITCH,
},
"protect_show": {
"device_class": SwitchDeviceClass.SWITCH,
},
"appoint_power": {
"device_class": SwitchDeviceClass.SWITCH,
}
},
Platform.SELECT: {
"mode": {
"options": {
"none": {"mode": "none"},
"heat": {"mode": "heat"},
"auto": {"mode": "auto"},
"eco": {"mode": "eco"},
"fast": {"mode": "fast"}
}
},
"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"},
"high": {"func_select": "high"}
}
},
"type_select": {
"options": {
"normal": {"type_select": "normal"},
"eco": {"type_select": "eco"},
"fast": {"type_select": "fast"}
}
},
"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
},
"version": {
"device_class": SensorDeviceClass.ENUM
},
"tds_value": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "ppm",
"state_class": SensorStateClass.MEASUREMENT
},
"scene_id": {
"device_class": SensorDeviceClass.ENUM
},
"volume": {
"device_class": SensorDeviceClass.SOUND_PRESSURE,
"unit_of_measurement": "%",
"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,
}
}
}
}
}

View File

@@ -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": [
{

View 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.NONE,
"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.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"temperature_feedback": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"water_feedback": {
"device_class": SensorDeviceClass.NONE,
"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.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"pm25": {
"device_class": SensorDeviceClass.PM25,
"unit_of_measurement": "µg/m³",
"state_class": SensorStateClass.MEASUREMENT
},
"ud_swing_angle": {
"device_class": SensorDeviceClass.NONE,
"state_class": SensorStateClass.MEASUREMENT
},
"lr_diy_down_percent": {
"device_class": SensorDeviceClass.NONE,
"unit_of_measurement": PERCENTAGE,
"state_class": SensorStateClass.MEASUREMENT
},
"lr_diy_up_percent": {
"device_class": SensorDeviceClass.NONE,
"unit_of_measurement": PERCENTAGE,
"state_class": SensorStateClass.MEASUREMENT
},
"ud_diy_down_percent": {
"device_class": SensorDeviceClass.NONE,
"unit_of_measurement": PERCENTAGE,
"state_class": SensorStateClass.MEASUREMENT
},
"ud_diy_up_percent": {
"device_class": SensorDeviceClass.NONE,
"unit_of_measurement": PERCENTAGE,
"state_class": SensorStateClass.MEASUREMENT
}
}
}
}
}

View File

@@ -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.5"
"requirements": ["lupa>=2.0"],
"version": "v0.1.0"
}

View File

@@ -49,6 +49,15 @@
},
"title": "Configure"
},
"change_credentials": {
"title": "Change Credentials",
"description": "Change Midea cloud account's username and password",
"data": {
"account": "Username",
"password": "Password",
"server": "Server"
}
},
"reset":{
"title": "Reset the configuration of appliance",
"description": "Remove the old configuration and make a new configuration use template\nIf your configuration was modified, the changes will lost\nIf your appliance type or model not in template, then the new configuration won't be made",
@@ -285,8 +294,38 @@
"work_mode": {
"name": "Work Mode"
},
"func_select": {
"name": "Func Select"
},
"machine": {
"name": "Machine"
},
"type_select": {
"name": "Type Select"
},
"water_quality": {
"name": "Water Quality"
},
"work_status": {
"name": "Work Status"
},
"voice": {
"name": "Voice"
},
"swing_angle": {
"name": "Swing Angle"
},
"swing_direction": {
"name": "Swing Direction"
},
"scene": {
"name": "Scene"
},
"sleep_sensor": {
"name": "Sleep Sensor"
},
"gear": {
"name": "Gear"
}
},
"sensor": {
@@ -856,6 +895,186 @@
},
"zero_cold_tem": {
"name": "Zero Cold Temperature"
},
"bottom_temp": {
"name": "Bottom Temperature"
},
"cur_rate": {
"name": "Current Rate"
},
"discharge_left_time": {
"name": "Discharge Left Time"
},
"discharge_status": {
"name": "Discharge Status"
},
"end_time_hour": {
"name": "End Time Hour"
},
"end_time_minute": {
"name": "End Time Minute"
},
"flow": {
"name": "Flow"
},
"heat_water_level": {
"name": "Heat Water Level"
},
"in_temperature": {
"name": "In Temperature"
},
"mg_remain": {
"name": "Mg Remain"
},
"morning_night_bash": {
"name": "Morning Night Bash"
},
"passwater_highbyte": {
"name": "Passwater Highbyte"
},
"passwater_lowbyte": {
"name": "Passwater Lowbyte"
},
"rate": {
"name": "Rate"
},
"scene_id": {
"name": "Scene ID"
},
"screen_light": {
"name": "Screen Light"
},
"sterilize_cycle_index": {
"name": "Sterilize Cycle Index"
},
"sterilize_left_days": {
"name": "Sterilize Left Days"
},
"tds_value": {
"name": "TDS Value"
},
"top_temp": {
"name": "Top Temperature"
},
"uv_sterilize_minute": {
"name": "UV Sterilize Minute"
},
"uv_sterilize_second": {
"name": "UV Sterilize Second"
},
"volume": {
"name": "Volume"
},
"wash_temperature": {
"name": "Wash Temperature"
},
"water_system": {
"name": "Water System"
},
"waterday_highbyte": {
"name": "Waterday Highbyte"
},
"waterday_lowbyte": {
"name": "Waterday Lowbyte"
},
"work_hour": {
"name": "Work Hour"
},
"work_minute": {
"name": "Work Minute"
},
"work_second": {
"name": "Work Second"
},
"cur_temperature_above": {
"name": "Current Temperature Above"
},
"cur_temperature_underside": {
"name": "Current Temperature Underside"
},
"weight": {
"name": "Weight"
},
"people_number": {
"name": "People Number"
},
"steam_quantity": {
"name": "Steam Quantity"
},
"totalstep": {
"name": "Total Step"
},
"stepnum": {
"name": "Step Number"
},
"hour_set": {
"name": "Hour Set"
},
"minute_set": {
"name": "Minute Set"
},
"second_set": {
"name": "Second Set"
},
"ota": {
"name": "OTA"
},
"cbs_version": {
"name": "CBS Version"
},
"cloudmenuid": {
"name": "Cloud Menu ID"
},
"real_gear": {
"name": "Real Gear"
},
"dust_life_time": {
"name": "Dust Life Time"
},
"filter_life_time": {
"name": "Filter Life Time"
},
"battery_status": {
"name": "Battery Status"
},
"battery_level": {
"name": "Battery Level"
},
"temperature_feedback": {
"name": "Temperature Feedback"
},
"water_feedback": {
"name": "Water Feedback"
},
"timer_off_hour": {
"name": "Timer Off Hour"
},
"timer_off_minute": {
"name": "Timer Off Minute"
},
"timer_on_hour": {
"name": "Timer On Hour"
},
"timer_on_minute": {
"name": "Timer On Minute"
},
"pm25": {
"name": "PM2.5"
},
"ud_swing_angle": {
"name": "UD Swing Angle"
},
"lr_diy_down_percent": {
"name": "LR DIY Down Percent"
},
"lr_diy_up_percent": {
"name": "LR DIY Up Percent"
},
"ud_diy_down_percent": {
"name": "UD DIY Down Percent"
},
"ud_diy_up_percent": {
"name": "UD DIY Up Percent"
}
},
"binary_sensor": {
@@ -927,6 +1146,93 @@
},
"water_lack": {
"name": "Water Lack"
},
"communication_error": {
"name": "Communication Error"
},
"door_status": {
"name": "Door Status"
},
"ele_exception": {
"name": "Ele Exception"
},
"elec_warning": {
"name": "Elec Warning"
},
"limit_error": {
"name": "Limit Error"
},
"sensor_error": {
"name": "Sensor Error"
},
"lock": {
"name": "Lock"
},
"furnace_light": {
"name": "Furnace Light"
},
"dissipate_heat": {
"name": "Dissipate Heat"
},
"pre_heat": {
"name": "Pre Heat"
},
"door_open": {
"name": "Door Open"
},
"lack_water": {
"name": "Lack Water"
},
"high_temperature_work": {
"name": "High Temperature Work"
},
"lack_box": {
"name": "Lack Box"
},
"clean_sink_ponding": {
"name": "Clean Sink Ponding"
},
"clean_scale": {
"name": "Clean Scale"
},
"flip_side": {
"name": "Flip Side"
},
"reaction": {
"name": "Reaction"
},
"ramadan": {
"name": "Ramadan"
},
"change_water": {
"name": "Change Water"
},
"execute": {
"name": "Execute"
},
"power": {
"name": "Power"
},
"humidify": {
"name": "Humidify"
},
"swing": {
"name": "Swing"
},
"anion": {
"name": "Anion"
},
"display_on_off": {
"name": "Display On/Off"
},
"dust_reset": {
"name": "Dust Reset"
},
"temp_wind_switch": {
"name": "Temp Wind Switch"
},
"filter_reset": {
"name": "Filter Reset"
}
},
"climate": {
@@ -956,6 +1262,177 @@
"add_water_flag": {
"name": "Add Water Flag"
},
"ali_manager": {
"name": "Ali Manager"
},
"appoint_power": {
"name": "Appoint Power"
},
"appoint_wash": {
"name": "Appoint Wash"
},
"auto_off": {
"name": "Auto Off"
},
"baby_wash": {
"name": "Baby Wash"
},
"bath": {
"name": "Bath"
},
"bath_person": {
"name": "Bath Person"
},
"big_water": {
"name": "Big Water"
},
"bottom_heat": {
"name": "Bottom Heat"
},
"clean": {
"name": "Clean"
},
"cloud": {
"name": "Cloud"
},
"cloud_appoint": {
"name": "Cloud Appoint"
},
"dad_wash": {
"name": "Dad Wash"
},
"eplus": {
"name": "Eplus"
},
"fast_hot_power": {
"name": "Fast Hot Power"
},
"fast_wash": {
"name": "Fast Wash"
},
"frequency_hot": {
"name": "Frequency Hot"
},
"get_temp": {
"name": "Get Temp"
},
"get_time": {
"name": "Get Time"
},
"half_heat": {
"name": "Half Heat"
},
"heat": {
"name": "Heat"
},
"hot_power": {
"name": "Hot Power"
},
"midea_manager": {
"name": "Midea Manager"
},
"mom_wash": {
"name": "Mom Wash"
},
"music": {
"name": "Music"
},
"negative_ions": {
"name": "Negative Ions"
},
"need_discharge": {
"name": "Need Discharge"
},
"night": {
"name": "Night"
},
"now_wash": {
"name": "Now Wash"
},
"one_egg": {
"name": "One Egg"
},
"people_wash": {
"name": "People Wash"
},
"protect": {
"name": "Protect"
},
"protect_show": {
"name": "Protect Show"
},
"scene": {
"name": "Scene"
},
"screen_off": {
"name": "Screen Off"
},
"scroll_hot": {
"name": "Scroll Hot"
},
"shower": {
"name": "Shower"
},
"single_wash": {
"name": "Single Wash"
},
"sleep": {
"name": "Sleep"
},
"smart_sterilize": {
"name": "Smart Sterilize"
},
"sound_dad": {
"name": "Sound Dad"
},
"sterilization": {
"name": "Sterilization"
},
"sterilize_high_temp": {
"name": "Sterilize High Temp"
},
"summer": {
"name": "Summer"
},
"t_hot": {
"name": "T Hot"
},
"tech_water": {
"name": "Tech Water"
},
"ti_protect": {
"name": "Ti Protect"
},
"top_heat": {
"name": "Top Heat"
},
"two_egg": {
"name": "Two Egg"
},
"uv_sterilize": {
"name": "UV Sterilize"
},
"warm_power": {
"name": "Warm Power"
},
"wash": {
"name": "Wash"
},
"wash_with_temp": {
"name": "Wash With Temp"
},
"water_cyclic": {
"name": "Water Cyclic"
},
"water_flow": {
"name": "Water Flow"
},
"whole_heat": {
"name": "Whole Heat"
},
"winter": {
"name": "Winter"
},
"ai_flag": {
"name": "AI Flag"
},

View File

@@ -49,6 +49,15 @@
},
"title": "选项"
},
"change_credentials": {
"title": "修改账号密码",
"description": "修改美的云账号的用户名和密码",
"data": {
"account": "用户名",
"password": "密码",
"server": "服务器"
}
},
"reset":{
"title": "重置配置文件",
"description": "移除已有的设备配置,并使用标准模板重新生成设备配置\n如果你的设备配置json文件进行过修改重置之后修改将丢失\n如果标准模板中没有该设备类型则不会生成设备配置",
@@ -173,6 +182,93 @@
},
"water_lack": {
"name": "缺水"
},
"communication_error": {
"name": "通信错误"
},
"door_status": {
"name": "门状态"
},
"ele_exception": {
"name": "电气异常"
},
"elec_warning": {
"name": "电气警告"
},
"limit_error": {
"name": "限制错误"
},
"sensor_error": {
"name": "传感器错误"
},
"lock": {
"name": "锁定"
},
"furnace_light": {
"name": "炉灯"
},
"dissipate_heat": {
"name": "散热"
},
"pre_heat": {
"name": "预热"
},
"door_open": {
"name": "门开启"
},
"lack_water": {
"name": "缺水"
},
"high_temperature_work": {
"name": "高温工作"
},
"lack_box": {
"name": "缺盒"
},
"clean_sink_ponding": {
"name": "清洁水槽积水"
},
"clean_scale": {
"name": "清洁水垢"
},
"flip_side": {
"name": "翻面"
},
"reaction": {
"name": "反应"
},
"ramadan": {
"name": "斋月"
},
"change_water": {
"name": "换水"
},
"execute": {
"name": "执行"
},
"power": {
"name": "电源"
},
"humidify": {
"name": "加湿"
},
"swing": {
"name": "摆风"
},
"anion": {
"name": "负离子"
},
"display_on_off": {
"name": "显示开关"
},
"dust_reset": {
"name": "灰尘重置"
},
"temp_wind_switch": {
"name": "温风开关"
},
"filter_reset": {
"name": "滤网重置"
}
},
"climate": {
@@ -381,8 +477,38 @@
"work_mode": {
"name": "工作模式"
},
"func_select": {
"name": "功能选择"
},
"machine": {
"name": "机器"
},
"type_select": {
"name": "类型选择"
},
"water_quality": {
"name": "水质"
},
"work_status": {
"name": "工作状态"
},
"voice": {
"name": "语音"
},
"swing_angle": {
"name": "摆风角度"
},
"swing_direction": {
"name": "摆风方向"
},
"scene": {
"name": "场景"
},
"sleep_sensor": {
"name": "睡眠传感器"
},
"gear": {
"name": "档位"
}
},
"sensor": {
@@ -952,12 +1078,366 @@
},
"zero_cold_tem": {
"name": "零冷水温度"
},
"bottom_temp": {
"name": "底部温度"
},
"cur_rate": {
"name": "当前流量"
},
"discharge_left_time": {
"name": "排水剩余时间"
},
"discharge_status": {
"name": "排水状态"
},
"end_time_hour": {
"name": "结束时间小时"
},
"end_time_minute": {
"name": "结束时间分钟"
},
"flow": {
"name": "流量"
},
"heat_water_level": {
"name": "热水水位"
},
"in_temperature": {
"name": "进水温度"
},
"mg_remain": {
"name": "镁离子剩余"
},
"morning_night_bash": {
"name": "晨夜冲洗"
},
"passwater_highbyte": {
"name": "过水高字节"
},
"passwater_lowbyte": {
"name": "过水低字节"
},
"rate": {
"name": "流量"
},
"scene_id": {
"name": "场景ID"
},
"screen_light": {
"name": "屏幕亮度"
},
"sterilize_cycle_index": {
"name": "杀菌循环索引"
},
"sterilize_left_days": {
"name": "杀菌剩余天数"
},
"tds_value": {
"name": "TDS值"
},
"top_temp": {
"name": "顶部温度"
},
"uv_sterilize_minute": {
"name": "紫外线杀菌分钟"
},
"uv_sterilize_second": {
"name": "紫外线杀菌秒"
},
"version": {
"name": "版本"
},
"volume": {
"name": "音量"
},
"wash_temperature": {
"name": "洗涤温度"
},
"water_system": {
"name": "水系统"
},
"waterday_highbyte": {
"name": "日用水高字节"
},
"waterday_lowbyte": {
"name": "日用水低字节"
},
"work_hour": {
"name": "工作小时"
},
"work_minute": {
"name": "工作分钟"
},
"work_second": {
"name": "工作秒"
},
"cur_temperature_above": {
"name": "当前温度(上)"
},
"cur_temperature_underside": {
"name": "当前温度(下)"
},
"weight": {
"name": "重量"
},
"people_number": {
"name": "人数"
},
"steam_quantity": {
"name": "蒸汽量"
},
"totalstep": {
"name": "总步数"
},
"stepnum": {
"name": "步数"
},
"hour_set": {
"name": "设置小时"
},
"minute_set": {
"name": "设置分钟"
},
"second_set": {
"name": "设置秒"
},
"ota": {
"name": "OTA"
},
"cbs_version": {
"name": "CBS版本"
},
"cloudmenuid": {
"name": "云菜单ID"
},
"real_gear": {
"name": "实际档位"
},
"dust_life_time": {
"name": "灰尘寿命"
},
"filter_life_time": {
"name": "滤网寿命"
},
"battery_status": {
"name": "电池状态"
},
"battery_level": {
"name": "电池电量"
},
"temperature_feedback": {
"name": "温度反馈"
},
"water_feedback": {
"name": "水位反馈"
},
"timer_off_hour": {
"name": "定时关机小时"
},
"timer_off_minute": {
"name": "定时关机分钟"
},
"timer_on_hour": {
"name": "定时开机小时"
},
"timer_on_minute": {
"name": "定时开机分钟"
},
"pm25": {
"name": "PM2.5"
},
"ud_swing_angle": {
"name": "上下摆风角度"
},
"lr_diy_down_percent": {
"name": "左右自定义下百分比"
},
"lr_diy_up_percent": {
"name": "左右自定义上百分比"
},
"ud_diy_down_percent": {
"name": "上下自定义下百分比"
},
"ud_diy_up_percent": {
"name": "上下自定义上百分比"
}
},
"switch": {
"add_water_flag": {
"name": "加水标志"
},
"ali_manager": {
"name": "阿里管理"
},
"appoint_power": {
"name": "预约电源"
},
"appoint_wash": {
"name": "预约洗涤"
},
"auto_off": {
"name": "自动关机"
},
"baby_wash": {
"name": "婴儿洗涤"
},
"bath": {
"name": "沐浴"
},
"bath_person": {
"name": "沐浴人员"
},
"big_water": {
"name": "大水"
},
"bottom_heat": {
"name": "底部加热"
},
"clean": {
"name": "清洁"
},
"cloud": {
"name": "云"
},
"cloud_appoint": {
"name": "云预约"
},
"dad_wash": {
"name": "爸爸洗涤"
},
"eplus": {
"name": "E加"
},
"fast_hot_power": {
"name": "快速加热电源"
},
"fast_wash": {
"name": "快速洗涤"
},
"frequency_hot": {
"name": "变频加热"
},
"get_temp": {
"name": "获取温度"
},
"get_time": {
"name": "获取时间"
},
"half_heat": {
"name": "半加热"
},
"heat": {
"name": "加热"
},
"hot_power": {
"name": "加热电源"
},
"midea_manager": {
"name": "美的管理"
},
"mom_wash": {
"name": "妈妈洗涤"
},
"music": {
"name": "音乐"
},
"negative_ions": {
"name": "负离子"
},
"need_discharge": {
"name": "需要排水"
},
"night": {
"name": "夜间"
},
"now_wash": {
"name": "立即洗涤"
},
"one_egg": {
"name": "一个蛋"
},
"people_wash": {
"name": "人员洗涤"
},
"protect": {
"name": "保护"
},
"protect_show": {
"name": "保护显示"
},
"scene": {
"name": "场景"
},
"screen_off": {
"name": "屏幕关闭"
},
"scroll_hot": {
"name": "滚动加热"
},
"shower": {
"name": "淋浴"
},
"single_wash": {
"name": "单次洗涤"
},
"sleep": {
"name": "睡眠"
},
"smart_sterilize": {
"name": "智能杀菌"
},
"sound_dad": {
"name": "声音爸爸"
},
"sterilization": {
"name": "杀菌"
},
"sterilize_high_temp": {
"name": "高温杀菌"
},
"summer": {
"name": "夏季"
},
"t_hot": {
"name": "T加热"
},
"tech_water": {
"name": "技术水"
},
"ti_protect": {
"name": "TI保护"
},
"top_heat": {
"name": "顶部加热"
},
"two_egg": {
"name": "两个蛋"
},
"uv_sterilize": {
"name": "紫外线杀菌"
},
"warm_power": {
"name": "保温电源"
},
"wash": {
"name": "洗涤"
},
"wash_with_temp": {
"name": "温度洗涤"
},
"water_cyclic": {
"name": "水循环"
},
"water_flow": {
"name": "水流"
},
"whole_heat": {
"name": "全加热"
},
"winter": {
"name": "冬季"
},
"ai_flag": {
"name": "AI标志"
},