中文 English

点了个“更新”,全屋三台家电全变砖?Home Assistant 升级 Midea AC LAN 的6分钟卡死惊魂与底层 Deps 离线自愈实录

发布时间: 2026-10-26 · 阅读量 --
homeassistant HA 智能家居 自动化 Automation 故障排查 Troubleshooting 自托管 Self-hosting Midea 美的 Python

智能家居最让人心惊肉跳的瞬间,不是买来的新硬件配不上网,而是一个已经稳定运行了大半年的核心插件,在 HACS 里弹出了一个鲜红的更新提示。你顺手点了一下「更新」,重启系统后,不仅设备全部失联,连整个智能家居中枢都在启动过程中僵死在半路上。

2026 年 10 月 26 日,我针对家里 Home Assistant 系统中的 Midea AC LAN 集成(负责接管全屋的美的滚筒洗衣机、洗碗机与燃气热水器)做了一次日常版本升级升级评估与实操。从 v2026.9.1 升级到 v2026.10.0,看似只是一个小版本迭代,背后却险象环生:启动流程足足卡死超过 6 分钟、三台核心大件全部显示「无法加载」、控制实体属性大面积变动,以及境内弱网环境下 Python 包管理器的致命缺陷。

这篇文章将完整复盘这场历时近两小时的排障与架构深潜:不仅有详尽的现象分析、源码考古与生活化比方,更有经过生产环境真实验证的零侵入离线自愈方案,以及支持 Windows 11、Ubuntu 26.04 和 macOS 27 的一键自动化脚本。

原创题图:从 6 分钟开机卡死到秒级自愈,Home Assistant 离线依赖注入实录

图 1:本文原创题图。展示了本次升级涉及的三台核心大家电、启动阶段的 Stage 2 阻塞超时,以及最终通过规范目录实现的零侵入离线挂载。

一、问题背景:承载全家生活的局域网中枢

在智能家居架构中,白电(大家电)的接入地位极为特殊。温湿度计或人体传感器偶尔掉线,顶多是灯光联动稍慢一秒;但洗衣机、洗碗机和热水器,是直接与水路、强电和燃气打交道的重型设备。

我的 Home Assistant 部署在局域网专用的独立服务器上(运行最新的 Home Assistant OS,Core 版本为 2026.10.4,底层 Python 运行时为 3.14)。为了彻底摆脱厂商云端断网、服务器维护或延迟过高的限制,家里的美的设备全部通过经典的开源第三方集成 midea_ac_lan 走局域网纯本地协议交互:

设备名称 硬件型号 / 协议识别码 协议版本 接管控制与传感器职能
美的滚筒洗衣机 38111463 / Type 0xDB V2 局域网协议 运行进度、洗涤程序、水温档位、脱水转速、剩余时间
美的智能洗碗机 H5 000000H5 / Type 0xE1 V3 局域网协议 门状态、漂洗剂与软水盐告警、洗涤模式、童锁、电源
美的燃气热水器 5110188E / Type 0xE3 V3 局域网协议 出水水温、燃烧状态火焰感应、零冷水循环、运行保护

这三台设备总共衍生出 34 个实体,长期嵌入在 Lovelace 首页和自动化大屏中。

当天下午,HACS(Home Assistant Community Store)弹出了 Midea AC LAN 的新版本提醒:从本地运行的 v2026.9.1 推送至 v2026.10.0。作者在 Release 说明中提到了多项稳定性增强、对新设备类型的支持以及优雅停机时的连接断开修复。

从表面上看,这是一个非常常规的小版本更新。然而,当升级指令下发后,平静的家庭局域网瞬间被打破。

真实截图:Home Assistant 集成列表中的 Midea AC LAN 正常加载,接管全屋 3 台核心大家电

图 2:真实系统截图。Home Assistant 集成管理看板中正常工作的 Midea AC LAN 模块。它同时管理着滚筒洗衣机、洗碗机和燃气热水器。

二、问题表现:漫长的 6 分钟卡死与全线瘫痪

按照通常习惯,如果在 HACS 界面直接点击「更新」并重启,接下来会遭遇令人绝望的连锁灾难。

在测试环境中,我们先通过隔离工具将新版集成文件写入 custom_components/midea_ac_lan/,然后下发了重启命令。通常情况下,这台高性能主机的 Home Assistant Core 重启只需要 15 到 20 秒。但这一次,倒计时在浏览器界面上转了一圈又一圈,整整 5 分钟过去了,Web 控制台依然毫无响应。

通过底层宿主机抓取实时日志,我们看到了令人震惊的场景:

  1. 启动流程被硬生生阻断:系统卡在 Stage 2 初始化阶段,日志中反复刷出 [homeassistant.bootstrap] Waiting on tasks: {<Task pending name=‘setup component midea_ac_lan’…>}。
  2. 触发超时强行跳过:足足等待了 360 秒(6 分钟)后,Home Assistant Core 的看门狗计时器终于忍无可忍,抛出警告:Something is blocking Home Assistant from wrapping up the start up phase. We’re going to continue anyway.。
  3. 全线加载失败:虽然整个中枢终于勉强挤进了运行态,但打开「设置 → 设备与集成」,Midea AC LAN 旗下的三个条目(洗衣机、洗碗机、热水器)全部呈现暗红色的 not_loaded(未加载)状态!
  4. 核心功能暴毙:在设备调试面板中,新版代码抛出了致命异常:TypeError: device_selector() got an unexpected keyword argument ‘sn8’。

为什么更新一个家电集成,不仅让设备全废,还会导致整个 Home Assistant 启动过程像被水泥浇筑了一样卡死 6 分钟?

三、生动比方:开学典礼上的慢速快递员

为了让即使没有软件工程背景的读者、甚至家里上小学的小朋友也能秒懂这里面的玄机,我们可以把 Home Assistant 的启动过程比作一场「全校开学典礼」。

原创比方图:开学典礼与课桌专用书袋的生活化对比

图 3:本文原创示意图。用生活中的开学典礼、校门口安检与课桌专用书袋,通俗比拟依赖拉取卡死、假离线挂载与真实解决路径。

1. 现象:开学典礼被迫暂停 6 分钟

想象一下,开学第一天早上,全校师生(灯光、网关、传感器、面板)都站在操场上,校长准备宣布典礼开始(系统进入 RUNNING 状态)。

这时负责考勤的主任(Core 启动器)拿起一张装备清单(插件的 manifest.json),走到「小美同学」(Midea 集成)面前,发现清单上写着:「今天上课必须携带 2026 最新版的美的说明书(midea-lan==2026.10.0)」。

主任一看小美书包里没有这本书,便立即派出一个快递员去外省总仓库(PyPI 镜像源 files.pythonhosted.org)去取。

偏偏那天高速公路上大堵车,网络信号极其恶劣,快递员骑着一辆破旧的自行车,一秒钟只能往前挪几厘米(下载速度只有可怜的 10 KB/s)。更要命的是,学校规定:开学典礼必须所有人装备齐全才能宣布开始!

于是,全操场几千名师生在凄风冷雨中足足干等了 6 分钟!直到校长实在冻得受不了了,才吹哨强行宣布开幕,但由于小美同学终究没拿到书,被当场罚站(集成标记为 not_loaded),今天的所有课都无法上了。

2. 误区一:把书扔在校门口地上(/config 假离线)

有人可能会问:我提前把这本说明书从别的地方下载好,扔在学校大门口的过道上(即把 midealan 放在 /config/ 根目录),不就不用派快递员了吗?

这就引出了后面我们要深剖的代码假象:学校的保洁门卫大爷只有在引导客人进校门的一瞬间(import custom_components)会低头看一下大门口;等真正上课检查装备时,大爷根本不看门口地上堆的杂物!所以你扔在门外的书,老师根本不知道,依然会派快递员去外省跑腿。

3. 误区二:带了一张光盘,安检员非要打电话国际长途(file:// 轮子陷阱)

又有人想:那我给老师带一张存了说明书的光盘(本地 file://…whl)行不行?

不行!因为学校的装备安检员有个不可理喻的强迫症规定(执行 uv pip install 时被强制追加了 –upgrade 升级参数)。安检员拿到你的光盘后,非要拿起长途电话,向跨国出版总部重新核对一遍:这本书里提到的标点符号有没有更新的版本?结果电话线又被卡死在越洋慢速线路上了。

4. 黄金正解:课桌里预留的专用暗格(/config/deps 目录)

那怎样才能既不打电话、又不派快递员呢?

深入翻阅学校的百年规章(Home Assistant Core 官方启动代码)后,我们发现:校长早在每个学生的课桌最深处,设计了一个专用的「免检暗格」(路径为 /config/deps/lib/python3.14/site-packages/)。

只要你提前把印有官方防伪钢印(.dist-info)的正版说明书整整齐齐放进这个暗格里,老师一进门伸手一摸,发现书已经在暗格中躺得好好的,便当场打勾放行!开学典礼 1 秒钟顺利举行,全校皆大欢喜。

四、问题深度分析:双重陷阱与代码考古

回到严肃的技术细节。导致这次升级灾难的,实际上是两记环环相扣的暗箭:

现场日志截图:HA Core 启动被 setup component midea_ac_lan 阻塞长达 6 分钟

图 4:真实运行环境日志快照。清清楚楚记录了 homeassistant.bootstrap 在 Stage 2 中因等待 setup component midea_ac_lan 耗尽 360 秒超时的全过程。

1. 第一重陷阱:版本参数断崖式不兼容

Midea AC LAN 本身只是一个对接 Home Assistant 的外壳界面,真正解析美的电器的网络二进制报文、加解密通信密钥(Token/Key)以及匹配设备型号协议的核心,是一个名为 midea-lan 的独立 Python 伴生库。

在旧版本 v2026.9.1 中,集成依赖的是 midea-lan 2026.9.1;而在新版 v2026.10.0 中,集成代码重构了设备选择器,调用方式变为:

device_selector(
    device_type,
    device_id,
    ip_address,
    port,
    token,
    key,
    protocol,
    model,
    sn8,                 # 新增参数
    manufacturer_code,   # 新增参数
    cloud_name           # 新增参数
)

然而,旧版的 midea-lan 2026.9.1 函数定义里根本没有这三个新参数!

如果系统在升级集成时,未能同步将底层的 midea-lan 库替换为 2026.10.0,那么任何一台美的设备在执行 async_setup_entry 时,都会瞬间抛出 TypeError 异常,直接崩溃。

2. 第二重陷阱:假离线挂载与 sys.path 的障眼法

你可能会好奇,既然依赖库这么重要,之前系统难道一直是在线联网下载的吗?

这里就隐藏着许多智能家居老玩家和维护脚本都会踩中的「认知误区」。在之前的运维记录中,曾有人将 midea-lan 2026.9.1 手动解压放到了配置根目录 /config/midealan/ 下,误以为这样就能实现「离线 Vendoring」。

但通过分析 Home Assistant 2026.10 的核心加载源码 homeassistant/loader.py(第 1709 至 1713 行),真相水落石出:

# homeassistant/loader.py 片段
try:
    sys.path.insert(0, config_dir)
    # 仅在此处动态 import custom_components
    integration = importlib.import_module(f"{CUSTOM_COMPONENTS}.{domain}")
finally:
    # 扫描与初次引入完成后,立刻将 config_dir 从 sys.path 彻底剔除!
    sys.path.remove(config_dir)

原来,Home Assistant 只在最开始探测自定义集成模块的瞬间,极其短暂地将 /config 目录推入内存中的 sys.path;随后立刻将其移出!

在后续的常规运行期、以及核心类库判断依赖是否满足的 is_installed() 阶段,/config 根本不在 Python 的模块搜索路径里。之前之所以能跑,完全是因为当时碰巧在容器或虚拟环境的系统全局 site-packages 里安装成功过;放在 /config 根目录下的文件,从始至终都是摆设!

原创架构图:loader.py 与 bootstrap.py 的依赖查找与加载路径解剖

图 5:本文原创架构图。深度剖析了 loader.py 的临时挂载、package.py 的强制 upgrade 网络重试,以及 bootstrap.py 中永久注入的 user-site 机制。

3. 第三重陷阱:uv pip –upgrade 的网络黑洞

Home Assistant 从 2024 年底开始,全面引入了极速的 Python 包管理工具 uv 代替传统的 pip。这本来是一项提升性能的壮举,但在国内弱网环境下,却演化成了一个噩梦。

当集成在 manifest.json 中声明了:

{
  "domain": "midea_ac_lan",
  "requirements": ["midea-lan==2026.10.0"]
}

Core 在启动时会通过 homeassistant.util.package.is_installed() 进行校验。由于上面提到的假离线失效,Core 认为这个包没有安装,于是调用:

uv pip install --upgrade midea-lan==2026.10.0

在用户的家庭网络环境下,访问海外的 files.pythonhosted.org 速度经常跌落至 10 KB/s 以下。一个 330 KB 的 Wheel 包,下载需要数分钟甚至更久,期间一旦触发 TLS 握手重试或数据包中断,uv 就会挂起重试。

更有甚者,有人尝试把 manifest 改成指向本地文件:

{
  "requirements": ["file:///config/wheels/midea_lan-2026.10.0-py3-none-any.whl"]
}

这依然会卡死!因为 Core 调用安装命令时,写死了 –upgrade 标志。根据 PEP 规范与 uv 的解析逻辑,带有 –upgrade 时,包解析器会强行连网查询该包的所有间接依赖是否在 PyPI 上有更新版本。只要宿主机无法顺畅连接外网,这个网络重试黑洞就会直接将 Home Assistant 拖入长达 6 分钟的 Stage 2 启动死锁中!

五、问题根因与破局点:寻找官方认可的生路

既然改 manifest.json 会破坏版本定义且触发联网检查,放 /config 根目录又无效,官方到底留没留下纯离线、零侵入的解决方案?

答案就藏在 Home Assistant 启动引导器 homeassistant/bootstrap.py 中。

通过查阅 2026.10.4 的源码,我们锁定了函数 async_mount_local_lib_path:

async def async_mount_local_lib_path(config_dir: str) -> str:
    """Add local library to Python Path.

    This function is a coroutine.
    """
    deps_dir = os.path.join(config_dir, "deps")
    if (lib_dir := await async_get_user_site(deps_dir)) not in sys.path:
        sys.path.insert(0, lib_dir)
    return deps_dir

这里的逻辑非常清晰而优雅:

  1. 官方保留的专用目录:在配置目录下,有一个专为本地依赖预留的 deps/ 体系。
  2. 永久置顶 sys.path[0]:系统在 Stage 0 引导的最开始,就会通过 async_get_user_site 找到对应的 Python 版本目录(例如 /config/deps/lib/python3.14/site-packages),并将其直接插入到 Python 搜索路径的最顶端(sys.path[0])!
  3. 完美阻断联网安装:在后续检查依赖时,importlib.metadata 会首先扫描 sys.path[0]。如果我们在该目录下同时放置了解压后的代码包(midealan/)和元数据分发信息包(midea_lan-2026.10.0.dist-info/),is_installed(“midea-lan==2026.10.0”) 将当场返回 True!
  4. 保持集成发布包的绝对纯净:官方 manifest.json 不需要做任何篡改,完全维持 midea-lan==2026.10.0 的官方声明。以后即使官方发布热修复或通过 Git 同步,都不会发生配置冲突。

真实目录证据:解包至 /config/deps/lib/python3.14/site-packages/ 的 midealan 与 dist-info

图 6:现场环境的目录结构快照。确认 midealan 代码目录与 .dist-info 规范放置在 Python 3.14 的 site-packages 路径下。

六、如何解决问题:从离线注入到闭环验收

理清了底层原理,解决方案的实施路径就变得清晰而极其稳健。整个实施过程严格遵循生产级变更标准,分为五个关键阶段:

阶段一:宿主机离线拉取与双重哈希校验

我们在外围工作机(能够稳定访问海外源的 Mac 电脑)上,通过高带宽通道直接下载对应的发布制品,并在本地完成 SHA256 完整性核对:

阶段二:生产环境旧资产全量备份

在进行任何写入之前,通过自动化运维探针对 Home Assistant 上的原有资产执行严格备份:

阶段三:规范化 Deps 注入与旧文件清理

  1. 目录创建:在 Home Assistant 端建立严格匹配当前 Python 运行时版本的依赖目录:/config/deps/lib/python3.14/site-packages/。
  2. 解包注入:将 Wheel 包解压,把其中的 midealan/ 代码包和 midea_lan-2026.10.0.dist-info/ 元数据包同步推送到上述目录中,并逐文件校验大小。
  3. 写入集成:将 custom_components/midea_ac_lan/ 的 32 个新版文件全量写入,并维持未经修改的官方 manifest.json。
  4. 卫生清理:彻底删除过去留在 /config 根目录下的废弃 midealan 目录及临时目录,消除一切路径干扰。

阶段四:配置校验与安全重启

在下发重启指令前,调用 Home Assistant 官方的 check_config 服务:

阶段五:设备状态机核对与实体分流治理

重启完成后,我们通过 WebSocket 接口抓取三台核心电器的加载状态:

原有的 34 个基础实体全部完好保留,历史曲线平滑延续。

真实截图:洗碗机 H5 设备详情页,展示新版状态原始值及重命名并禁用的 11 个控制实体

图 7:洗碗机设备详情页面。展示了新版规范命名并默认禁用的 11 个控制实体,以及正常工作的状态汇报。

真实截图:美的滚筒洗衣机设备详情页,实体属性与运行状态完好保留

图 8:美的滚筒洗衣机设备页面。水温、脱水速度、剩余时间和运行阶段传感器数据均精准保持。

真实截图:美的燃气热水器设备详情页,燃烧状态、水温控制与保护开关

图 9:燃气热水器详情页面。清晰展示了当前水温、燃烧状态传感器与零冷水开关联动。

此时,还有两处细微但至关重要的「新版行为变动」需要人工接管处理:

  1. 洗碗机新增 11 个实验性实体:新版集成对洗碗机默认开启了「开始、预约开始、暂停、恢复、取消」等 5 个动作按钮,以及程序选择、预约时长、自动开门等开关。由于 H5 这款硬件是否完全支持远程通过网络触发启动尚存不确定性,为了防止误触引发漏水或干烧风险,我们将这些新增实体统一重命名为 .mei_de_xi_wan_ji_ 规范格式,并在实体注册表中将其标记为 disabled_by: user(安全禁用)。后续有实际需要时,可在 UI 中单独开启。
  2. 状态值格式变更为纯小写:新版代码将洗碗机和洗衣机的原始枚举值,由以前的首字母大写改为了小写 Key(例如 Running → running,ECO Wash → eco_wash,Idle → idle,Power Off → power_off)。由于上游集成的简体中文语言包尚未补充这些状态的翻译映射,Lovelace 面板上会直接显示英文原值。如果你有基于状态文字匹配的自动化脚本,需要同步核对是否受到影响。

真实截图:HACS 成功对齐已安装记录至 v2026.10.0,消除红点通知

图 10:HACS 集成管理卡片。已成功与上游 v2026.10.0 版本对齐,移除了更新角标。

七、全平台一键离线升级工具与脚本

为了让遇到同样弱网超时困扰、或者希望在未来的版本更新中一劳永逸避坑的玩家能够轻松落地,我们开发了配套的自动化离线注入工具集。

该工具完全基于 Python 3 标准库构建,零第三方 pip 依赖,支持本地离线校验、自动探测 Python 运行时版本、自动构建 deps 目录并安全重启验证。

原创流程图:Windows 11、Ubuntu 26.04 与 macOS 27 三端自动化实施机制

图 11:本文原创流程图。涵盖了三端自动化脚本从环境探针、离线哈希防篡改、隔离解包到闭环验证的完整流水线。

代码已开源并提供打包下载:

方法 A:人工自动执行(三端一键命令行)

在本地电脑上下载解压工具包后,打开对应系统的终端,运行单条指令即可。程序会自动检查环境并在隐藏密码提示下交互式读取 Token,杜绝凭据泄漏风险:

1. Windows 11(PowerShell 7 / Windows PowerShell)

powershell -NoProfile -ExecutionPolicy Bypass -File .\run-windows11.ps1 -Apply

说明:这里的执行策略覆盖仅作用于当前的 PowerShell 单次会话,不会修改全局系统安全策略。

2. Ubuntu 26.04 LTS(Bash)

bash ./run-ubuntu2604.sh --apply

3. macOS 27(Zsh)

zsh ./run-macos27.zsh --apply

若不带 –apply 参数,工具默认只执行「只读探测」,会输出当前 Home Assistant 版本、Python 目录规范以及 Midea 实体的在线情况,绝不会擅自修改服务端的任何文件。

方法 B:Agent 自动化配置方案

如果你日常使用具备命令行调用能力的 AI Agent(如 Claude Code、OpenCode 或专用运维智能体),可以将工具包解压到工作区,并将下述任务提示词直接托付给 Agent:

请使用当前目录中的 midea_deps_toolkit.py 对我的 Home Assistant 进行 Midea AC LAN 离线依赖升级与加固。
从环境变量 HA_URL 读取中枢地址,从 HA_TOKEN 读取长期访问令牌,严禁在控制台打印令牌或完整 IP 地址。

第一步:先执行只读评估:
python3 midea_deps_toolkit.py --non-interactive

第二步:核对探测到的 Python 版本后,执行安全离线依赖解包与升级:
python3 midea_deps_toolkit.py --non-interactive --apply

任务要求:
1. 下载 Release ZIP 与 midea-lan 2026.10.0 wheel 并执行 SHA256 完整性核对;
2. 在服务端严格注入至 /config/deps/lib/python3.14/site-packages/,禁止修改 manifest.json 原文;
3. 清理 /config 根目录下的历史游离 midealan 目录;
4. 调用 check_config 服务并在合法的前提下重启 Home Assistant;
5. 重启完成后通过 API 验证洗衣机、洗碗机、热水器三个条目状态均为 loaded;
6. 检查洗碗机新增的实体,将其统一重命名并安全禁用。

真实截图:家庭控制面板中正常运转的美的家电状态与联动卡片

图 12:整理完成后正常运转的「我的家」大屏控制台。美的洗衣机、洗碗机和热水器在概览页上稳定更新。

八、常见问题 Q&A

Q1:为什么我直接在 HACS 点更新后,每次重启都要卡很久?

这是典型的 uv pip install –upgrade 弱网超时。集成在 manifest 里写了依赖要求,系统发现本地没有装,便在每次启动时尝试联网拉取。由于国内访问国外下载源极慢,看门狗计时器每次都会硬等数分钟直到超时。按照本文方法把依赖放进 /config/deps/lib/pythonX.Y/site-packages/ 后,系统直接判定依赖已满足,启动立刻恢复秒级。

Q2:未来 Home Assistant 升级大版本(比如从 Python 3.14 升到 Python 3.15)该怎么办?

deps/lib/ 下的目录名称是严格绑定 Python 大小版本的。当未来 Home Assistant Core 升级其底层的 Python 运行时小版本时,你需要把旧版本目录下的 site-packages 内容复制或迁移到对应的新版本目录(例如 deps/lib/python3.15/site-packages)中即可。本文提供的工具支持 –python-ver 参数,可以秒级完成新目录的构建。

Q3:为什么不建议把 manifest.json 改成 file:// 本地 wheel?

首先,改动第三方集成的 manifest 会导致它失去官方源码的完整性,以后 HACS 检测或者更新时会经常提示文件冲突;其次,更关键的是 Home Assistant 调用底层 uv 时附带了 –upgrade 标志,即便你指向了本地 wheel 文件,uv 依然会发起网络请求重新验证依赖树,弱网环境下依然会发生超时卡死。

Q4:面板上的洗碗机状态显示为「power_off」或「idle」,能改成中文吗?

可以。新版上游代码只翻译了实体名称,未提供状态值的多语言映射。你可以通过在 configuration.yaml 中建立简单的模板传感器(Template Sensor),使用映射字典将 power_off 转换为「已关机」、running 转换为「运行中」、eco_wash 转换为「节能洗」,并在前端 Lovelace 卡片上引用该模板传感器,即可恢复原汁原味的优雅中文显示。

Q5:这套方案需要公网连接或第三方中转服务吗?

完全不需要。本文的所有工具、脚本和流程均完全运行在内网本地环境中,下载依赖是在你的外围电脑上完成并验证 SHA256,所有向 Home Assistant 的推送与调用全部基于局域网官方 REST API。既不依赖任何第三方平台,也不泄露任何家庭网络隐私。

参考资料与文献

文中的 Home Assistant 品牌标志来自官方开源资产,原理图与流程图均为本文原创绘制。界面截图均源自本次真实运行环境,所有网络 IP、机器标识、个人账户及敏感用能数值已做不透明脱敏遮蔽。

评论

使用 GitHub 登录后即可评论,中英文版本共用同一讨论。 在 GitHub 上参与讨论

本文阅读量 --