Skip to main content

Xiaomi IoT Local Control: Extracting Tokens for Home Assistant

 Cloud latency is the silent killer of home automation. You press a wireless switch, and three seconds later, the light turns on. In the world of IoT engineering, a three-second delay is an eternity.

For developers using Xiaomi (Mijia/Roborock) devices, the hardware is excellent, but the ecosystem forces a dependency on Chinese or European cloud servers. To achieve sub-100ms response times in Home Assistant, you need local control.

Local control via the python-miio library or Home Assistant's native Xiaomi integration requires a 32-character hexadecimal API token. Xiaomi deliberately hides this token within the Mi Home app's encrypted database. This guide details how to programmatically extract these tokens using Python, bypassing the need for rooted Android devices or compromised APKs.

The Architecture of the Lockout

Before extracting the key, it is vital to understand the lock. Xiaomi's IoT protocol (miIO) relies on a proprietary implementation of UDP communications.

When a device is out of the box, it acts as a Wi-Fi Access Point (AP). During provisioning, your phone connects to this AP and performs a handshake. Once the device connects to your home Wi-Fi, it stops broadcasting its own AP and begins listening for UDP packets on port 54321.

The Encryption Layer

The miIO protocol uses AES-128-CBC encryption. Every command sent to the device (e.g., set_powertoggle) must be encrypted using a specific key.

This key is derived from the Token.

  1. Handshake: The device sends a "Hello" packet to the controller.
  2. Token Generation: During initial pairing, the cloud generates a unique 32-character token.
  3. Storage: The token is flashed onto the device and stored in the Xiaomi Cloud database.
  4. The Problem: The Mi Home app retrieves this token to control the device but does not display it in the UI. Without this token, Home Assistant cannot encrypt commands, and the device will ignore your packets.

The Fix: Cloud Extraction via Python

The most reliable method for engineers to retrieve this token is to query the Xiaomi Cloud API directly. Unlike extracting from a mobile backup (which Apple and Google have made increasingly difficult), the Cloud API is standard HTTP/JSON.

We will use Python to mimic a login to the Xiaomi Cloud, retrieve the user's device registry, and parse the JSON response for the token field.

Prerequisites

  • Python 3.9+
  • A Xiaomi Cloud account (Mi Account) connected to your devices.
  • The micloud library.

Step 1: Environment Setup

Create a virtual environment to keep your dependencies isolated. This prevents conflicts with system-wide Python packages.

mkdir xiaomi-token-extractor
cd xiaomi-token-extractor
python3 -m venv venv
source venv/bin/activate  # On Windows use: venv\Scripts\activate

# Install the library to handle the auth handshake
pip install micloud

Step 2: The Extraction Script

Create a file named get_tokens.py. This script handles the challenge-response authentication mechanism required by Xiaomi's servers and handles the specific region selection (a common point of failure).

Note: Xiaomi fragments their cloud infrastructure. If your devices are set to the "Mainland China" server in the app, you must query the cn server. If they are in Europe, you must query de.

import os
import json
from getpass import getpass
from micloud import MiCloud
from micloud.micloudexception import MiCloudException

def serialize_device(device):
    """
    Helper to clean up device data for display.
    """
    return {
        "name": device.get('name'),
        "ip": device.get('localip'),
        "token": device.get('token'),
        "model": device.get('model'),
        "did": device.get('did'), # Device ID
        "is_online": device.get('isOnline')
    }

def main():
    print("=== Xiaomi Token Extractor ===")
    username = input("Enter Mi Account Email/ID: ")
    password = getpass("Enter Mi Account Password: ")
    
    # Common regions: 'cn' (China), 'de' (Europe), 'i2' (India), 'sg' (Singapore), 'us' (USA)
    print("\nRegions: cn (China), de (Europe), us (USA), sg (Singapore)")
    region = input("Enter your server region (default: cn): ").strip() or 'cn'

    print(f"\nAuthenticating against [{region}] servers...")

    try:
        # Initialize the Cloud Connector
        mc = MiCloud(username, password)
        
        # Perform login. If 2FA is enabled, this library may trigger a prompt
        # however, usually, simple credentials suffice for the API token fetch.
        if not mc.login():
            print("(!) Login failed. Check credentials.")
            return

        print("Authentication successful. Fetching device registry...")
        
        # Get all devices registered to this account in the specific region
        devices = mc.get_devices(country=region)
        
        if not devices:
            print(f"(!) No devices found in region '{region}'. Try a different region.")
            return

        print(f"\nFound {len(devices)} devices. Extracting tokens...\n")
        
        print(f"{'NAME':<25} {'IP ADDRESS':<15} {'TOKEN':<32} {'MODEL'}")
        print("-" * 90)

        for dev in devices:
            d = serialize_device(dev)
            
            # Filter out devices that don't reveal a token (often Zigbee child devices)
            token = d['token'] if d['token'] else "N/A (Zigbee/BLE?)"
            ip = d['ip'] if d['ip'] else "Offline/Cloud"
            
            print(f"{d['name']:<25} {ip:<15} {token:<32} {d['model']}")

    except MiCloudException as e:
        print(f"\n[Error] Xiaomi Cloud API Error: {e}")
    except Exception as e:
        print(f"\n[Error] Unexpected error: {e}")

if __name__ == "__main__":
    main()

Step 3: Execution and Interpretation

Run the script:

python get_tokens.py

You will see an output table similar to this:

NAME                      IP ADDRESS      TOKEN                            MODEL
------------------------------------------------------------------------------------------
Living Room Purifier      192.168.1.45    56193a...[32 chars]...f91a       zhimi.airpurifier.mb3
Roborock S5               192.168.1.50    746869...[32 chars]...616e       roborock.vacuum.s5
Bedroom Temp Sensor       Offline/Cloud   N/A (Zigbee/BLE?)                lumi.sensor_ht

Copy the 32-character string under the TOKEN column. This is your AES key.

Integrating with Home Assistant

With the token extracted, you can now configure the integration. While many devices are auto-discovered, explicitly defining them in configuration.yaml is often more stable for "power user" setups using the platform: xiaomi_miio integration (or the specific integration for your device type).

However, the modern standard is the UI-based integration.

  1. Go to Settings > Devices & Services.
  2. Click Add Integration.
  3. Search for Xiaomi Miio.
  4. If the device is on the same subnet, it may be discovered. If not, select "Add Device manually".
  5. Enter the IP Address and the Token you just extracted.

If you are using YAML (for older distinct platforms like fans or vacuums), the config looks like this:

# Example configuration.yaml entry
vacuum:
  - platform: xiaomi_miio
    host: 192.168.1.50
    token: 746869...[YOUR_TOKEN]...616e
    name: Roborock S5

Deep Dive: Why The Token Changes

A critical concept for retention of your IoT setup is Token Rotation.

The token is not permanent hardware ID. It is a session key generated during the Wi-Fi provisioning process.

  • Scenario A: You reboot your router. The token remains the same.
  • Scenario B: You power cycle the Xiaomi device. The token remains the same.
  • Scenario C: You reset the Wi-Fi on the Xiaomi device (usually holding two buttons for 3 seconds) to change the SSID. The token changes.

If you ever reset the device's Wi-Fi settings, the encryption key is regenerated. You must re-run the Python script above to fetch the new token and update Home Assistant.

Common Pitfalls and Edge Cases

1. The Region Trap

The API is strictly region-locked. If you bought a device from AliExpress (CN version) but set your app region to "Germany" to unify your dashboard, the device might not appear, or the API might return an empty list. Always check the region defined in your mobile app settings.

2. Zigbee and BLE Gateway Devices

You might notice some devices in the output have no IP address and no token. These are likely Zigbee sensors (door sensors, buttons) or Bluetooth Low Energy (BLE) thermometers.

  • These devices do not communicate via Wi-Fi directly.
  • They do not have their own miIO tokens.
  • They communicate via a Gateway (Hub). You only need the token for the Gateway. Home Assistant communicates with the Gateway, which relays the status of the sub-devices.

3. Network Isolation (VLANs)

If your Home Assistant instance is on a server VLAN and your IoT devices are on a separate IoT VLAN, auto-discovery usually fails because UDP broadcast packets do not traverse subnets.

  • Solution: You must extract the token manually using the method above and configure the device by static IP.
  • Ensure firewall rules allow traffic from HA to the Device IP on port 54321 (UDP).

Conclusion

Local control is the benchmark for a professional-grade smart home. By stripping away the cloud dependency, you not only improve privacy but drastically reduce latency.

Using Python to interface with the Xiaomi Cloud API provides a clean, repeatable way to retrieve the necessary credentials without resorting to questionable hacks. Once you possess the token, the device is truly yours.