fix: cloud api loop error when device type not found.

This commit is contained in:
sususweet
2025-09-24 16:32:23 +08:00
parent a9e2b784b5
commit 2f88658fda
3 changed files with 157 additions and 172 deletions

View File

@@ -1,8 +1,6 @@
import os import os
import base64 import base64
import voluptuous as vol
from importlib import import_module from importlib import import_module
from functools import partial
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.util.json import load_json from homeassistant.util.json import load_json
@@ -14,7 +12,6 @@ from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.core import ( from homeassistant.core import (
HomeAssistant, HomeAssistant,
ServiceCall
) )
from homeassistant.const import ( from homeassistant.const import (
Platform, Platform,
@@ -97,25 +94,21 @@ async def load_device_config(hass: HomeAssistant, device_type, sn8):
if any(k in raw for k in ["entities", "centralized", "queries", "manufacturer"]): if any(k in raw for k in ["entities", "centralized", "queries", "manufacturer"]):
json_data = raw json_data = raw
if not json_data: if not json_data:
# 使用绝对路径并在执行器中导入,避免事件循环阻塞与相对导入触发包初始化循环 device_path = f".device_mapping.{'T0x%02X' % device_type}"
module_path = f"{__package__}.device_mapping.T0x{device_type:02X}"
try: try:
mapping_module = await hass.async_add_executor_job(partial(import_module, module_path)) mapping_module = import_module(device_path, __package__)
MideaLogger.warning(f"device_path: % {device_path}")
MideaLogger.warning(f"mapping_module.DEVICE_MAPPING: % {mapping_module.DEVICE_MAPPING}")
if sn8 in mapping_module.DEVICE_MAPPING.keys():
json_data = mapping_module.DEVICE_MAPPING[sn8]
elif "default" in mapping_module.DEVICE_MAPPING:
json_data = mapping_module.DEVICE_MAPPING["default"]
MideaLogger.warning(f"json_data: % {json_data}")
except ModuleNotFoundError: except ModuleNotFoundError:
mapping_module = None MideaLogger.warning(f"Can't load mapping file for type {'T0x%02X' % device_type}")
MideaLogger.warning(f"Mapping module not found: {module_path}")
except Exception as e:
mapping_module = None
MideaLogger.warning(f"Import mapping module failed: {module_path}, err={e}")
if mapping_module and hasattr(mapping_module, "DEVICE_MAPPING"):
dm = getattr(mapping_module, "DEVICE_MAPPING") or {}
if sn8 in dm.keys():
json_data = dm.get(sn8) or {}
elif "default" in dm:
json_data = dm.get("default") or {}
if len(json_data) > 0:
save_data = {sn8: json_data} save_data = {sn8: json_data}
# offload save_json as well
await hass.async_add_executor_job(save_json, config_file, save_data) await hass.async_add_executor_job(save_json, config_file, save_data)
return json_data return json_data
@@ -172,25 +165,18 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
return False return False
# 拉取家庭与设备列表 # 拉取家庭与设备列表
appliances = None
first_home_id = None
try: try:
homes = await cloud.list_home() homes = await cloud.list_home()
if homes and len(homes) > 0: if homes and len(homes) > 0:
first_home_id = list(homes.keys())[0]
appliances = await cloud.list_appliances(first_home_id)
else:
appliances = await cloud.list_appliances(None)
except Exception as e:
MideaLogger.error(f"Fetch appliances failed: {e}")
appliances = None
if appliances is None:
appliances = {}
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN].setdefault("accounts", {}) hass.data[DOMAIN].setdefault("accounts", {})
bucket = {"device_list": {}, "coordinator_map": {}, "cloud": cloud, "home_id": first_home_id} bucket = {"device_list": {}, "coordinator_map": {}}
home_ids = list(homes.keys())
for home_id in home_ids:
appliances = await cloud.list_appliances(home_id)
if appliances is None:
continue
# 为每台设备构建占位设备与协调器(不连接本地) # 为每台设备构建占位设备与协调器(不连接本地)
for appliance_code, info in appliances.items(): for appliance_code, info in appliances.items():
@@ -298,15 +284,17 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
except Exception: except Exception:
pass pass
coordinator = MideaDataUpdateCoordinator(hass, config_entry, device) coordinator = MideaDataUpdateCoordinator(hass, config_entry, device, cloud=cloud)
await coordinator.async_config_entry_first_refresh() # 后台刷新,避免初始化阻塞
hass.async_create_task(coordinator.async_config_entry_first_refresh())
bucket["device_list"][appliance_code] = info bucket["device_list"][appliance_code] = info
bucket["coordinator_map"][appliance_code] = coordinator bucket["coordinator_map"][appliance_code] = coordinator
except Exception as e: except Exception as e:
MideaLogger.error(f"Init device failed: {appliance_code}, error: {e}") MideaLogger.error(f"Init device failed: {appliance_code}, error: {e}")
hass.data[DOMAIN]["accounts"][config_entry.entry_id] = bucket hass.data[DOMAIN]["accounts"][config_entry.entry_id] = bucket
except Exception as e:
MideaLogger.error(f"Fetch appliances failed: {e}")
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True return True

View File

@@ -3,9 +3,9 @@ import time
import datetime import datetime
import json import json
import base64 import base64
from threading import Lock
from aiohttp import ClientSession from aiohttp import ClientSession
from secrets import token_hex from secrets import token_hex
from .logger import MideaLogger
from .security import CloudSecurity, MeijuCloudSecurity, MSmartCloudSecurity from .security import CloudSecurity, MeijuCloudSecurity, MSmartCloudSecurity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -50,7 +50,6 @@ class MideaCloud:
self._device_id = CloudSecurity.get_deviceid(account) self._device_id = CloudSecurity.get_deviceid(account)
self._session = session self._session = session
self._security = security self._security = security
self._api_lock = Lock()
self._app_key = app_key self._app_key = app_key
self._account = account self._account = account
self._password = password self._password = password
@@ -86,16 +85,15 @@ class MideaCloud:
"accesstoken": self._access_token "accesstoken": self._access_token
}) })
response:dict = {"code": -1} response:dict = {"code": -1}
for i in range(0, 3): _LOGGER.debug(f"Midea cloud API header: {header}")
_LOGGER.debug(f"Midea cloud API dump_data: {dump_data}")
try: try:
with self._api_lock: r = await self._session.request("POST", url, headers=header, data=dump_data, timeout=5)
r = await self._session.request("POST", url, headers=header, data=dump_data, timeout=10)
raw = await r.read() raw = await r.read()
_LOGGER.debug(f"Midea cloud API url: {url}, data: {data}, response: {raw}") _LOGGER.debug(f"Midea cloud API url: {url}, data: {data}, response: {raw}")
response = json.loads(raw) response = json.loads(raw)
break
except Exception as e: except Exception as e:
pass _LOGGER.debug(f"API request attempt failed: {e}")
if int(response["code"]) == 0 and "data" in response: if int(response["code"]) == 0 and "data" in response:
return response["data"] return response["data"]
@@ -267,9 +265,10 @@ class MeijuCloud(MideaCloud):
data = { data = {
"applianceCode": str(appliance_code), "applianceCode": str(appliance_code),
"command": { "command": {
"query": {"query_type": "total_query"} "query": {}
} }
} }
MideaLogger.error(f"get_device_status: {data}")
if response := await self._api_request( if response := await self._api_request(
endpoint="/mjl/v1/device/status/lua/get", endpoint="/mjl/v1/device/status/lua/get",
data=data data=data

View File

@@ -10,7 +10,6 @@ from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .core.device import MiedaDevice from .core.device import MiedaDevice
from .const import DOMAIN
from .core.logger import MideaLogger from .core.logger import MideaLogger
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -31,6 +30,7 @@ class MideaDataUpdateCoordinator(DataUpdateCoordinator[MideaDeviceData]):
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ConfigEntry,
device: MiedaDevice, device: MiedaDevice,
cloud=None,
) -> None: ) -> None:
"""Initialize the coordinator.""" """Initialize the coordinator."""
super().__init__( super().__init__(
@@ -45,6 +45,7 @@ class MideaDataUpdateCoordinator(DataUpdateCoordinator[MideaDeviceData]):
self.device = device self.device = device
self.state_update_muted: CALLBACK_TYPE | None = None self.state_update_muted: CALLBACK_TYPE | None = None
self._device_id = device.device_id self._device_id = device.device_id
self._cloud = cloud
async def _async_setup(self) -> None: async def _async_setup(self) -> None:
"""Set up the coordinator.""" """Set up the coordinator."""
@@ -89,9 +90,8 @@ class MideaDataUpdateCoordinator(DataUpdateCoordinator[MideaDeviceData]):
return self.data return self.data
try: try:
# 尝试账号模式下的云端轮询(如果 cloud 存在且支持 # 使用传入的 cloud 实例(若可用
account_bucket = self.hass.data.get(DOMAIN, {}).get("accounts", {}).get(self.config_entry.entry_id) cloud = self._cloud
cloud = account_bucket.get("cloud") if account_bucket else None
if cloud and hasattr(cloud, "get_device_status"): if cloud and hasattr(cloud, "get_device_status"):
try: try:
status = await cloud.get_device_status(self._device_id) status = await cloud.get_device_status(self._device_id)
@@ -120,8 +120,7 @@ class MideaDataUpdateCoordinator(DataUpdateCoordinator[MideaDeviceData]):
async def async_set_attribute(self, attribute: str, value) -> None: async def async_set_attribute(self, attribute: str, value) -> None:
"""Set a device attribute.""" """Set a device attribute."""
# 云端控制:构造 control 与 status携带当前状态作为上下文 # 云端控制:构造 control 与 status携带当前状态作为上下文
account_bucket = self.hass.data.get(DOMAIN, {}).get("accounts", {}).get(self.config_entry.entry_id) cloud = self._cloud
cloud = account_bucket.get("cloud") if account_bucket else None
control = {attribute: value} control = {attribute: value}
status = dict(self.device.attributes) status = dict(self.device.attributes)
if cloud and hasattr(cloud, "send_device_control"): if cloud and hasattr(cloud, "send_device_control"):
@@ -134,8 +133,7 @@ class MideaDataUpdateCoordinator(DataUpdateCoordinator[MideaDeviceData]):
async def async_set_attributes(self, attributes: dict) -> None: async def async_set_attributes(self, attributes: dict) -> None:
"""Set multiple device attributes.""" """Set multiple device attributes."""
account_bucket = self.hass.data.get(DOMAIN, {}).get("accounts", {}).get(self.config_entry.entry_id) cloud = self._cloud
cloud = account_bucket.get("cloud") if account_bucket else None
control = dict(attributes) control = dict(attributes)
status = dict(self.device.attributes) status = dict(self.device.attributes)
if cloud and hasattr(cloud, "send_device_control"): if cloud and hasattr(cloud, "send_device_control"):