mirror of
https://github.com/sususweet/midea-meiju-codec.git
synced 2025-12-17 09:55:51 +00:00
feat: add plugin download for devices.
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -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`文件,等待适配就可以了。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
@@ -177,6 +178,18 @@ class MideaCloud:
|
|||||||
):
|
):
|
||||||
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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user