mirror of
https://github.com/sususweet/midea-meiju-codec.git
synced 2026-02-28 06:36:36 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0591822cd2 | ||
|
|
1eb5518526 | ||
|
|
b0e2681c4e | ||
|
|
fed8519996 | ||
|
|
987ec12a81 | ||
|
|
890e241922 | ||
|
|
2ff714d0d5 | ||
|
|
8e160c53f7 | ||
|
|
c77dd6939e | ||
|
|
dd769e039d | ||
|
|
cfabc390f3 | ||
|
|
21b29f1a51 |
@@ -61,7 +61,8 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.LIGHT,
|
||||
Platform.HUMIDIFIER,
|
||||
Platform.NUMBER,
|
||||
Platform.BUTTON
|
||||
Platform.BUTTON,
|
||||
Platform.VACUUM
|
||||
]
|
||||
|
||||
async def import_module_async(module_name):
|
||||
@@ -172,7 +173,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType):
|
||||
from .const import BIT_LUA
|
||||
bit_lua = base64.b64decode(BIT_LUA.encode("utf-8")).decode("utf-8")
|
||||
try:
|
||||
with open(bit, "wt") as fp:
|
||||
with open(bit, "wt", encoding="utf-8") as fp:
|
||||
fp.write(bit_lua)
|
||||
except PermissionError as e:
|
||||
MideaLogger.error(f"Failed to create bit.lua at {bit}: {e}")
|
||||
@@ -180,7 +181,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType):
|
||||
import tempfile
|
||||
temp_dir = tempfile.gettempdir()
|
||||
bit = os.path.join(temp_dir, "bit.lua")
|
||||
with open(bit, "wt") as fp:
|
||||
with open(bit, "wt", encoding="utf-8") as fp:
|
||||
fp.write(bit_lua)
|
||||
MideaLogger.warning(f"Using temporary file for bit.lua: {bit}")
|
||||
|
||||
@@ -381,6 +382,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
str(Platform.SWITCH),
|
||||
str(Platform.FAN),
|
||||
str(Platform.SELECT),
|
||||
str(Platform.VACUUM),
|
||||
]:
|
||||
for entity_key in platform_cfg.keys():
|
||||
preset_keys.add(entity_key)
|
||||
|
||||
@@ -492,7 +492,7 @@ class MeijuCloud(MideaCloud):
|
||||
self._security.aes_decrypt_with_fixed_key(lua))
|
||||
stream = stream.replace("\r\n", "\n")
|
||||
fnm = f"{path}/{response['fileName']}"
|
||||
async with aiofiles.open(fnm, "w") as fp:
|
||||
async with aiofiles.open(fnm, "w", encoding="utf-8") as fp:
|
||||
await fp.write(stream)
|
||||
return fnm
|
||||
|
||||
@@ -744,10 +744,22 @@ class MSmartHomeCloud(MideaCloud):
|
||||
self._security.aes_decrypt_with_fixed_key(lua))
|
||||
stream = stream.replace("\r\n", "\n")
|
||||
fnm = f"{path}/{response['fileName']}"
|
||||
async with aiofiles.open(fnm, "w") as fp:
|
||||
async with aiofiles.open(fnm, "w", encoding="utf-8") as fp:
|
||||
await fp.write(stream)
|
||||
return fnm
|
||||
|
||||
async def download_plugin(
|
||||
self, path: str,
|
||||
appliance_code: str,
|
||||
smart_product_id: str,
|
||||
device_type: int,
|
||||
sn: str,
|
||||
sn8: str,
|
||||
model_number: str | None,
|
||||
manufacturer_code: str = "0000",
|
||||
):
|
||||
return
|
||||
|
||||
async def send_cloud(self, appliance_code: int, data: bytearray):
|
||||
appliance_code = str(appliance_code)
|
||||
params = {
|
||||
|
||||
@@ -84,13 +84,48 @@ class MiedaDevice(threading.Thread):
|
||||
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
|
||||
|
||||
def _determine_control_status_based_on_running(self, running_status):
|
||||
def _handle_t0xd9_db_location_selection(self, status, value):
|
||||
# 处理T0xD9复式洗衣机的db_location_selection更新
|
||||
if value == "left":
|
||||
status["db_location"] = 1
|
||||
self._attributes["db_location"] = 1
|
||||
elif value == "right":
|
||||
status["db_location"] = 2
|
||||
self._attributes["db_location"] = 2
|
||||
|
||||
def _adjust_t0xd9_db_location_based_on_position(self, status=None):
|
||||
# 根据db_position调整T0xD9复式洗衣机的db_location
|
||||
db_position = self._attributes.get("db_position", 1)
|
||||
current_location = self._attributes.get("db_location", 1)
|
||||
|
||||
if db_position == 1:
|
||||
# db_position = 1,db_location 保持不变
|
||||
calculated_location = current_location
|
||||
elif db_position == 0:
|
||||
# db_position = 0,db_location 切换为另一个选项
|
||||
calculated_location = 2 if current_location == 1 else 1
|
||||
|
||||
if status is not None:
|
||||
status["db_location"] = calculated_location
|
||||
|
||||
return calculated_location
|
||||
|
||||
def _sync_t0xd9_location_selection(self, location):
|
||||
# 同步T0xD9复式洗衣机的db_location和db_location_selection
|
||||
if location == 1:
|
||||
self._attributes["db_location_selection"] = "left"
|
||||
elif location == 2:
|
||||
self._attributes["db_location_selection"] = "right"
|
||||
|
||||
def _adjust_t0xd9_control_status(self, running_status):
|
||||
# 依据db_running_status调整T0xD9复式洗衣机的db_control_status
|
||||
# 根据运行状态确定控制状态, 只有当运行状态是"start"时,控制状态才为"start"
|
||||
if running_status == "start":
|
||||
return "start"
|
||||
control_status = "start"
|
||||
# 其他所有情况(包括standby、pause、off、error等),控制状态应为pause
|
||||
else:
|
||||
return "pause"
|
||||
control_status = "pause"
|
||||
self._attributes["db_control_status"] = control_status
|
||||
|
||||
@property
|
||||
def device_name(self):
|
||||
@@ -179,39 +214,14 @@ class MiedaDevice(threading.Thread):
|
||||
new_status[attr] = self._attributes.get(attr)
|
||||
new_status[attribute] = value
|
||||
|
||||
# 针对T0xD9复式洗衣机,当切换筒选择时,立即刷新状态以显示新筒的状态
|
||||
if self._device_type == 0xD9 and attribute == "db_location_selection":
|
||||
# 更新属性
|
||||
self._attributes[attribute] = value
|
||||
|
||||
# 更新db_location(用于查询)
|
||||
if value == "left":
|
||||
self._attributes["db_location"] = 1
|
||||
elif value == "right":
|
||||
self._attributes["db_location"] = 2
|
||||
|
||||
# 立即刷新状态以显示新筒的状态
|
||||
await self.refresh_status()
|
||||
|
||||
# 获取当前运行状态
|
||||
running_status = self._attributes.get("db_running_status")
|
||||
if running_status is not None:
|
||||
# 根据运行状态确定控制状态
|
||||
control_status = self._determine_control_status_based_on_running(running_status)
|
||||
# 更新本地属性
|
||||
self._attributes["db_control_status"] = control_status
|
||||
# 添加到要发送的状态中(如果需要发送到云端)
|
||||
new_status["db_control_status"] = control_status
|
||||
# return # 发送到云端,所以注释teturn
|
||||
|
||||
# 针对T0xD9复式洗衣机,根据选择的筒添加db_location参数
|
||||
if self._device_type == 0xD9 and attribute != "db_location_selection":
|
||||
location_selection = self._attributes.get("db_location_selection", "left")
|
||||
if location_selection == "left":
|
||||
new_status["db_location"] = 1
|
||||
elif location_selection == "right":
|
||||
new_status["db_location"] = 2
|
||||
|
||||
# 针对T0xD9复式洗衣机,当本地变更 db_location_selection 时,调整 db_location
|
||||
if self._device_type == 0xD9:
|
||||
if attribute == "db_location_selection":
|
||||
self._handle_t0xd9_db_location_selection(new_status, value)
|
||||
# 非 db_location_selection 更新,根据 db_position 设置 db_location
|
||||
else:
|
||||
self._adjust_t0xd9_db_location_based_on_position(new_status)
|
||||
|
||||
# Convert dot-notation attributes to nested structure for transmission
|
||||
nested_status = self._convert_to_nested_structure(new_status)
|
||||
|
||||
@@ -239,31 +249,6 @@ class MiedaDevice(threading.Thread):
|
||||
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
|
||||
|
||||
async def set_attributes(self, attributes):
|
||||
# 针对T0xD9复式洗衣机,当切换筒选择时
|
||||
if self._device_type == 0xD9 and "db_location_selection" in attributes:
|
||||
location_selection = attributes["db_location_selection"]
|
||||
|
||||
# 更新本地属性
|
||||
self._attributes["db_location_selection"] = location_selection
|
||||
|
||||
# 更新db_location(用于查询)
|
||||
if location_selection == "left":
|
||||
self._attributes["db_location"] = 1
|
||||
elif location_selection == "right":
|
||||
self._attributes["db_location"] = 2
|
||||
|
||||
# 立即刷新状态以显示新筒的状态
|
||||
await self.refresh_status()
|
||||
|
||||
# 获取当前运行状态
|
||||
running_status = self._attributes.get("db_running_status")
|
||||
if running_status is not None:
|
||||
# 根据运行状态确定控制状态
|
||||
control_status = self._determine_control_status_based_on_running(running_status)
|
||||
# 更新本地属性
|
||||
self._attributes["db_control_status"] = control_status
|
||||
# return # 发送到云端,所以注释teturn
|
||||
|
||||
new_status = {}
|
||||
for attr in self._centralized:
|
||||
new_status[attr] = self._attributes.get(attr)
|
||||
@@ -273,25 +258,15 @@ class MiedaDevice(threading.Thread):
|
||||
has_new = True
|
||||
new_status[attribute] = value
|
||||
|
||||
# 针对T0xD9复式洗衣机,确保发送到云端的控制命令包含筒位置信息
|
||||
# 针对T0xD9复式洗衣机,根据 db_location_selection 调整 db_location
|
||||
if self._device_type == 0xD9:
|
||||
# 如果attributes中有db_location_selection,确保new_status也有
|
||||
if "db_location_selection" in attributes:
|
||||
location_selection = attributes["db_location_selection"]
|
||||
new_status["db_location_selection"] = location_selection
|
||||
# 添加对应的db_location
|
||||
if location_selection == "left":
|
||||
new_status["db_location"] = 1
|
||||
elif location_selection == "right":
|
||||
new_status["db_location"] = 2
|
||||
# 如果没有db_location_selection,但当前有选择,添加db_location
|
||||
elif "db_location_selection" not in attributes and self._attributes.get("db_location_selection"):
|
||||
location_selection = self._attributes.get("db_location_selection", "left")
|
||||
if location_selection == "left":
|
||||
new_status["db_location"] = 1
|
||||
elif location_selection == "right":
|
||||
new_status["db_location"] = 2
|
||||
|
||||
self._handle_t0xd9_db_location_selection(new_status, location_selection)
|
||||
else:
|
||||
# 非 db_location_selection 更新,根据 db_position 设置 db_location
|
||||
self._adjust_t0xd9_db_location_based_on_position(new_status)
|
||||
|
||||
# Convert dot-notation attributes to nested structure for transmission
|
||||
nested_status = self._convert_to_nested_structure(new_status)
|
||||
|
||||
@@ -381,15 +356,15 @@ class MiedaDevice(threading.Thread):
|
||||
|
||||
async def refresh_status(self):
|
||||
for query in self._queries:
|
||||
# 针对T0xD9复式洗衣机,根据选择的筒动态添加db_location参数
|
||||
# 针对T0xD9复式洗衣机,根据 db_position 动态调整 db_location
|
||||
actual_query = query.copy() if isinstance(query, dict) else query
|
||||
if self._device_type == 0xD9 and isinstance(actual_query, dict):
|
||||
location_selection = self._attributes.get("db_location_selection", "left")
|
||||
if location_selection == "left":
|
||||
actual_query["db_location"] = 1
|
||||
elif location_selection == "right":
|
||||
actual_query["db_location"] = 2
|
||||
|
||||
# 根据 db_position 调整 db_location
|
||||
calculated_location = self._adjust_t0xd9_db_location_based_on_position(actual_query)
|
||||
|
||||
# 同步更新db_location_selection
|
||||
self._sync_t0xd9_location_selection(calculated_location)
|
||||
|
||||
cloud = self._cloud
|
||||
if cloud and hasattr(cloud, "get_device_status"):
|
||||
if isinstance(cloud, MSmartHomeCloud):
|
||||
@@ -434,6 +409,12 @@ class MiedaDevice(threading.Thread):
|
||||
if single not in self._attributes or self._attributes[single] != value:
|
||||
# self._attributes[single] = value
|
||||
new_status[single] = value
|
||||
|
||||
# 对于T0xD9复式洗衣机,依据云端 db_running_status,调整本地 db_control_status
|
||||
if self._device_type == 0xD9 and "db_running_status" in new_status:
|
||||
running_status = new_status["db_running_status"]
|
||||
self._adjust_t0xd9_control_status(running_status)
|
||||
|
||||
if len(new_status) > 0:
|
||||
for c in self._calculate_get:
|
||||
lvalue = c.get("lvalue")
|
||||
@@ -623,5 +604,3 @@ class MiedaDevice(threading.Thread):
|
||||
# f"{e.__traceback__.tb_lineno}, {repr(e)}")
|
||||
# self.disconnect()
|
||||
# break
|
||||
|
||||
|
||||
|
||||
@@ -1128,7 +1128,8 @@ DEVICE_MAPPING = {
|
||||
},
|
||||
("22012369", "22040023", "22270043"): {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}, {"query_type": "prevent_straight_wind"}],
|
||||
"queries": [{}, {"query_type": "prevent_straight_wind"}, {"query_type": "prevent_super_cool"},
|
||||
{"query_type": "wind_swing_ud_angle"}, {"query_type": "wind_swing_lr_angle"}],
|
||||
"centralized": ["buzzer"],
|
||||
"calculate":{
|
||||
"get": [
|
||||
@@ -1183,10 +1184,33 @@ DEVICE_MAPPING = {
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"wind_swing_ud_angle": {
|
||||
"options": {
|
||||
"关闭": {"wind_swing_ud_angle": 0},
|
||||
"最上": {"wind_swing_ud_angle": 1},
|
||||
"偏上": {"wind_swing_ud_angle": 25},
|
||||
"中间": {"wind_swing_ud_angle": 50},
|
||||
"偏下": {"wind_swing_ud_angle": 75},
|
||||
"最下": {"wind_swing_ud_angle": 100}
|
||||
}
|
||||
},
|
||||
"wind_swing_lr_angle": {
|
||||
"options": {
|
||||
"关闭": {"wind_swing_lr_angle": 0},
|
||||
"最左": {"wind_swing_lr_angle": 1},
|
||||
"偏左": {"wind_swing_lr_angle": 25},
|
||||
"中间": {"wind_swing_lr_angle": 50},
|
||||
"偏右": {"wind_swing_lr_angle": 75},
|
||||
"最右": {"wind_swing_lr_angle": 100}
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"buzzer": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"default_value": "on",
|
||||
"translation_key": "voice"
|
||||
},
|
||||
"screen_display": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
@@ -1196,6 +1220,9 @@ DEVICE_MAPPING = {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [1, 2]
|
||||
},
|
||||
"prevent_super_cool": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"dry": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
@@ -1223,7 +1250,8 @@ DEVICE_MAPPING = {
|
||||
},
|
||||
"22251077": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}, {"query_type": "prevent_straight_wind"}],
|
||||
"queries": [{}, {"query_type": "no_wind_sense"}, {"query_type": "prevent_super_cool"},
|
||||
{"query_type": "wind_swing_ud_angle"}, {"query_type": "wind_swing_lr_angle"}],
|
||||
"centralized": ["buzzer"],
|
||||
"calculate":{
|
||||
"get": [
|
||||
@@ -1278,15 +1306,49 @@ DEVICE_MAPPING = {
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"wind_swing_ud_angle": {
|
||||
"options": {
|
||||
"关闭": {"wind_swing_ud_angle": 0},
|
||||
"最上": {"wind_swing_ud_angle": 1},
|
||||
"偏上": {"wind_swing_ud_angle": 25},
|
||||
"中间": {"wind_swing_ud_angle": 50},
|
||||
"偏下": {"wind_swing_ud_angle": 75},
|
||||
"最下": {"wind_swing_ud_angle": 100}
|
||||
}
|
||||
},
|
||||
"wind_swing_lr_angle": {
|
||||
"options": {
|
||||
"关闭": {"wind_swing_lr_angle": 0},
|
||||
"最左": {"wind_swing_lr_angle": 1},
|
||||
"偏左": {"wind_swing_lr_angle": 25},
|
||||
"中间": {"wind_swing_lr_angle": 50},
|
||||
"偏右": {"wind_swing_lr_angle": 75},
|
||||
"最右": {"wind_swing_lr_angle": 100}
|
||||
}
|
||||
},
|
||||
"no_wind_sense": {
|
||||
"options": {
|
||||
"关闭": {"no_wind_sense": 0},
|
||||
"上+下无风感": {"no_wind_sense": 1},
|
||||
"上无风感": {"no_wind_sense": 2},
|
||||
"下无风感": {"no_wind_sense": 3},
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"buzzer": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"default_value": "on",
|
||||
"translation_key": "voice"
|
||||
},
|
||||
"screen_display": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"translation_key": "screen_close",
|
||||
},
|
||||
"prevent_super_cool": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"dry": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
|
||||
@@ -8,15 +8,19 @@ DEVICE_MAPPING = {
|
||||
"queries": [{}],
|
||||
"centralized": [],
|
||||
"entities": {
|
||||
Platform.SELECT: {
|
||||
"fan_setting": {
|
||||
"options": {
|
||||
Platform.VACUUM: {
|
||||
"vacuum": {
|
||||
"battery_level": "battery_percent",
|
||||
"status": "work_status",
|
||||
"fan_speeds": {
|
||||
"soft": {"level": "soft"},
|
||||
"normal": {"level": "normal"},
|
||||
"high": {"level": "high"},
|
||||
"super": {"level": "super"}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"work_mode": {
|
||||
"options": {
|
||||
"sweep_and_mop": {"work_mode": "sweep_and_mop"},
|
||||
@@ -27,15 +31,6 @@ DEVICE_MAPPING = {
|
||||
},
|
||||
"work_status": {
|
||||
"options": {
|
||||
"charge": {"work_status": "charge"},
|
||||
"charge_pause": {"work_status": "charge_pause"},
|
||||
"charge_continue": {"work_status": "charge_continue"},
|
||||
"auto_clean": {"work_status": "auto_clean"},
|
||||
"auto_clean_pause": {"work_status": "auto_clean_pause"},
|
||||
"auto_clean_continue": {"work_status": "auto_clean_continue"},
|
||||
"pause": {"work_status": "pause"},
|
||||
"stop": {"work_status": "stop"},
|
||||
"work": {"work_status": "work"},
|
||||
"video_cruise_start": {"work_status": "video_cruise_start"},
|
||||
"video_cruise_pause": {"work_status": "video_cruise_pause"},
|
||||
"mop_clean": {"mop_clean_setting": {"mode_type": "common", "clean_level": "normal"}},
|
||||
@@ -125,27 +120,18 @@ DEVICE_MAPPING = {
|
||||
],
|
||||
"centralized": ["work_status", "battery_percent", "sweep_mop_mode", "mop", "sub_work_status"],
|
||||
"entities": {
|
||||
Platform.SELECT: {
|
||||
"work_status": {
|
||||
"options": {
|
||||
"charge": {"work_status": "charge"},
|
||||
"charge_pause": {"work_status": "charge_pause"},
|
||||
"charge_continue": {"work_status": "charge_continue"},
|
||||
"auto_clean": {"work_status": "auto_clean"},
|
||||
"auto_clean_pause": {"work_status": "auto_clean_pause"},
|
||||
"auto_clean_continue": {"work_status": "auto_clean_continue"},
|
||||
"pause": {"work_status": "pause"},
|
||||
"stop": {"work_status": "stop"},
|
||||
"work": {"work_status": "work"}
|
||||
}
|
||||
},
|
||||
"fan_level": {
|
||||
"options": {
|
||||
Platform.VACUUM: {
|
||||
"vacuum": {
|
||||
"battery_level": "battery_percent",
|
||||
"status": "work_status",
|
||||
"fan_speeds": {
|
||||
"soft": {"fan_setting": {"level": "soft"}},
|
||||
"normal": {"fan_setting": {"level": "normal"}},
|
||||
"high": {"fan_setting": {"level": "high"}}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"sweep_mop_mode": {
|
||||
"options": {
|
||||
"sweep_and_mop": {"work_mode_setting": {"work_mode": "sweep_and_mop"}},
|
||||
|
||||
@@ -19,17 +19,21 @@ DEVICE_MAPPING = {
|
||||
},
|
||||
"entities": {
|
||||
Platform.BINARY_SENSOR: {
|
||||
"db_power": {
|
||||
"device_class": BinarySensorDeviceClass.RUNNING,
|
||||
},
|
||||
"door_opened": {
|
||||
"db_door_opened": {
|
||||
"device_class": BinarySensorDeviceClass.OPENING,
|
||||
"translation_key": "door_opened"
|
||||
},
|
||||
"bucket_water_overheating": {
|
||||
"db_bucket_water_overheating": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
"translation_key": "bucket_water_overheating"
|
||||
},
|
||||
"drying_tunnel_overheating": {
|
||||
"db_drying_tunnel_overheating": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
"translation_key": "drying_tunnel_overheating"
|
||||
},
|
||||
"db_detergent_needed": {
|
||||
"device_class": BinarySensorDeviceClass.PROBLEM,
|
||||
"translation_key": "detergent_lack"
|
||||
}
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"issue_tracker": "https://github.com/sususweet/midea-meiju-codec/issues",
|
||||
"requirements": ["lupa>=2.0"],
|
||||
"version": "v0.2.5"
|
||||
"version": "v0.2.6"
|
||||
}
|
||||
|
||||
@@ -442,6 +442,15 @@
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"no_wind_sense": {
|
||||
"name": "No Wind Sense"
|
||||
},
|
||||
"wind_swing_ud_angle": {
|
||||
"name": "Vertical Wind Direction"
|
||||
},
|
||||
"wind_swing_lr_angle": {
|
||||
"name": "Horizontal Wind Direction"
|
||||
},
|
||||
"custom_temperature": {
|
||||
"name": "Custom Temperature"
|
||||
},
|
||||
@@ -2358,7 +2367,10 @@
|
||||
"name": "Fan"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"prevent_super_cool": {
|
||||
"name": "Prevent Super Cool"
|
||||
},
|
||||
"new_wind_machine": {
|
||||
"name": "Fresh air switch"
|
||||
},
|
||||
@@ -3385,6 +3397,21 @@
|
||||
"sedentary_remind": {
|
||||
"name": "Sedentary Remind"
|
||||
}
|
||||
},
|
||||
"vacuum": {
|
||||
"vacuum": {
|
||||
"name": "Robotic Vacuum",
|
||||
"state_attributes": {
|
||||
"fan_speed": {
|
||||
"state": {
|
||||
"soft": "Soft",
|
||||
"normal": "Normal",
|
||||
"high": "High",
|
||||
"super": "Super"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,6 +462,15 @@
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"no_wind_sense": {
|
||||
"name": "无风感"
|
||||
},
|
||||
"wind_swing_ud_angle": {
|
||||
"name": "上下摆动风向"
|
||||
},
|
||||
"wind_swing_lr_angle": {
|
||||
"name": "左右摆动风向"
|
||||
},
|
||||
"custom_temperature": {
|
||||
"name": "自定义烧水温度"
|
||||
},
|
||||
@@ -2689,7 +2698,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"prevent_super_cool": {
|
||||
"name": "智控温"
|
||||
},
|
||||
"new_wind_machine": {
|
||||
"name": "新风开关"
|
||||
},
|
||||
@@ -3716,6 +3728,21 @@
|
||||
"sedentary_remind": {
|
||||
"name": "久坐提醒"
|
||||
}
|
||||
},
|
||||
"vacuum": {
|
||||
"vacuum": {
|
||||
"name": "扫地机器人",
|
||||
"state_attributes": {
|
||||
"fan_speed": {
|
||||
"state": {
|
||||
"soft": "轻柔",
|
||||
"normal": "标准",
|
||||
"high": "强力",
|
||||
"super": "超强"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
195
custom_components/midea_auto_cloud/vacuum.py
Normal file
195
custom_components/midea_auto_cloud/vacuum.py
Normal file
@@ -0,0 +1,195 @@
|
||||
from homeassistant.components.vacuum import (
|
||||
StateVacuumEntity,
|
||||
VacuumEntityFeature,
|
||||
VacuumActivity,
|
||||
)
|
||||
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 .core.logger import MideaLogger
|
||||
from .midea_entity import MideaEntity
|
||||
from . import load_device_config
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up vacuum entities for Midea devices."""
|
||||
# 账号型 entry:从 __init__ 写入的 accounts 桶加载设备和协调器
|
||||
account_bucket = hass.data.get(DOMAIN, {}).get("accounts", {}).get(config_entry.entry_id)
|
||||
if not account_bucket:
|
||||
async_add_entities([])
|
||||
return
|
||||
device_list = account_bucket.get("device_list", {})
|
||||
coordinator_map = account_bucket.get("coordinator_map", {})
|
||||
|
||||
devs = []
|
||||
for device_id, info in device_list.items():
|
||||
device_type = info.get("type")
|
||||
sn8 = info.get("sn8")
|
||||
config = await load_device_config(hass, device_type, sn8) or {}
|
||||
entities_cfg = (config.get("entities") or {}).get(Platform.VACUUM, {})
|
||||
manufacturer = config.get("manufacturer")
|
||||
rationale = config.get("rationale")
|
||||
coordinator = coordinator_map.get(device_id)
|
||||
device = coordinator.device if coordinator else None
|
||||
|
||||
for entity_key, ecfg in entities_cfg.items():
|
||||
devs.append(MideaVacuumEntity(
|
||||
coordinator, device, manufacturer, rationale, entity_key, ecfg
|
||||
))
|
||||
async_add_entities(devs)
|
||||
|
||||
class MideaVacuumEntity(MideaEntity, StateVacuumEntity):
|
||||
def __init__(self, coordinator, device, manufacturer, rationale, entity_key, config):
|
||||
super().__init__(
|
||||
coordinator,
|
||||
device.device_id,
|
||||
device.device_name,
|
||||
f"T0x{device.device_type:02X}",
|
||||
device.sn,
|
||||
device.sn8,
|
||||
device.model,
|
||||
entity_key,
|
||||
device=device,
|
||||
manufacturer=manufacturer,
|
||||
rationale=rationale,
|
||||
config=config,
|
||||
)
|
||||
self._key_battery_level = self._config.get("battery_level")
|
||||
self._key_status = self._config.get("status")
|
||||
self._key_fan_speeds = self._config.get("fan_speeds")
|
||||
#self._key_locate = self._config.get("locate")
|
||||
#self._key_clean_spot = self._config.get("clean_spot")
|
||||
#self._key_map = self._config.get("map")
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = VacuumEntityFeature(0)
|
||||
features |= VacuumEntityFeature.STOP
|
||||
features |= VacuumEntityFeature.PAUSE
|
||||
features |= VacuumEntityFeature.START
|
||||
features |= VacuumEntityFeature.RETURN_HOME
|
||||
features |= VacuumEntityFeature.FAN_SPEED
|
||||
features |= VacuumEntityFeature.STATUS
|
||||
features |= VacuumEntityFeature.BATTERY
|
||||
#features |= VacuumEntityFeature.LOCATE
|
||||
#features |= VacuumEntityFeature.CLEAN_SPOT
|
||||
#features |= VacuumEntityFeature.MAP
|
||||
return features
|
||||
|
||||
@property
|
||||
def battery_level(self):
|
||||
"""Return the battery level of the vacuum cleaner."""
|
||||
battery = self._get_nested_value(self._key_battery_level)
|
||||
if battery is not None:
|
||||
try:
|
||||
return int(battery)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return None
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""Return the status of the vacuum cleaner."""
|
||||
status = self._get_nested_value(self._key_status)
|
||||
if status is not None:
|
||||
return status
|
||||
return None
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the vacuum cleaner."""
|
||||
status = self.status
|
||||
if not status:
|
||||
return None
|
||||
|
||||
# Map Midea status to Home Assistant states
|
||||
status_mapping = {
|
||||
# === 清洁中状态 (CLEANING) ===
|
||||
"work": VacuumActivity.CLEANING, # 清扫中
|
||||
"auto_clean": VacuumActivity.CLEANING, # 自动清扫中
|
||||
|
||||
# === 已停靠状态 (DOCKED) ===
|
||||
"charging_on_dock": VacuumActivity.DOCKED, # 座充中
|
||||
"on_base": VacuumActivity.DOCKED, # 在基站上
|
||||
"charge_finish": VacuumActivity.DOCKED, # 充电完成
|
||||
|
||||
# === 空闲状态 (IDLE) ===
|
||||
"stop": VacuumActivity.IDLE, # 已停止
|
||||
"sleep": VacuumActivity.IDLE, # 休眠中
|
||||
|
||||
# === 暂停状态 (PAUSED) ===
|
||||
"clean_pause": VacuumActivity.PAUSED, # 清扫暂停
|
||||
"charge_pause": VacuumActivity.PAUSED, # 充电暂停
|
||||
|
||||
# === 返回中状态 (RETURNING) ===
|
||||
"charging": VacuumActivity.RETURNING, # 返回基站中
|
||||
|
||||
# === 错误状态 (ERROR) ===
|
||||
"error": VacuumActivity.ERROR, # 错误
|
||||
}
|
||||
|
||||
return status_mapping.get(status, status)
|
||||
|
||||
@property
|
||||
def fan_speed(self):
|
||||
"""Return the current fan speed."""
|
||||
return self._dict_get_selected(self._key_fan_speeds)
|
||||
|
||||
@property
|
||||
def fan_speed_list(self):
|
||||
"""Return the list of available fan speeds."""
|
||||
return list(self._key_fan_speeds.keys())
|
||||
|
||||
async def async_start(self):
|
||||
"""Start or resume the cleaning task."""
|
||||
# 设置为工作状态
|
||||
if self._key_status:
|
||||
await self.async_set_attribute(self._key_status, "work")
|
||||
else:
|
||||
await self._async_set_status_on_off(self._key_power, True)
|
||||
|
||||
async def async_stop(self):
|
||||
"""Stop the vacuum cleaner."""
|
||||
# 设置为停止状态
|
||||
if self._key_status:
|
||||
await self.async_set_attribute(self._key_status, "stop")
|
||||
else:
|
||||
await self._async_set_status_on_off(self._key_power, False)
|
||||
|
||||
async def async_pause(self):
|
||||
"""Pause the cleaning task."""
|
||||
# 设置为暂停状态
|
||||
if self._key_status:
|
||||
await self.async_set_attribute(self._key_status, "pause")
|
||||
|
||||
async def async_return_to_base(self):
|
||||
"""Return the vacuum cleaner to its base."""
|
||||
# 设置为回基站状态
|
||||
if self._key_status:
|
||||
await self.async_set_attribute(self._key_status, "charge")
|
||||
|
||||
async def async_set_fan_speed(self, fan_speed: str):
|
||||
"""Set the fan speed."""
|
||||
new_status = self._key_fan_speeds.get(fan_speed)
|
||||
if new_status is not None:
|
||||
await self.async_set_attributes(new_status)
|
||||
|
||||
#async def async_locate(self):
|
||||
#"""Locate the vacuum cleaner."""
|
||||
# 定位设备
|
||||
# 具体实现取决于设备的控制方式
|
||||
#if hasattr(self, "_key_locate"):
|
||||
#await self.async_set_attribute(self._key_locate, True)
|
||||
|
||||
#async def async_clean_spot(self):
|
||||
#"""Perform a clean spot."""
|
||||
# 执行定点清扫
|
||||
# 具体实现取决于设备的控制方式
|
||||
#if hasattr(self, "_key_clean_spot"):
|
||||
#await self.async_set_attribute(self._key_clean_spot, True)
|
||||
Reference in New Issue
Block a user