中文 English

A Single Click Broke All Three Midea Appliances: Escaping Home Assistant's 6-Minute Boot Freeze with Offline Dependency Injection

Published: 2026-10-26 · Views --
homeassistant HA Smart Home Automation Automation Troubleshooting Troubleshooting Self-Hosting Self-hosting Midea 美的 Python

The most heart-stopping moment in smart home administration is never unboxing a finicky new sensor. It is watching a core integration that has run smoothly for months flash a bright update prompt in HACS. You casually hit “Update,” reboot the server, and watch in disbelief as your appliances vanish, while the entire smart home hub locks up mid-boot.

On October 26, 2026, I performed an upgrade assessment and deployment for the Midea AC LAN integration on our local Home Assistant hub, which governs our household drum washing machine, smart dishwasher, and gas water heater. Transitioning from v2026.9.1 to v2026.10.0 seemed like an ordinary patch. Instead, it triggered a series of critical bottlenecks: an agonizing boot freeze exceeding six minutes, all three core appliances falling into a red “Not Loaded” state, sweeping breaking changes in device entity schemas, and a dependency resolution stall under degraded network connectivity.

This article provides a comprehensive field review of the nearly two-hour diagnostic deep dive. It includes detailed symptom analysis, architectural code archaeology, everyday analogies, a zero-patch offline healing blueprint verified in production, and one-click automation scripts tailored for Windows 11, Ubuntu 26.04, and macOS 27.

Original Editorial Figure: From a 6-minute boot freeze to instant offline healing

Figure 1: Original editorial graphic. Illustrates the three appliances involved, the Stage 2 bootstrap timeout, and the clean offline mounting architecture.

1. Background: The Mission-Critical LAN Appliance Hub

Major household appliances hold a unique role in smart home architecture. If a temperature sensor or motion detector drops offline, the worst outcome is a lighting automation delayed by a second. But washing machines, dishwashers, and water heaters interface directly with high-pressure water, mains power, and municipal natural gas lines.

Our Home Assistant instance runs on a dedicated bare-metal server (running Home Assistant OS, Core version 2026.10.4, backed by the Python 3.14 runtime). To eliminate reliance on vendor cloud servers, cloud maintenance windows, and unpredictable latency, our household Midea appliances communicate over local network protocols using the popular open-source midea_ac_lan custom component:

Appliance Name Hardware Model / Protocol Code Protocol Mode Supervised Controls & Sensors
Midea Drum Washer 38111463 / Type 0xDB V2 LAN Protocol Cycle progress, program selection, temperature, spin speed, time remaining
Midea Dishwasher H5 000000H5 / Type 0xE1 V3 LAN Protocol Door contact, rinse aid / salt alerts, wash program, child lock, power
Midea Gas Water Heater 5110188E / Type 0xE3 V3 LAN Protocol Target water temp, flame burning state, zero-cold-water recirculation, safeties

Together, these three appliances generate 34 active entities embedded across our Lovelace dashboards and automation routines.

That afternoon, HACS indicated an available update for Midea AC LAN from v2026.9.1 to v2026.10.0. The release notes highlighted stability improvements, enhanced device models, and graceful socket closures upon shutdown.

On paper, this appeared to be a routine bump. However, once applied, the stability of our smart home hub collapsed.

Real Screenshot: Midea AC LAN loaded normally in Home Assistant Integrations Dashboard

Figure 2: Real system screenshot. The Midea AC LAN integration card operating in Home Assistant, managing the washer, dishwasher, and water heater simultaneously.

2. Symptoms: The 6-Minute Boot Freeze and Total Blackout

Under typical circumstances, hitting “Update” in HACS followed by a system restart triggers a brief 15 to 20-second initialization cycle. But after applying v2026.10.0 to custom_components/midea_ac_lan/, the browser progress indicator spun indefinitely. Five minutes passed without any sign of web interface life.

Connecting to the host console to inspect raw logs revealed severe systemic gridlock:

  1. Initialization stalled at Stage 2: The Core boot sequence was blocked dead in its tracks, with bootstrap debug logs repeating: [homeassistant.bootstrap] Waiting on tasks: {<Task pending name=‘setup component midea_ac_lan’…>}.
  2. Watchdog timeout triggered: After a grueling 360 seconds (6 full minutes), Core’s bootstrap watchdog finally gave up, issuing an alert: Something is blocking Home Assistant from wrapping up the start up phase. We’re going to continue anyway..
  3. Total integration failure: While the web dashboard eventually opened, navigating to Settings -> Devices & Services showed all three Midea appliances marked with a red not_loaded status badge.
  4. Fatal argument incompatibility: Core diagnostic dumps exposed an immediate exception: TypeError: device_selector() got an unexpected keyword argument ‘sn8’.

Why did a seemingly simple integration update not only break all connected appliances, but also hold the entire Home Assistant startup hostage for six minutes?

3. Everyday Analogy: The School Assembly and the Desk Drawer

To make these underlying software mechanics crystal clear for anyone—even a school student without a programming background—we can compare Home Assistant’s boot sequence to a morning school assembly.

Original Analogy Diagram: The School Assembly, School Gate Pitfall, and VIP Desk Drawer

Figure 3: Original explanatory graphic. Comparing boot dependency stalls, false root vendoring, and isolated user-site mounting to familiar real-world school rituals.

1. The Symptom: The Frozen Assembly

Picture the first day of school. All students, teachers, and staff (lights, sensors, automation rules) assemble on the field waiting for the principal to declare the semester open (reaching the RUNNING state).

The attendance dean (Core Bootstrapper) pulls out an equipment checklist (the integration’s manifest.json). He walks up to “Little Mei” (the Midea integration) and notes: “To enter class today, you must hold the 2026 handbook (midea-lan==2026.10.0).”

Discovering that Mei is missing this exact version, the dean dispatches a bicycle courier across town to an out-of-province warehouse (the PyPI server files.pythonhosted.org).

Unfortunately, the highway is jammed and the bicycle is moving at a crawl of one inch per second (a miserable 10 KB/s download speed). Strict school rules state: The assembly cannot officially begin until every student has their materials!

Consequently, thousands of students freeze on the windy field for six long minutes. Only when everyone is shivering does the principal blow the whistle to force the ceremony open anyway—but Little Mei is benched (marked as not_loaded), unable to participate in any classes.

2. Pitfall One: Leaving Books at the Front Gate (/config Root)

You might ask: “What if I downloaded the handbook beforehand and left it on the ground outside the school gates (placing midealan in the /config root)?”

This reveals a major architectural illusion: the gatekeeper only checks the front door for a split second while escorting visitors inside (importing custom_components). When regular classes begin, the staff never look at the gate ground! The handbook sitting at the curb is ignored, and the courier is dispatched anyway.

3. Pitfall Two: Bringing a CD-ROM with Mandatory Verification (file:// Wheel Trap)

What if you hand the teacher a local CD-ROM containing the handbook (pointing to a local file://…whl)?

Still blocked! The school safety inspector operates under a strict rule (Home Assistant appends –upgrade to every uv pip install run). Upon receiving your CD, he refuses to read it until he dials overseas headquarters to verify whether any footnotes have newer revisions worldwide. The international phone line stalls, trapping the assembly in the exact same timeout.

4. The Golden Path: The Secret Desk Drawer (/config/deps)

How do you bypass both the slow courier and the overseas phone call?

Consulting the official school regulations (Core’s bootstrap.py source code) uncovers a hidden secret: the principal built a dedicated “VIP storage compartment” into every desk (located at /config/deps/lib/python3.14/site-packages/).

If you unpack the handbook and its official seal (.dist-info) into this designated drawer, the teacher checks the desk, sees everything in place, and stamps immediate approval. The assembly begins in seconds, with zero phone calls and zero delays.

4. Deep Technical Analysis: Breaking Changes and Architecture Traps

Returning to software engineering details, this incident was caused by two intersecting traps:

Real System Log Screenshot: Core startup blocked by setup component midea_ac_lan for 6 minutes

Figure 4: Real terminal log excerpt showing the Stage 2 bootstrap timeout and the device_selector() TypeError exception.

1. Trap One: The Breaking Argument Incompatibility

The Midea AC LAN integration is a frontend bridge. The actual parsing of appliance binary payloads, cryptographic token/key handshakes, and protocol framing is performed by a companion Python library called midea-lan.

In v2026.9.1, the component relied on midea-lan 2026.9.1. In v2026.10.0, the upstream maintainer refactored the device factory:

device_selector(
    device_type,
    device_id,
    ip_address,
    port,
    token,
    key,
    protocol,
    model,
    sn8,                 # New parameter
    manufacturer_code,   # New parameter
    cloud_name           # New parameter
)

The older library does not accept these three keyword parameters. If the integration is upgraded while the underlying Python library remains at 2026.9.1, every device initialization crashes with an immediate TypeError.

2. Trap Two: The sys.path Illusion in loader.py

Why did offline files previously placed in /config stop working?

In earlier maintenance notes, some practitioners placed midealan/ directly in /config/. Examining Home Assistant Core’s homeassistant/loader.py (lines 1709-1713) exposes why this was an illusion:

# homeassistant/loader.py snippet
try:
    sys.path.insert(0, config_dir)
    # Dynamically import custom_components only
    integration = importlib.import_module(f"{CUSTOM_COMPONENTS}.{domain}")
finally:
    # Immediately remove config_dir from sys.path!
    sys.path.remove(config_dir)

Home Assistant only mounts /config to sys.path momentarily during custom component discovery. Once discovery completes, /config is stripped from Python’s search path. During normal runtime and requirement checks (is_installed()), /config is completely invisible. The previous setup functioned solely because the old library had previously been cached in the container’s global site-packages.

Original Architecture Diagram: Dependency resolution mechanics in loader.py vs bootstrap.py

Figure 5: Original architecture diagram. Contrasting the temporary mount in loader.py against the permanent Stage 0 user-site insertion in bootstrap.py.

3. Trap Three: The uv pip –upgrade Network Black Hole

Home Assistant utilizes uv as its underlying package engine. When a component specifies:

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

Core invokes is_installed(). Because the library is not found in Python’s standard path, Core runs:

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

Under domestic residential connections with restricted routes to files.pythonhosted.org, transfers frequently collapse to ~10 KB/s. Even attempting to rewrite manifest.json to point to a local wheel file:

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

still fails! Core hardcodes the –upgrade flag during automated dependency installation. According to Python packaging specifications, –upgrade forces the resolver to query online registries to verify transitive sub-dependencies. With degraded upstream connectivity, Home Assistant remains trapped in a 6-minute boot freeze.

5. Root Cause and Solution: The Official User-Site Path

To achieve true offline independence without modifying official integration manifests, we turn to Core’s bootstrap routine in homeassistant/bootstrap.py.

Line 745 reveals the built-in escape hatch: 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

This built-in mechanism provides the perfect solution:

  1. Standard designated folder: Core reserves /config/deps/ specifically for local libraries.
  2. Permanent sys.path[0] priority: During Stage 0 startup, Core determines the active Python site directory (e.g., /config/deps/lib/python3.14/site-packages) and prepends it directly to sys.path[0].
  3. Bypassing network downloads: When is_installed(“midea-lan==2026.10.0”) runs, Python’s importlib.metadata locates the unpacked package and .dist-info directly in sys.path[0], immediately returning True.
  4. Preserving pristine manifests: The upstream manifest.json remains untouched, ensuring full compatibility with future official updates and Git checkouts.

Real Filesystem Verification: Unpacked midealan and dist-info inside /config/deps

Figure 6: Real terminal evidence. The verified directory tree showing midealan and midea_lan-2026.10.0.dist-info structured in the Python 3.14 site-packages path.

6. Implementation and Verification

The production-grade upgrade workflow was executed across five structured stages:

Stage 1: Workstation Staging and Dual SHA256 Auditing

Artifacts were downloaded on an external workstation with clean international bandwidth:

Stage 2: Comprehensive Environment Backup

Before modifying any server assets:

Stage 3: User-Site Injection and Cleanup

  1. Created directory structure: /config/deps/lib/python3.14/site-packages/ on Home Assistant.
  2. Unpacked the wheel and deployed midealan/ and midea_lan-2026.10.0.dist-info/.
  3. Deployed the 32 new integration files into custom_components/midea_ac_lan/ with the stock manifest.
  4. Purged obsolete legacy directories from the /config root.

Stage 4: Configuration Audit and Rapid Reboot

Invoked Home Assistant’s check_config service via API:

Stage 5: Entity Reconciliation and State Normalization

Following restart, all three appliance config entries reported loaded:

All 34 original entities were preserved intact.

Real Screenshot: Dishwasher H5 device page showing loaded entities and disabled controls

Figure 7: Dishwasher device overview in Home Assistant, displaying standardized naming and the 11 safely disabled control entities.

Real Screenshot: Drum washer device page with intact sensors and operational telemetry

Figure 8: Drum washer device details, showing cycle progress, spin speed, and temperature settings functioning normally.

Real Screenshot: Gas water heater device page showing burning state and zero cold water switches

Figure 9: Gas water heater overview, showing live flame state telemetry, recirculation switches, and temperature targets.

Two important post-upgrade adjustments were addressed:

  1. Dishwasher Entity Triage: Version 2026.10.0 introduces 11 new control entities (Start, Delayed Start, Pause, Resume, Cancel buttons, cycle selectors, and auto-door toggles). Because remote starting on the H5 hardware carries unintended cycle risks if unmonitored, these entities were standardized to .mei_de_xi_wan_ji_ and marked disabled_by: user in the entity registry.
  2. Lowercase State Key Changes: Upstream changed raw sensor state strings from Title Case to lowercase keys (e.g., Running to running, ECO Wash to eco_wash, Idle to idle). Any custom automation scripts that match against raw text strings should be adjusted to match the new lowercase values.

Real Screenshot: HACS integration dashboard aligned with v2026.10.0

Figure 10: HACS repository status card aligned with upstream v2026.10.0, eliminating pending update notifications.

7. Cross-Platform Automation Toolkit

To simplify this process for users facing similar network constraints, we packaged the procedure into a standalone automation toolkit.

Built entirely on the Python 3 standard library, it requires zero external pip packages and includes automatic Python version probing, SHA256 integrity audits, and safe configuration verification.

Original Workflow Diagram: Windows 11, Ubuntu 26.04, and macOS 27 automation architecture

Figure 11: Original workflow architecture. Illustrates the end-to-end pipeline from local staging to user-site injection and WebSocket verification.

Toolkit download links:

Method A: Manual CLI Execution

Run the script matching your operating system. The program prompts for credentials interactively without printing tokens:

1. Windows 11 (PowerShell 7 / Windows PowerShell)

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

2. Ubuntu 26.04 LTS (Bash)

bash ./run-ubuntu2604.sh --apply

3. macOS 27 (Zsh)

zsh ./run-macos27.zsh --apply

Running without –apply executes a read-only audit of Core version numbers, detected Python directories, and current appliance entity states.

Method B: Agent Automated Configuration

If you utilize an autonomous coding assistant or operations agent (such as Claude Code, OpenCode, or an operations bot), extract the toolkit to your workspace and provide the following prompt:

Please use midea_deps_toolkit.py in the current directory to perform an offline dependency upgrade for Midea AC LAN on my Home Assistant instance.
Read the instance URL from HA_URL and the Long-Lived Access Token from HA_TOKEN. Do not print credentials or full IP addresses to output.

Step 1: Execute a read-only assessment:
python3 midea_deps_toolkit.py --non-interactive

Step 2: Confirm the Python runtime version, then apply the offline injection:
python3 midea_deps_toolkit.py --non-interactive --apply

Requirements:
1. Download Release ZIP and midea-lan 2026.10.0 wheel, verifying SHA256 hashes;
2. Unpack and inject into /config/deps/lib/python3.14/site-packages/ without altering manifest.json;
3. Clean legacy /config root directories;
4. Invoke check_config service and trigger a graceful restart;
5. Verify via API that all three appliance entries report 'loaded';
6. Triage and disable newly added dishwasher control entities.

Real Screenshot: The live Lovelace home dashboard with operational Midea appliances

Figure 12: The final Lovelace home dashboard. All three Midea appliances reflect active states reliably across our household controls.

8. Frequently Asked Questions (Q&A)

Q1: Why does Home Assistant freeze on boot every time after updating via HACS?

This is caused by uv pip install –upgrade timing out under degraded network routes. The integration declares a dependency requirement, and Core attempts to pull it from remote registries on boot. Because upstream servers stall under local network constraints, the bootstrap watchdog waits up to six minutes before timing out. Placing the dependency in /config/deps/lib/pythonX.Y/site-packages/ satisfies the requirement check immediately, restoring 15-second cold boots.

Q2: What happens when Home Assistant upgrades its Python version (e.g., from 3.14 to 3.15)?

The directory path under deps/lib/ is tied to the Python minor version. When Home Assistant Core updates its underlying Python runtime, copy or move the site-packages folder to match the new version directory (such as deps/lib/python3.15/site-packages). The included toolkit accepts a –python-ver parameter to scaffold this in seconds.

Q3: Why not point manifest.json to a local wheel file via file://?

Modifying manifest.json breaks upstream tracking and causes HACS conflicts on subsequent updates. More importantly, Core appends the –upgrade flag to internal installer commands, which forces uv to query remote registries for updated sub-dependencies anyway, resulting in the same timeout.

Q4: Dishwasher states show raw strings like “power_off” or “idle”. Can these be localized?

Yes. Upstream localized the entity names but omitted translations for raw state keys. You can configure a template sensor in configuration.yaml that maps raw values like power_off to “Off” or localized terms, then reference that sensor on your Lovelace cards.

Q5: Does this solution require third-party proxies or external services?

No. The toolkit runs entirely within your local environment. Artifacts are staged and verified via SHA256 locally, and deployments communicate directly with Home Assistant over the standard local REST API.

References and Documentation

Home Assistant brand assets referenced under official open-source guidelines. Architectural and explanatory diagrams are original works. Interface screenshots reflect our verified production environment with private identifiers, addresses, and sensitive values fully redacted. EOF

Comments

Sign in with GitHub to comment. Chinese and English versions share the same discussion. Discuss on GitHub

Views --