Open-source project
Yonsm/MiService avatar
Yonsm/MiService

MiService puts Xiaomi cloud, MIoT properties and speaker control in one command line tool

XiaoMi Cloud Service for mi.com

860 stars229 forksPythonMIT

At a glance

What is it?
MiService is a Python library and CLI for Xiaomi cloud accounts, MiIO and MIoT device control, and XiaoAi speaker playback. It installs with a single pip command, works without any third party package, and can talk to a device directly over the LAN without a cloud account.
Who is it for?
MiService is a good fit for a home automation script, a self-hosted dashboard or a Raspberry Pi that needs to read and write Xiaomi device properties without building a full account service of its own, since it handles OTP login, MIoT signing and data decryption for you.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One library covering accounts, MIoT properties and speaker playback

MiService is a Python library and command line tool for the XiaoMi cloud service, published on PyPI as miservice and usable as a module. The README lays out the structure as a tree, which is the clearest statement of scope. Under the top level sit MiAccount for login and token management, MiIOService for the MiIO protocol, MiNAService for the MiAI service, a miio_command interface, a MiIOLocalService for direct LAN access, a built-in AES module and a local_command interface.

Each branch maps onto a real device operation rather than a thin API wrapper. MiIOService covers signed and encrypted MiIO requests, MIoT property get, set and action calls, device listing, and home and room hierarchy queries. It also queries MIoT Spec interface documentation, which is the piece most users will not expect: the tool can return a device's interface description, formatted as text or Python, or as the raw spec dict when neither is requested.

The intended audience is anyone scripting against Xiaomi hardware rather than clicking an app. The pyproject classifiers give the same signal, with Environment :: Console, Topic :: Home Automation and Operating System :: OS Independent.

MiAccount handles OTP login and keeps a token in ~/.mi.token

The account layer supports username and password login, and it handles two step verification delivered by SMS or email. Once authenticated, the service token is persisted to a file named ~/.mi.token, so a script does not have to log in on every invocation and, more importantly, does not have to handle the OTP prompt every time.

The environment variable table sets out what is mandatory and what is not. MI_USER and MI_PASS are required, MI_DID is optional but effectively required when operating a device because it selects the target by device ID or device name. MI_LOCAL is the odd one out: setting it to the form <IP>:<Token> switches the tool to direct LAN operation and removes the need for MI_USER, MI_PASS and MI_DID altogether.

That split is the design decision worth understanding. The credential path and the offline path are separate, and the library keeps both. The README does not document how the stored token file is protected, whether it is encrypted, or how to revoke a token, so treat ~/.mi.token as a secret and keep it out of shared machines and version control.

Properties are addressed with siid and piid, not with friendly names

Device control follows the MIoT addressing scheme, and the CLI exposes it directly. Get Props takes a comma separated list of siid with optional piid, set props uses the same form with an equals sign and a value, and actions take a service id plus arguments.

Reading a single property looks like this.

bash
miservice 2-1

Here 2 is the siid and 1 is the piid, and the piid may be omitted when it is 1. The README notes that both numbers can be looked up from the spec description, which is why the spec subcommand exists. Writing a value uses the same addressing with an explicit type marker.

bash
miservice 2=#60

The hash forces the text type, and single or double quotes work as well. Without a forced type the command auto-detects, and the README lists what it may detect: JSON null, false, true, integers, floats or text. Actions work the same way, with the aiid defaulting to 1 when it is omitted, and the placeholder [] being required when an action takes no arguments.

bash
miservice 5 []

This is the least friendly part of the interface. A script that hardcodes 2-1 and 5-4 will break silently when a device firmware revision shifts its service layout, so read the spec output at runtime if you care.

The mina subcommand covers TTS, playback and the latest AI answer

MiNAService targets XiaoAi speakers and is exposed through the mina subcommand. With no arguments it lists the MiNA devices on the account. With text it sends a TTS broadcast to all devices, and the README notes a sharp edge: if the text happens to equal a subcommand name such as pause, it is treated as the subcommand rather than as speech, so quoting or adding a prefix is required in that case.

Device level commands need MI_DID to select the speaker, and the README specifies that the value corresponds to the miotDID field in the device list. The available operations are pause, stop, play, play with a URL, loop with a URL for single track repeat, volume, status and ask.

bash
miservice mina volume 50            # 设置音量 0-100

The ask command returns the most recent AI response, and the service layer also adapts to device models, with some models using a different play_music API, as the library structure notes.

A second entry point exists for MIoT action calls with a JSON payload, which is how you invoke an action with structured arguments instead of positional ones.

bash
miservice action '{"did":"267090026","siid":5,"aiid":1,"in":["Hello"]}'

Note the quoting: the JSON is passed as a single shell argument, and on some shells the outer single quotes need care.

MI_LOCAL mode skips the cloud and talks to the device over UDP

The local path is the most interesting part of the design. Setting MI_LOCAL to the address and token of a single device takes the tool out of the cloud entirely and communicates with the device directly over UDP.

bash
export MI_LOCAL=192.168.1.10:0123456789abcdef0123456789abcdef

From there the commands are simplified, because there is no longer a device list to select from, and property names are given directly.

bash
miservice power=on,brightness=80

Raw miIO method calls remain available through the same slash syntax used in cloud mode, for example miservice /miIO.info. The README notes that the token can be obtained from the output of a cloud query with miservice list, or captured with the phone app.

There is a caveat about that last route. Pulling a device token from the app is a manual step and a credential exposure, so if the token rotates, the script stops working until someone refreshes it. The README does not document token rotation behaviour or a way to request a fresh token from the tool itself.

The local path also requires the device to be reachable on your LAN, and it works on one device at a time because MI_LOCAL holds a single IP and token pair.

Zero hard dependencies, with optional speedups for HTTP and AES

The README advertises zero hard dependencies, and the mechanism is a fallback rather than an abstract. If aiohttp is not installed, the library falls back to its built in biohttp implementation. If pycryptodome is not installed, AES falls back to a bundled pure Python implementation, which the library structure identifies as AES-128-CBC and marks as being for the miiolocal path.

The optional packages are therefore performance choices, not functional ones. The install section shows the base install and both optional extras.

bash
pip3 install miservice
bash
pip3 install aiohttp

The pyproject file confirms the same structure through optional dependency groups named aiohttp, crypto and dev, where the dev group pulls pytest, pytest-asyncio, ruff, mypy, build and twine. The declared floor is Python 3.8, with classifiers covering 3.8 through 3.14.

The consequence for deployment is straightforward. A container that only needs to read one property on a schedule needs no native wheels and no compiler, since the pure Python AES path covers the local mode. A busy installation that talks to the cloud for many devices will notice the difference that aiohttp and pycryptodome make.

Where MiService is the wrong tool, and how a hand rolled script differs

The first limitation is the credential model. Every cloud operation needs a Xiaomi account password in an environment variable, and a two step verification flow on first login. That is inherent to talking to the vendor's cloud, but it means an unattended deployment depends on a long lived password and on a token file in the home directory, and the README documents neither a service account nor a way to avoid a human completing the OTP step once.

The second is the interface. Nothing in MiService refers to a device by a friendly name at the MIoT layer. You work with siid, piid and aiid numbers, and those numbers come from the spec lookup that the tool also provides. A hand rolled script that talks to the HTTP endpoints directly would have to reimplement request signing, response encryption and MIoT data decryption, which is the work MiService has already done and the reason to use it.

The third limitation is scope. This is a Xiaomi device tool, not a general home automation platform. There is no scheduler, no scene engine, no history and no UI, and the README describes it as a library and a command line tool with no mention of a service mode. If you need alerting, scheduling or persistence, you add those yourself.

There is also a packaging wrinkle. The repository publishes no GitHub releases, so versions come from PyPI, and the command line banner reports MiService 3.0.2 while the pyproject file takes its version dynamically from miservice.__version__. The last push to the repository was on 2026-10-15, and the project also keeps a CHANGELOG.md, which is where you would look for what changed between versions.

Editorial conclusion

MiService is a good fit for a home automation script, a self-hosted dashboard or a Raspberry Pi that needs to read and write Xiaomi device properties without building a full account service of its own, since it handles OTP login, MIoT signing and data decryption for you. It is the wrong choice for anyone who cannot accept storing a Xiaomi account password in environment variables, since MI_USER and MI_PASS are required for the cloud path, and it offers nothing for devices outside the MiIO and MIoT ecosystems. Before adopting it, set MI_USER and MI_PASS, run miservice list to confirm the account returns your devices, and check whether the device you care about also carries a local token, since MI_LOCAL mode removes the cloud dependency entirely.

Frequently asked questions

How do I install MiService and set up the environment variables?

Run pip3 install miservice, then export MI_USER with the Xiaomi account username and MI_PASS with the password. Python 3.8 or newer is required, and aiohttp and pycryptodome are optional extras that improve HTTP and AES performance.

What is MI_LOCAL and when should I use it?

MI_LOCAL is set to <IP>:<Token> and switches the tool to direct LAN control over UDP with no cloud account, so MI_USER, MI_PASS and MI_DID are not needed. The token can come from a miservice list cloud query or from the phone app, and the README notes it works with one device at a time.

How do I find the siid and piid values for a device property?

Query the MIoT spec for the model, for example miservice spec xiaomi.wifispeaker.lx04 text, which returns the interface description split into property get, property set and action call sections. The README notes that siid and piid values can be found in that output, and that the piid may be omitted when it is 1.

How do I make a XiaoAi speaker say something?

Use the mina subcommand, for example miservice mina 你好世界, which broadcasts a TTS message to all devices. If the text equals a subcommand name such as pause it is treated as a command, so quote it or add a prefix.

Does MiService work without any third party packages?

Yes. The README states zero hard dependencies: without aiohttp the library falls back to its built in biohttp, and without pycryptodome it falls back to a bundled pure Python AES-128-CBC implementation used by the local service.

What license is MiService released under?

The pyproject file declares the license as MIT and the repository ships a LICENSE file. The project is also classified as Development Status :: 5 - Production/Stable.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Yonsm/MiService on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yonsm-miservice.svg)](https://hysenlabs.com/projects/yonsm-miservice)

Was this useful?