feat: add plugin download for devices.

This commit is contained in:
sususweet
2025-11-27 16:17:58 +08:00
parent a0c1423933
commit 7fee90b1fd
6 changed files with 131 additions and 4 deletions

View File

@@ -61,7 +61,7 @@ Collaboration method: After adding this plugin, find devices that are not correc
Expand the `Attributes` card below and submit these fields with the issue. Pay special attention to the `Device type` and `Subtype` fields, as these are the basis for obtaining the corresponding lua files for device control. Expand the `Attributes` card below and submit these fields with the issue. Pay special attention to the `Device type` and `Subtype` fields, as these are the basis for obtaining the corresponding lua files for device control.
Then go to the Home Assistant installation directory, find the device's corresponding T_0000_`Device type`_`Subtype`_***.lua file in the `.storage/midea_auto_cloud/lua/` directory, and wait for adaptation. Then go to the Home Assistant installation directory, find the device's corresponding T_0000_`Device type`_`Subtype`_***.lua file in the `.storage/midea_auto_cloud/lua/` directory and `zip` file in the `.storage/midea_auto_cloud/plugin/` directory, and wait for adaptation.
![img_1.png](./img/img_1.png) ![img_1.png](./img/img_1.png)

View File

@@ -61,7 +61,7 @@
展开下面的`属性`卡片把里面这些字段随issue提交。 着重关注Device type、Subtype这两个字段这是后续获得设备控制对应lua文件的基础。 展开下面的`属性`卡片把里面这些字段随issue提交。 着重关注Device type、Subtype这两个字段这是后续获得设备控制对应lua文件的基础。
再进入Homeassistant的安装目录`.storage/midea_auto_cloud/lua/`目录下找到设备对应的T_0000_`Device type`_`Subtype`_***.lua文件等待适配就可以了。 再进入Homeassistant的安装目录`.storage/midea_auto_cloud/lua/`目录下找到设备对应的T_0000_`Device type`_`Subtype`_***.lua文件以及`.storage/midea_auto_cloud/plugin/`目录下的`zip`文件,等待适配就可以了。
![img_1.png](./img/img_1.png) ![img_1.png](./img/img_1.png)

View File

@@ -1,6 +1,7 @@
import asyncio import asyncio
import os import os
import base64 import base64
import traceback
from importlib import import_module from importlib import import_module
import re import re
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@@ -44,7 +45,7 @@ from .const import (
CONF_SN, CONF_SN,
CONF_MODEL_NUMBER, CONF_MODEL_NUMBER,
CONF_SERVERS, STORAGE_PATH, CONF_MANUFACTURER_CODE, CONF_SERVERS, STORAGE_PATH, CONF_MANUFACTURER_CODE,
CONF_SELECTED_HOMES CONF_SELECTED_HOMES, CONF_SMART_PRODUCT_ID, STORAGE_PLUGIN_PATH
) )
# 账号型:登录云端、获取设备列表,并为每台设备建立协调器(无本地控制) # 账号型:登录云端、获取设备列表,并为每台设备建立协调器(无本地控制)
from .const import CONF_PASSWORD as CONF_PASSWORD_KEY, CONF_SERVER as CONF_SERVER_KEY from .const import CONF_PASSWORD as CONF_PASSWORD_KEY, CONF_SERVER as CONF_SERVER_KEY
@@ -249,6 +250,22 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
model_number=info.get(CONF_MODEL_NUMBER), model_number=info.get(CONF_MODEL_NUMBER),
manufacturer_code=info.get(CONF_MANUFACTURER_CODE), manufacturer_code=info.get(CONF_MANUFACTURER_CODE),
) )
try:
os.makedirs(hass.config.path(STORAGE_PLUGIN_PATH), exist_ok=True)
plugin_path = hass.config.path(STORAGE_PLUGIN_PATH)
await cloud.download_plugin(
path=plugin_path,
appliance_code=appliance_code,
smart_product_id=info.get(CONF_SMART_PRODUCT_ID),
device_type=info.get(CONF_TYPE),
sn=info.get(CONF_SN),
sn8=info.get(CONF_SN8),
model_number=info.get(CONF_MODEL_NUMBER),
manufacturer_code=info.get(CONF_MANUFACTURER_CODE),
)
except Exception as e:
traceback.print_exc()
try: try:
device = MiedaDevice( device = MiedaDevice(
name=info.get(CONF_NAME), name=info.get(CONF_NAME),

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,7 @@ import datetime
import json import json
import base64 import base64
import traceback import traceback
import os
import aiofiles import aiofiles
import requests import requests
from aiohttp import ClientSession from aiohttp import ClientSession
@@ -176,7 +177,19 @@ class MideaCloud:
manufacturer_code: str = "0000", manufacturer_code: str = "0000",
): ):
raise NotImplementedError() raise NotImplementedError()
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",
):
raise NotImplementedError()
async def send_central_ac_control(self, appliance_code: int, nodeid: str, modelid: str, idtype: int, control: dict) -> bool: async def send_central_ac_control(self, appliance_code: int, nodeid: str, modelid: str, idtype: int, control: dict) -> bool:
"""Send control to central AC subdevice. Subclasses should implement if supported.""" """Send control to central AC subdevice. Subclasses should implement if supported."""
raise NotImplementedError() raise NotImplementedError()
@@ -284,6 +297,7 @@ class MeijuCloud(MideaCloud):
"type": int(appliance.get("type"), 16), "type": int(appliance.get("type"), 16),
"sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "", "sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "",
"sn8": appliance.get("sn8", "00000000"), "sn8": appliance.get("sn8", "00000000"),
"smart_product_id": appliance.get("smartProductId", "0"),
"model_number": appliance.get("modelNumber", "0"), "model_number": appliance.get("modelNumber", "0"),
"manufacturer_code": appliance.get("enterpriseCode", "0000"), "manufacturer_code": appliance.get("enterpriseCode", "0000"),
"model": appliance.get("productModel"), "model": appliance.get("productModel"),
@@ -472,6 +486,93 @@ class MeijuCloud(MideaCloud):
return fnm 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",
):
# 构建 applianceList根据传入的参数动态生成
appliance_info = {
"appModel": sn8,
"appEnterprise": manufacturer_code,
"appType": f"0x{device_type:02X}",
"applianceCode": str(appliance_code) if isinstance(appliance_code, int) else appliance_code,
"smartProductId": str(smart_product_id) if isinstance(smart_product_id, int) else smart_product_id,
"modelNumber": model_number or "0",
"versionCode": 0
}
appliance_list = [appliance_info]
data = {
"applianceList": json.dumps(appliance_list),
"iotAppId": self.APP_ID,
"match": "1",
"clientType": "1",
"clientVersion": 201
}
fnm = None
if response := await self._api_request(
endpoint="/v1/plugin/update/getPluginV3",
data=data
):
# response 是 {"list": [...]}
plugin_list = response.get("list", [])
if not plugin_list:
MideaLogger.warning(f"No plugin found for device type 0x{device_type:02X}, sn: {sn}")
return None
# 找到匹配的设备(优先匹配 applianceCode其次匹配 appType
matched_plugin = None
# 首先尝试精确匹配 applianceCode
for plugin in plugin_list:
if plugin.get("applianceCode") == sn and plugin.get("appType") == f"0x{device_type:02X}":
matched_plugin = plugin
break
# 如果没有精确匹配,使用第一个匹配 appType 的
if not matched_plugin:
for plugin in plugin_list:
if plugin.get("appType") == f"0x{device_type:02X}":
matched_plugin = plugin
break
if not matched_plugin:
MideaLogger.warning(f"No matching plugin found for device type 0x{device_type:02X}, sn: {sn}")
return None
# 下载 zip 文件
zip_url = matched_plugin.get("url")
zip_title = matched_plugin.get("title", f"plugin_0x{device_type:02X}.zip")
if not zip_url:
MideaLogger.warning(f"No download URL found for plugin: {zip_title}")
return None
try:
# 确保目录存在
os.makedirs(path, exist_ok=True)
res = await self._session.get(zip_url)
if res.status == 200:
zip_data = await res.read()
if zip_data:
fnm = f"{path}/{zip_title}"
async with aiofiles.open(fnm, "wb") as fp:
await fp.write(zip_data)
MideaLogger.info(f"Downloaded plugin file: {fnm}")
else:
MideaLogger.warning(f"Downloaded zip file is empty: {zip_url}")
else:
MideaLogger.warning(f"Failed to download plugin, status: {res.status}, url: {zip_url}")
except Exception as e:
MideaLogger.error(f"Error downloading plugin: {e}")
traceback.print_exc()
return fnm
class MSmartHomeCloud(MideaCloud): class MSmartHomeCloud(MideaCloud):
APP_ID = "1010" APP_ID = "1010"
SRC = "10" SRC = "10"

View File

@@ -7,6 +7,7 @@ class MideaLogType(IntEnum):
DEBUG = 1 DEBUG = 1
WARN = 2 WARN = 2
ERROR = 3 ERROR = 3
INFO = 4
class MideaLogger: class MideaLogger:
@@ -18,6 +19,8 @@ class MideaLogger:
log = f"[{device_id}] {log}" log = f"[{device_id}] {log}"
if log_type == MideaLogType.DEBUG: if log_type == MideaLogType.DEBUG:
logging.getLogger(mod.__name__).debug(log) logging.getLogger(mod.__name__).debug(log)
elif log_type == MideaLogType.INFO:
logging.getLogger(mod.__name__).info(log)
elif log_type == MideaLogType.WARN: elif log_type == MideaLogType.WARN:
logging.getLogger(mod.__name__).warning(log) logging.getLogger(mod.__name__).warning(log)
elif log_type == MideaLogType.ERROR: elif log_type == MideaLogType.ERROR:
@@ -27,6 +30,10 @@ class MideaLogger:
def debug(log, device_id=None): def debug(log, device_id=None):
MideaLogger._log(MideaLogType.DEBUG, log, device_id) MideaLogger._log(MideaLogType.DEBUG, log, device_id)
@staticmethod
def info(log, device_id=None):
MideaLogger._log(MideaLogType.INFO, log, device_id)
@staticmethod @staticmethod
def warning(log, device_id=None): def warning(log, device_id=None):
MideaLogger._log(MideaLogType.WARN, log, device_id) MideaLogger._log(MideaLogType.WARN, log, device_id)