Skip to content

Commit

Permalink
feat(cloud): add plugin download for meiju and smarthome (#336)
Browse files Browse the repository at this point in the history
add plug download for meiju and smarthome cloud
  • Loading branch information
wuwentao authored Dec 23, 2024
1 parent 0c517fd commit 30de473
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 16 deletions.
8 changes: 7 additions & 1 deletion midealocal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from midealocal.cloud import (
SUPPORTED_CLOUDS,
MideaCloud,
get_default_cloud,
get_midea_cloud,
get_preset_account_cloud,
)
Expand Down Expand Up @@ -55,9 +56,10 @@ async def _get_cloud(self) -> MideaCloud:
or not self.namespace.password
):
default_cloud = get_preset_account_cloud()
default_cloud_name = get_default_cloud()
_LOGGER.info("Using preset account.")
return get_midea_cloud(
cloud_name=default_cloud["cloud_name"],
cloud_name=default_cloud_name,
session=self.session,
account=default_cloud["username"],
password=default_cloud["password"],
Expand Down Expand Up @@ -195,6 +197,10 @@ async def download(self) -> None:
lua = await cloud.download_lua(str(Path()), device_type, device_sn, model)
_LOGGER.info("Downloaded lua file: %s", lua)

_LOGGER.debug("Download plugin file for %s [%s]", device_sn, hex(device_type))
plugin = await cloud.download_plugin(str(Path()), device_type, device_sn)
_LOGGER.info("Downloaded plugin file: %s", plugin)

async def set_attribute(self) -> None:
"""Set attribute for device."""
device_list = await self.discover()
Expand Down
131 changes: 116 additions & 15 deletions midealocal/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ async def _api_request(
)
raw = await r.read()
_LOGGER.debug(
"Midea cloud API url: %s, data: %s, response: %s",
"Midea cloud API url: %s, \n data: %s, \n response: %s",
url,
_redact_data(str(data)),
_redact_data(str(raw)),
Expand Down Expand Up @@ -306,6 +306,15 @@ async def download_lua(
"""Download lua integration."""
raise NotImplementedError

async def download_plugin(
self,
path: str,
device_type: int,
sn: str,
) -> str | None:
"""Download lua integration."""
raise NotImplementedError


class MeijuCloud(MideaCloud):
"""Meiju Cloud."""
Expand Down Expand Up @@ -333,6 +342,20 @@ def __init__(
api_url=cloud_data["api_url"],
)

def _make_general_data(self) -> dict[str, Any]:
return {
"src": self._app_id,
"format": "2",
"stamp": datetime.now(tz=UTC).strftime("%Y%m%d%H%M%S"),
"platformId": "1",
"deviceId": self._device_id,
"reqId": token_hex(16),
"uid": self._uid,
"clientType": "1",
"appId": self._app_id,
"language": "en_US",
}

async def login(self) -> bool:
"""Authenticate to Meiju Cloud."""
if login_id := await self._get_login_id():
Expand Down Expand Up @@ -539,6 +562,46 @@ async def download_lua(
await fp.write(stream)
return str(fnm) if fnm else None

async def download_plugin(
self,
path: str,
device_type: int,
sn: str,
) -> str | None:
"""Download lua integration."""
data = self._make_general_data()
data.update(
{
"clientVersion": "201",
"match": "1",
"applianceList": [
{
"appModel": sn[9:17],
"appType": hex(device_type),
"modelNumber": "0",
},
],
},
)
fnm = None
if response := await self._api_request(
endpoint="/v1/plugin/update/getplugin",
data=data,
):
# get file name from url
_LOGGER.debug("response: %s, type: %s", response, type(response))
file_name = response["list"][0]["url"].split("/")[-1]
# download plugin from url
res = await self._session.get(response["list"][0]["url"])
if res.status == HTTPStatus.OK:
# get the file content in binary mode
plugin = await res.read()
if plugin:
fnm = f"{path}/{file_name}"
async with aiofiles.open(fnm, "wb") as fp:
await fp.write(plugin)
return str(fnm) if fnm else None


class SmartHomeCloud(MideaCloud):
"""MSmart Home Cloud."""
Expand Down Expand Up @@ -582,6 +645,7 @@ def _make_general_data(self) -> dict[str, Any]:
"uid": self._uid,
"clientType": "1",
"appId": self._app_id,
"language": "en_US",
}

async def _api_request(
Expand Down Expand Up @@ -699,20 +763,18 @@ async def download_lua(
manufacturer_code: str = "0000",
) -> str | None:
"""Download lua integration."""
data = {
"clientType": "1",
"appId": self._app_id,
"format": "2",
"deviceId": self._device_id,
"iotAppId": self._app_id,
"applianceMFCode": manufacturer_code,
"applianceType": hex(device_type),
"applianceSn": self._security.aes_encrypt_with_fixed_key(
sn.encode("ascii"),
).hex(),
"version": "0",
"encryptedType ": "2",
}
data = self._make_general_data()
data.update(
{
"applianceMFCode": manufacturer_code,
"applianceType": hex(device_type),
"applianceSn": self._security.aes_encrypt_with_fixed_key(
sn.encode("ascii"),
).hex(),
"version": "0",
"encryptedType ": "2",
},
)
if model_number is not None:
data["modelNumber"] = model_number
fnm = None
Expand All @@ -734,6 +796,45 @@ async def download_lua(
await fp.write(stream)
return str(fnm) if fnm else None

async def download_plugin(
self,
path: str,
device_type: int,
sn: str,
) -> str | None:
"""Download lua integration."""
data = self._make_general_data()
data.update(
{
"clientVersion": "0",
"applianceList": [
{
"appModel": sn[9:17],
"appType": hex(device_type),
"modelNumber": "0",
},
],
},
)
fnm = None
if response := await self._api_request(
endpoint="/v1/plugin/update/overseas/get",
data=data,
):
# get file name from url
_LOGGER.debug("response: %s, type: %s", response, type(response))
file_name = response["result"][0]["url"].split("/")[-1]
# download plugin from url
res = await self._session.get(response["result"][0]["url"])
if res.status == HTTPStatus.OK:
# get the file content in binary mode
plugin = await res.read()
if plugin:
fnm = f"{path}/{file_name}"
async with aiofiles.open(fnm, "wb") as fp:
await fp.write(plugin)
return str(fnm) if fnm else None


class MideaAirCloud(MideaCloud):
"""Midea Air Cloud."""
Expand Down

0 comments on commit 30de473

Please sign in to comment.