feat: add support for MSmartHome App.

This commit is contained in:
sususweet
2025-11-19 01:48:11 +08:00
parent 5369e6c13d
commit 6062b0534f
4 changed files with 127 additions and 92 deletions

View File

@@ -162,28 +162,6 @@ class MideaCloud:
async def login(self) -> bool:
raise NotImplementedError()
async def send_cloud(self, appliance_id: int, data: bytearray):
appliance_code = str(appliance_id)
params = {
'applianceCode': appliance_code,
'order': self._security.aes_encrypt(bytes_to_dec_string(data)).hex(),
'timestamp': 'true',
"isFull": "false"
}
if response := await self._api_request(
endpoint='/v1/appliance/transparent/send',
data=params,
):
if response and response.get('reply'):
_LOGGER.debug("[%s] Cloud command response: %s", appliance_code, response)
reply_data = self._security.aes_decrypt(bytes.fromhex(response['reply']))
return reply_data
else:
_LOGGER.warning("[%s] Cloud command failed: %s", appliance_code, response)
return None
async def list_home(self) -> dict | None:
return {1: "My home"}
@@ -198,10 +176,6 @@ class MideaCloud:
manufacturer_code: str = "0000",
):
raise NotImplementedError()
async def send_device_control(self, appliance_code: int, control: dict, status: dict | None = None) -> bool:
"""Send control to a device via cloud. Subclasses should implement if supported."""
raise NotImplementedError()
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."""
@@ -323,6 +297,28 @@ class MeijuCloud(MideaCloud):
return appliances
return None
async def send_cloud(self, appliance_id: int, data: bytearray):
appliance_code = str(appliance_id)
params = {
'applianceCode': appliance_code,
'order': self._security.aes_encrypt(bytes_to_dec_string(data)).hex(),
'timestamp': 'true',
"isFull": "false"
}
if response := await self._api_request(
endpoint='/v1/appliance/transparent/send',
data=params,
):
if response and response.get('reply'):
_LOGGER.debug("[%s] Cloud command response: %s", appliance_code, response)
reply_data = self._security.aes_decrypt(bytes.fromhex(response['reply']))
return reply_data
else:
_LOGGER.warning("[%s] Cloud command failed: %s", appliance_code, response)
return None
async def get_device_status(self, appliance_code: int, query: dict) -> dict | None:
data = {
"applianceCode": str(appliance_code),
@@ -637,6 +633,32 @@ class MSmartHomeCloud(MideaCloud):
fp.write(stream)
return fnm
async def send_cloud(self, appliance_code: int, data: bytearray):
appliance_code = str(appliance_code)
params = {
"clientType": "1",
"appId": self.APP_ID,
"format": "2",
"deviceId": self._device_id,
"applianceCode": appliance_code,
'order': self._security.aes_encrypt(bytes_to_dec_string(data)).hex(),
'timestamp': 'true',
"isFull": "false"
}
if response := await self._api_request(
endpoint='/v1/appliance/transparent/send',
data=params,
):
if response and response.get('reply'):
_LOGGER.debug("[%s] Cloud command response: %s", appliance_code, response)
reply_data = self._security.aes_decrypt(bytes.fromhex(response['reply']))
return reply_data
else:
_LOGGER.warning("[%s] Cloud command failed: %s", appliance_code, response)
return None
async def get_device_status(
self,
appliance_code: int,
@@ -671,9 +693,29 @@ class MSmartHomeCloud(MideaCloud):
return response
return None
async def send_device_control(self, appliance_code: int, control: dict, status: dict | None = None) -> bool:
async def send_device_control(
self,
appliance_code: int,
device_type: int,
sn: str,
model_number: str | None,
manufacturer_code: str = "0000",
control: dict | None = None,
status: dict | None = None
) -> bool:
data = {
"applianceCode": str(appliance_code),
"clientType": "1",
"appId": self.APP_ID,
"format": "2",
"deviceId": self._device_id,
"iotAppId": self.APP_ID,
"applianceMFCode": manufacturer_code,
"applianceType": "0x%02X" % device_type,
"modelNumber": model_number,
"applianceSn": self._security.aes_encrypt_with_fixed_key(sn.encode("ascii")).hex(),
"version": "0",
"encryptedType ": "2",
"applianceCode": appliance_code,
"command": {
"control": control
}

View File

@@ -3,7 +3,7 @@ import socket
import traceback
from enum import IntEnum
from .cloud import MideaCloud, MSmartHomeCloud
from .cloud import MideaCloud, MSmartHomeCloud, MeijuCloud
from .security import LocalSecurity, MSGTYPE_HANDSHAKE_REQUEST, MSGTYPE_ENCRYPTED_REQUEST
from .packet_builder import PacketBuilder
from .message import MessageQuestCustom
@@ -180,7 +180,17 @@ class MiedaDevice(threading.Thread):
cloud = self._cloud
if cloud and hasattr(cloud, "send_device_control"):
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
if isinstance(cloud, MSmartHomeCloud):
await cloud.send_device_control(
appliance_code=self._device_id,
device_type=self.device_type,
sn=self.sn,
model_number=self.subtype,
manufacturer_code=self._manufacturer_code,
control=nested_status,
status=self._attributes)
elif isinstance(cloud, MeijuCloud):
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
async def set_attributes(self, attributes):
new_status = {}
@@ -207,7 +217,17 @@ class MiedaDevice(threading.Thread):
cloud = self._cloud
if cloud and hasattr(cloud, "send_device_control"):
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
if isinstance(cloud, MSmartHomeCloud):
await cloud.send_device_control(
appliance_code=self._device_id,
device_type=self.device_type,
sn=self.sn,
model_number=self.subtype,
manufacturer_code=self._manufacturer_code,
control=nested_status,
status=self._attributes)
elif isinstance(cloud, MeijuCloud):
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
def set_ip_address(self, ip_address):
MideaLogger.debug(f"Update IP address to {ip_address}")
@@ -271,6 +291,14 @@ class MiedaDevice(threading.Thread):
async def refresh_status(self):
for query in self._queries:
try:
if self._lua_runtime is not None:
if query_cmd := self._lua_runtime.build_query(query):
await self._build_send(query_cmd)
return
except Exception as e:
traceback.print_exc()
cloud = self._cloud
if cloud and hasattr(cloud, "get_device_status"):
if isinstance(cloud, MSmartHomeCloud):
@@ -282,21 +310,13 @@ class MiedaDevice(threading.Thread):
manufacturer_code=self._manufacturer_code,
query=query
)
else:
if self._lua_runtime is not None:
if query_cmd := self._lua_runtime.build_query(query):
try:
await self._build_send(query_cmd)
return
except Exception as e:
traceback.print_exc()
self._parse_cloud_message(status)
elif isinstance(cloud, MeijuCloud):
status = await cloud.get_device_status(
appliance_code=self._device_id,
query=query
)
self._parse_cloud_message(status)
self._parse_cloud_message(status)
def _parse_cloud_message(self, status):
# MideaLogger.debug(f"Received: {decrypted}")
@@ -395,7 +415,13 @@ class MiedaDevice(threading.Thread):
return ParseMessageResult.SUCCESS
async def _send_message(self, data):
if reply := await self._cloud.send_cloud(self._device_id, data):
reply = None
if isinstance(self._cloud, MSmartHomeCloud):
reply = await self._cloud.send_cloud(self._device_id, data)
elif isinstance(self._cloud, MeijuCloud):
reply = await self._cloud.send_cloud(self._device_id, data)
if reply is not None:
if reply_dec := self._lua_runtime.decode_status(dec_string_to_bytes(reply).hex()):
MideaLogger.debug(f"Decoded: {reply_dec}")
result = self._parse_cloud_message(reply_dec)