Skip to main content

mihome-ctl

Passwordless QR login to the official Xiaomi cloud — extract every device's miIO token / local IP / BLE beacon-key, and control cloud IR remotes (TV / air-conditioner / fan / DIY on-off) through your home's parent blaster (e.g. a Xiao AI speaker). No local hardware, no Home Assistant required.

  • Talks only to official hosts: account.xiaomi.com + {region}.api.io.mi.com
  • Supports the tw / sg regions (python-miio / micloud have no tw locale)
  • Produces per-device token + local IP + BLE key
  • Passwordless (QR); secrets written with chmod 600

CI  ·  Docs: https://daviddwlee84.github.io/mihome-ctl/  ·  中文說明見文件站

A thin wrapper over the Xiaomi-cloud-tokens-extractor connector (MIT, vendored + trimmed into mihome_ctl/connector.py, see THIRD_PARTY_LICENSES/) plus a cloud-IR control layer.

Install

uv tool install mihome-ctl          # or: pipx install mihome-ctl
# From source:
uv sync && uv run mihome-ctl --help

Optional extras:

uv tool install 'mihome-ctl[verify]'   # verify: LAN token check (python-miio)
uv tool install 'mihome-ctl[mcp]'      # MCP server (for Claude / agents)

Claude integration — skill + MCP (one step)

After installing, wire up the agent skill and MCP server in one command:

mihome-ctl setup            # lists what it will do, asks [y/N], then installs the skill + registers the MCP
mihome-ctl setup -y         # skip the confirmation prompt (non-interactive)
mihome-ctl setup --project  # install into ./.claude instead of the user dir
mihome-ctl setup --npx      # install the skill via `npx skills add` (vercel-labs/skills) instead
mihome-ctl setup --dry-run  # preview what it would do, write nothing
mihome-ctl setup --uninstall  # remove the skill + unregister the MCP (also honors -y / --dry-run)

setup ships the skill inside the wheel, so the default path needs no Node — it copies SKILL.md locally and runs claude mcp add mihome-ctl -- mihome-ctl-mcp (or prints the config if the claude CLI isn't found). So the whole onboarding is:

uv tool install 'mihome-ctl[mcp]' && mihome-ctl setup
  • Agent Skill mihome-ir — lets Claude control the TV / A-C / fan by voice-style requests. Also installable directly: npx skills add daviddwlee84/mihome-ctl --skill mihome-ir.
  • MCP server mihome-ctl-mcp — exposes list_remotes / list_keys / ir_send / ir_ac plus device_prop_get / device_prop_set / device_action (control any device) as MCP tools (reuses the QR session cached by mihome-ctl ir).

Usage

mihome-ctl extract               # QR login, extract tokens (scans tw sg cn)
mihome-ctl extract --server tw sg   # specific regions (space-separated, not repeated flags)
mihome-ctl list                  # reprint last result offline
mihome-ctl verify --did <DID>    # LAN-verify one device (needs [verify])

mihome-ctl ir                    # list cloud miir.* remotes + parent blaster
mihome-ctl ir-send --remote <name> --key VOL+ [--repeat 3]
mihome-ctl ir-send --remote <name>            # omit --key to list a remote's keys
mihome-ctl ir-ac --temp 26 --mode cool        # absolute A/C control (state-based)
mihome-ctl ir-ac --off | --status
mihome-ctl ir-code --matchid xm_1_199         # IRDB matchid → per-key Pronto (see notes)

# control any (non-IR) device by MIoT siid/piid — cloud by default, --local for LAN
mihome-ctl devices                                             # pretty table of devices + did (--md for markdown)
mihome-ctl spec --did <DID>                                    # named properties/actions (from miot-spec.org) — so you know which siid/piid
mihome-ctl prop-get --did <DID> --siid 2 --piid 1              # read a property
mihome-ctl prop-set --did <DID> --siid 2 --piid 1 --value true # write a property
mihome-ctl action   --did <DID> --siid 2 --aiid 1 --args '[]'  # call an action
mihome-ctl miio     --did <DID> --method get_prop --params '["power"]'  # raw local miIO

On first run a QR is drawn in the terminal (or open http://127.0.0.1:31415) — scan once with the Mi Home app; the session is cached in the state dir's mi-session.json, so subsequent runs skip the scan. Use --relogin to force a re-login.

State directory

Resolved in this order (secret filenames: mi-tokens.json / mi-session.json / mi-ir.json / devices.md):

  1. MIHOME_CTL_HOME environment variable
  2. the nearest ./.secrets/ walking up from the cwd (convenient when embedded as a submodule inside another repo)
  3. otherwise the user state dir — honoring XDG_STATE_HOME on every platform (incl. macOS), else the platformdirs default (~/Library/Application Support/mihome-ctl on macOS, ~/.local/state/mihome-ctl on Linux). An existing legacy dir is still read so nothing is orphaned.

All secrets are written with mode 0600.

Architecture — one core, many front-ends

mihome_ctl.core is a UI-agnostic layer (connector, cloud API, IR codec, operations) that only does work and returns structured data. Everything on top is a thin presentation layer:

  • mihome_ctl.commands + __main__ — the Tyro CLI (commands above)
  • mihome_ctl.mcp_server — an MCP server (mihome-ctl-mcp, needs [mcp])
  • the TUI (mihome-ctl tui, [tui] extra) — Remotes / Air-Con / Devices tabs — is another layer over the same core. The Devices tab resolves each device's MIoT spec into a live per-property panel: a toggle for on/off, a dropdown for modes, a numeric box for ranges, each showing the property's current value inline (auto-loaded when you open a device, or via Refresh) with per-row Get/Set — no siid/piid guessing. A manual siid/piid/aiid form stays for spec-less devices (blt.*/miir.*) and raw calls.

ir-code and licensing (important)

ir-code decodes a Xiaomi IRDB matchid into Pronto: base64 → AES-128-ECB → gzip → microsecond timings → Pronto. The decoder is a clean-room implementation (from the public protocol, using pycryptodome), so this package contains no AGPL code, vendors nothing AGPL, and downloads no AGPL tool at runtime — it stays cleanly MIT.

Limitation (stated honestly): the public {region}-urc.io.mi.com/controller/code/1 endpoint currently requires a Mi Home app signature (anonymous requests return status:19), so fetching codes online by matchid is experimental. The decoder itself is verified by a round-trip test. To use an external IRDB tool (e.g. the AGPL-licensed ysard/mi_remote_database), install it yourself and opt in — this package deliberately does not depend on or bundle it (core/ircodec/base.py keeps an IRCodecBackend extension point for an external backend).

Development

uv sync --extra verify --extra mcp
uv run pytest -q
uv run ruff check . && uv run ruff format --check .

Releasing

Publishing is automated: cut a GitHub Release tagged vX.Y.Z (matching the pyproject.toml version) and .github/workflows/release.yml builds and uploads to PyPI via Trusted Publishing (OIDC — no stored token). One-time PyPI setup instructions are in the workflow header.

License

MIT (see LICENSE). The bundled Xiaomi connector derives from an upstream MIT project — see THIRD_PARTY_LICENSES/.

Metadata

Release files for mihome-ctl 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mihome-ctl 0.7.0
File Size Uploaded Provenance
mihome_ctl-0.7.0.tar.gz 60.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mihome-ctl 0.7.0
File Interpreter ABI Platform
mihome_ctl-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 115.6 kB

Release files / mihome_ctl-0.7.0.tar.gz

Download URL mihome_ctl-0.7.0.tar.gz
Size 60.7 kB
Tags Source
SHA-256 checksum
How to use checksums
dd8d7c12ef8068e79a9759ad48d8f9a21400624a63a6e54a4db5f955d2f84ced
BLAKE2b-256 checksum
How to use checksums
c64df4e43913042c18924e7aed45ec5ef582bfae86f89ebbb4170d62c7b757c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / mihome_ctl-0.7.0-py3-none-any.whl

Download URL mihome_ctl-0.7.0-py3-none-any.whl
Size 54.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a84530633e215e473df58130a9f4097ca495001f1035a1f4ee3965152b166ac1
BLAKE2b-256 checksum
How to use checksums
cfb9035679729da46846f6e078d6743095e87f384d7f17c5a2cf560a262b838c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Security for mihome-ctl 0.7.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page