Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloud): add plugin download for meiju and smarthome #336

Merged
merged 1 commit into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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