xiao — Xiaomi Vacuum CLI & Mission Control

CLI + web dashboard to control a Xiaomi Robot Vacuum X20+ (model: xiaomi.vacuum.c102gl) via the Xiaomi Cloud API.
Cloud-only. This vacuum model has no local-network control path. Every command goes through Xiaomi Cloud with RC4-signed MIoT requests.
Agent-first. xiao is designed to be driven by LLM agents (Claude, OpenClaw, Cursor, Aider, Codex…) as a subprocess. If you are an agent reading this repo, start with AGENTS.md.

Agent Quick Reference
| Fact |
Value |
| Tool name |
xiao |
| Install |
pip install xiao-cli |
| Verify |
xiao --help → lists subcommands; exits 0 |
| State command |
xiao status (Rich panel) or xiao status --json (machine-readable) |
| Machine-readable |
xiao status --json / xiao consumables --json; or xiao web → GET /api/status |
| Exit codes |
0 success · 1 generic · 2 not configured · 77 auth failed · 80 room-clean accepted but still docked/charging after verification (AGENTS.md) |
| Agent guide |
AGENTS.md — canonical commands, intent mapping, error recovery |
| Machine index |
llms.txt |
Features
- Full vacuum control — start / stop / pause / dock / find / room / zone / spot.
- Base-station controls — mop wash, mop dry, dust collect, tray eject.
- Settings — fan speed, water level, volume, Do-Not-Disturb window.
- Consumable tracking with remaining life %.
- Schedule viewer with parsed room / day / setting data.
- Mission Control — glassmorphism web dashboard with real-time status, advanced X20+ settings controls, and a saved dark/light theme toggle.
- Auto token refresh via a persistent Chromium session (no repeated email 2FA).
- Full MIoT property/action support for
c102gl.
Installation
pip install xiao-cli
# or from source
git clone https://github.com/dacrypt/xiao.git
cd xiao && uv sync
Then install Playwright’s Chromium (needed for the cloud-login fallback):
playwright install chromium
Docker
A lightweight image is published to GHCR on every release. It’s meant for
running vacuum commands against an existing config — run xiao setup cloud once on your workstation, then mount the resulting config dir:
docker run --rm -v "$HOME/.config/xiao:/root/.config/xiao" \
ghcr.io/dacrypt/xiao status
(On macOS the host path is ~/Library/Application Support/xiao.)
The default image has no Chromium, so it can only use the cached session and
stops working when that expires (~6–8 h). For long-running use, build it with
docker build --build-arg WITH_PLAYWRIGHT=1 -t xiao . so silent token refresh
works. To serve the dashboard from a container, bind it explicitly — xiao then
requires the access token it prints:
docker run --rm -p 8120:8120 -v "$HOME/.config/xiao:/root/.config/xiao" \
ghcr.io/dacrypt/xiao web --host 0.0.0.0
MCP server (optional)
Install with the mcp extra and xiao exposes vacuum control as a
Model Context Protocol server, so
hosts like Claude Desktop / Cursor / mcp.so can drive the vacuum as
structured tools (no shelling out):
pip install "xiao-cli[mcp]"
xiao mcp # speaks MCP over stdio
Host config snippet (Claude Desktop claude_desktop_config.json):
{
"mcpServers": {
"xiao": { "command": "xiao", "args": ["mcp"] }
}
}
Exposed tools: status, start_cleaning, stop_cleaning,
pause_cleaning, return_to_dock, find_vacuum, consumables,
clean_room, list_rooms. The server reuses the same config.toml and
browser profile as the CLI.
Compatibility: Python 3.12+. Tested against vacuum model xiaomi.vacuum.c102gl on Xiaomi Cloud API as of 2024. Token refresh tested with Chromium ≥ 120 (currently running 147).
Shell completions (optional)
xiao --install-completion # bash / zsh / fish / pwsh — auto-detected
Restart your shell afterwards. xiao <TAB> will now complete subcommands.
Handy defaults
xiao with no subcommand prints the current vacuum status.
XIAO_DEBUG=1 xiao ... enables verbose logging for issue reports.
XIAO_NO_CTA=1 silences the GitHub-star banner.
Prerequisites
Before any xiao command will work:
xiao installed (see above) + playwright install chromium run once.
xiao setup cloud completed — writes config.toml at:
- macOS:
~/Library/Application Support/xiao/config.toml
- Linux:
~/.config/xiao/config.toml
That’s it. xiao setup cloud will also offer to run xiao setup browser-login at the end: a one-time Chromium window where you log into
account.xiaomi.com. The session cookies are saved to a private profile
under your config dir, so every future token refresh runs headless — no
captcha, no email 2FA.
You can rerun xiao setup browser-login anytime if a token expires or
the profile is cleared. Power users who already maintain a Chromium
session exposed over CDP can skip the profile entirely by setting
XIAO_CDP_PORT=18800 (or whatever port).
Setup
xiao setup cloud # Interactive: email → password → region → device discovery → save
xiao setup show # Print current config (tokens redacted)
Quick Start
xiao status # Current state, battery, fan speed, mode
xiao status --full # Comprehensive status (+ DND, water, consumables)
xiao start # Full-house clean
xiao stop # Stop cleaning
xiao dock # Return to charging dock
xiao find # Beep to locate vacuum
xiao report # Full report: status + consumables + schedules + history
Example xiao status:
╭─────────────────────────────── Vacuum Status ────────────────────────────────╮
│ State: Drying │
│ Battery: ███████████░░░░░░░░░ 56% │
│ Fan: turbo │
│ Charging: Charging │
│ dry_left_time_min: 470 │
╰──────────────────────────────────────────────────────────────────────────────╯
Example xiao consumables:
Consumables
┏━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┓
┃ Component ┃ Life ┃ Hours Left ┃ Status ┃
┡━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━┩
│ Main Brush │ 90% │ 272h │ │
│ Side Brush │ 86% │ 172h │ │
│ Filter │ 81% │ 122h │ │
│ Mop Pad │ 72% │ 57h │ │
└────────────┴──────┴────────────┴────────┘
Command Reference
Every command. One row each. Full catalog also lives in AGENTS.md.
| Command |
What it does |
Notable flags |
xiao status [--full] [--json] |
State + battery + fan + mode |
--full adds DND/water/consumables; --json for machine-readable output |
xiao start |
Full-house clean (always mop-washes first) |
— |
xiao stop / xiao pause |
Stop the current clean. In cloud mode the X20+ has no pause action, so pause also stops (and says so) |
— |
xiao dock |
Return to charging dock |
— |
xiao find |
Beep to locate |
— |
xiao clean -r <id|alias> |
Clean one or more rooms |
-r repeatable, --speed, -w, --repeat |
xiao clean -z "x1,y1,x2,y2" |
Clean a rectangle (coords in mm) |
-z repeatable, --speed, -w, --repeat |
xiao clean -s |
Spot clean (local miIO mode only — cloud mode exits 1; use a small -z zone) |
--speed, -w |
xiao wash / xiao dry [--stop] |
Base-station mop wash / dry |
— |
xiao dust / xiao eject |
Dust collect / eject tray |
— |
xiao settings speed <level> |
Fan: silent | standard | medium | turbo |
no arg → print current |
xiao settings water <level> |
Mop water: low | medium | high |
no arg → print current |
xiao settings volume <0-100> |
Voice volume |
no arg → print current |
xiao settings dnd on/off |
Do-not-disturb |
--start HH:MM --end HH:MM |
xiao settings resume-after-charge on/off |
Auto-resume cleaning after charging |
no arg → print current |
xiao settings carpet-boost on/off |
Boost suction on carpets |
no arg → print current |
xiao settings child-lock on/off |
Lock physical buttons on the robot |
no arg → print current |
xiao settings smart-wash on/off |
Toggle smart mop washing at the base station |
no arg → print current |
xiao settings carpet-avoidance avoid|auto |
Choose whether carpets are actively avoided or handled automatically |
no arg → print current |
xiao settings clean-rags-tip <0-120> |
Set base-station mop wash reminder interval in minutes |
no arg → print current |
xiao map rooms / xiao rooms |
List room IDs from the device room map, its schedules and your aliases |
— |
xiao map show |
Show what the cloud exposes about the map |
— |
xiao rooms alias <id> "<name>" |
Add friendly-name alias |
— |
xiao rooms rename <id|alias> "<new>" |
Rename an alias |
— |
xiao schedule |
Show parsed schedules in a compact table |
--json for machine-readable output |
xiao schedule list |
Alias for xiao schedule |
--json |
xiao consumables [--json] |
Brush / filter / mop health |
--json for machine-readable output |
xiao consumables reset <part> |
Reset counter (main_brush | side_brush | filter | mop | all) |
— |
xiao report |
Full combined report |
— |
xiao cloud-login |
Force full re-login (captcha + 2FA) |
— |
xiao web |
Launch Mission Control dashboard (localhost only by default) |
--port 8120, --host, --token |
xiao raw <siid> <aiid> [params…] |
Raw MIoT call (escape hatch) |
— |
Flag reference
--speed / --fan: silent · standard · medium · turbo
-w / --water: low · medium · high
--repeat N: integer ≥ 1, default 1
Room Cleaning
xiao clean -r 4 # Clean Study
xiao clean -r 3 -r 7 # Clean Living Room + Dining Room
xiao clean --speed turbo -r 8 # Turbo clean Kitchen
xiao clean -r 3 -w high # Living Room with high water (heavy mop)
How room cleaning reaches the X20+. The X20+ is Dreame-built. It answers
the spec’s room-clean action with code 0 and then ignores it. It obeys
Dreame’s start-clean (work mode 18 + a room selection), which the published
spec doesn’t declare, so /miotspec/action rejects it. xiao sends it the way
the Mi Home app does: through the Xiaomi cloud’s RPC passthrough
(/home/rpc/<did>). That works even when the robot is unreachable on the LAN
(it stops answering there after a while in the dock). Only when the cloud call
fails does xiao try the LAN (miIO, device token from config.toml).
If the robot is drying its mop, xiao stops the drying before starting any
clean (rooms, zones or the whole house), like the Mi Home app does. While
drying, the X20+ acknowledges a start and ignores it.
Room IDs and names come from the robot’s own map, the names you set in the Mi
Home app. The X20+ doesn’t expose them as a MIoT property: xiao downloads the
saved map the robot pushes to the Xiaomi cloud, decrypts it (Dreame format, AES
with a per-model IV) and reads its segments. Aliases still override names, and
xiao map rooms warns when an alias contradicts the map; drop one with
xiao rooms unalias <id>.
Room Management
Room IDs are specific to your vacuum’s map — they’re generated by the Xiaomi Home app the first time the robot maps the house.
xiao map rooms # ALWAYS run this first to discover IDs
xiao rooms alias 3 "Living Room" # Create an alias
xiao rooms rename 3 "Lounge" # Rename
Once aliased, names also work:
xiao clean -r "Living Room" -r "Kitchen"
If the X20+ accepts a room-clean command but still looks docked/charging a few seconds later, xiao clean now exits with code 80 and warns that Xiaomi can acknowledge room actions without actually moving the robot. In that case, re-check room IDs with xiao map rooms and fall back to xiao start if needed.
Rule for agents: never pass a room ID you haven’t verified with xiao map rooms. Maps can be re-generated by the Xiaomi Home app.
Zone Cleaning
Zones clean an arbitrary rectangle — useful for spot work (“just the dining table area”) or for rooms the vacuum hasn’t segmented correctly.
A zone is four coordinates in millimeters on the vacuum’s internal map:
x1,y1,x2,y2
└─┬─┘ └─┬─┘
top- bottom-
left right
On the X20+ (a Dreame-built map) (0,0) is near where the map was started, usually the dock, and coordinates can be negative. The easiest way to get real numbers is to draw a zone once inside the Xiaomi Home app and copy them. Like rooms, zones use Dreame start-clean (work mode 19) through the cloud RPC passthrough; they never fall back to start-sweep, which would clean the whole house.
# Single zone
xiao clean -z "25000,25000,35000,35000"
# Multiple zones in one run
xiao clean -z "23000,24000,27000,28000" \
-z "31000,25000,35000,30000"
# Zone + turbo fan + high water (deep-clean a specific patch)
xiao clean -z "25000,25000,35000,35000" --speed turbo -w high
# Two passes over the same zone
xiao clean -z "25000,25000,35000,35000" --repeat 2
Base Station
xiao wash # Start mop washing
xiao dry # Start mop drying
xiao dry --stop # Stop drying
xiao dust # Dust collection
xiao eject # Eject base tray
Settings
xiao settings speed turbo # silent | standard | medium | turbo
xiao settings water high # low | medium | high (mop water level)
xiao settings volume 50 # 0-100
xiao settings dnd on --start 22:00 --end 07:00
xiao settings dnd off
xiao settings resume-after-charge on
xiao settings carpet-boost off
xiao settings child-lock on
xiao settings smart-wash on
xiao settings carpet-avoidance auto
xiao settings clean-rags-tip 45
# Call any of the above without an argument to print the current value:
xiao settings speed # → Current fan speed: turbo
xiao settings water # → Water level: High (raw: 3)
xiao settings child-lock # → Child lock: Off (raw: 0)
xiao settings smart-wash # → Smart wash: On (raw: 1)
xiao settings carpet-avoidance # → Carpet avoidance: Avoid (raw: 1)
xiao settings clean-rags-tip # → Clean rags tip: 45 min (raw: 45)
Machine-readable output
Use --json on read commands for deterministic parsing:
xiao status --json
# {
# "state": "Drying",
# "battery": 84,
# "fan_speed": "turbo",
# "charging": "Charging",
# "dry_left_time_min": 388
# }
xiao consumables --json
Agent Intent Mapping
Short table of “user says X → run Y”. The canonical, longer version lives in AGENTS.md.
| User request |
Command |
| “clean the house” |
xiao start |
| “stop” / “cancel” |
xiao stop |
| “go home” / “dock” |
xiao dock |
| “battery?” / “status?” |
xiao status |
“clean the <room>” |
xiao map rooms → find id → xiao clean -r <id> |
“turbo mode in <room>” |
xiao clean -r <id> --speed turbo |
“deep mop <room>” |
xiao clean -r <id> --speed turbo -w high |
| “wash the mop” |
xiao wash |
| “empty the dust bin” |
xiao dust |
| “do not disturb 22 to 7” |
xiao settings dnd on --start 22:00 --end 07:00 |
| “open the dashboard” |
xiao web --port 8120 |
Error Recovery
Agents should apply these in order before asking the user.
| Signal |
Retry step |
If still failing |
Exit 77, or stderr contains token / 401 / auth |
Re-run the same command once (silent profile refresh fires on next call) |
xiao setup browser-login, then xiao cloud-login |
Exit 1 + rejected by the vacuum (code N) |
Check xiao status (docked? error? state 21?) |
Report the code to the user |
State: 21 / WashingMopPause |
The user refills clean water / empties dirty water, then xiao start resumes the paused job |
Press physical play button on the robot |
xiao clean -r <id> returns code 0 but xiao status after 10s still shows Idle/Docked |
Fall back to xiao start |
Report to user |
| Fan speed set rejected while docked |
Start the clean first, then set speed; or pass --speed inline on the clean command |
— |
xiao map rooms → “No rooms found” |
No room map exposed and no schedule references rooms — get the ID from the Xiaomi Home app and xiao rooms alias <id> "Name" |
— |
Mission Control (Web Dashboard)
xiao web --port 8120
# Open http://localhost:8120
The dashboard can start the robot and change settings, so it is locked down
by default:
- It listens on 127.0.0.1 only and rejects requests whose
Host isn’t
localhost (blocks DNS-rebinding from web pages you visit).
- State-changing requests must be same-origin and carry the
X-Xiao-Client: dashboard header, so other sites can’t drive it (CSRF).
xiao web --host 0.0.0.0 (LAN access) generates an access token and prints a
http://<host>:8120/?token=… URL; every API call then needs it. Pass your own
with --token or XIAO_WEB_TOKEN. Traffic is plain HTTP — trusted networks only.
Glassmorphism + neon sci-fi dashboard:
- Animated vacuum SVG (pulses cleaning, dims docked).
- Battery ring with gradient + estimated runtime.
- Room selector with fan / water presets.
- Base-station controls with status badges.
- Consumable health bars (color-coded, days-until-replacement).
- Cleaning history stats, schedule table, and a settings panel that now covers advanced X20+
vacuum-extend controls.
- Keyboard:
S=start, X=stop, D=dock, F=find, R=refresh.
- Dark/light theme toggle with saved browser preference.
- Mobile-first responsive, auto-refresh every 10s.
REST API
All endpoints at http://localhost:8120/api/ — use these for programmatic / JSON consumption.
POST requests need X-Xiao-Client: dashboard; if a token is configured, every
call (except /health) also needs X-Xiao-Token: <token>. Action endpoints return
{"ok": bool, "code": int, "result": …} — ok is false when the vacuum rejected the command.
curl -s -X POST -H 'X-Xiao-Client: dashboard' http://localhost:8120/api/dock
| Endpoint |
Method |
Description |
/status |
GET |
State, battery, fan, mode |
/status/live |
GET |
Minimal payload for fast polling (5s) |
/consumables |
GET |
Brush / filter health |
/rooms |
GET |
Rooms from the device map, schedules and aliases: [{id, name, sources}] |
/schedules |
GET |
Parsed schedules |
/settings |
GET |
DND, volume, fan, water, resume-after-charge, carpet-boost, child-lock, smart-wash, carpet-avoidance, clean-rags-tip snapshot |
/settings/resume-after-charge |
POST |
{enabled} toggle auto-resume after charging |
/settings/carpet-boost |
POST |
{enabled} toggle carpet suction boost |
/settings/child-lock |
POST |
{enabled} toggle hardware button lock |
/settings/smart-wash |
POST |
{enabled} toggle smart mop washing |
/settings/carpet-avoidance |
POST |
{mode} where mode is avoid or auto |
/settings/clean-rags-tip |
POST |
{minutes} where minutes is 0-120 |
/start |
POST |
Start clean |
/stop |
POST |
Stop |
/dock |
POST |
Return to dock |
/clean/rooms |
POST |
{room_ids, fan, water} |
/voice |
POST |
{action: clean|dock|stop|status, room?, zone?, lang: es|en} → {ok, say} (for Siri Shortcuts; always 200) |
/clean/zone |
POST |
{zones: [[x1,y1,x2,y2], …], fan, water} (mm, same as xiao clean -z) |
/wash |
POST |
Mop wash |
/dry |
POST |
Start/stop dry |
/dust |
POST |
Dust collect |
/settings/speed |
POST |
{level: 0-3} |
/settings/volume |
POST |
{level: 0-100} |
Apple Home & Siri (Matter)
The X20+ has no Matter firmware and HomeKit has no vacuum service, so xiao
ships a Matterbridge plugin that
exposes it as a Matter robot vacuum with your rooms in the Home app (voice:
Siri in US English, or the Shortcuts below in any language).
integrations/matterbridge-xiao/scripts/install-macos.sh
This runs xiao web and Matterbridge as login services on the Mac. Then pair
the Aspiradora node from the iPhone Home app (iOS 18.4+; a HomePod or Apple
TV is needed for remote control). Full guide:
integrations/matterbridge-xiao/README.md.
Siri in Spanish (or any language): Shortcuts
Apple only understands robot-vacuum voice commands in US English, so «Oye Siri,
limpia la cocina» goes to whatever else is in the “Cocina” room. Siri does run
any shortcut by its name, in any language. xiao generates one shortcut per
room, plus house / dock / stop / status. Each one calls POST /api/voice on
xiao web, and Siri reads back the answer.
The names all start with «Aspiradora…» («Aspiradora a la cocina»). A phrase that
names a Home room, like «limpia la cocina», is taken by Siri as a command for
that room (its light), and the shortcut never runs.
XIAO_HOST=0.0.0.0 integrations/matterbridge-xiao/scripts/install-macos.sh # xiao web on the LAN (token in config.toml)
xiao shortcuts export --out ~/Desktop/xiao-shortcuts # signed .shortcut files
AirDrop the files to the iPhone and tap Agregar atajo on each one. Then:
«Oye Siri, aspiradora a la cocina», «Oye Siri, aspiradora a la base», «Oye Siri, cómo
está la aspiradora». Options:
--name "7=Aspiradora a la cocina nueva" renames one room’s shortcut.
--zone "comedor=x1,y1,x2,y2" adds a zone.
--url http://<tailscale-name>:8120 makes the shortcuts work away from home
through Tailscale.
The files contain the xiao web token, so don’t share them. Requests from the
Mac itself (localhost, like the Matterbridge plugin) don’t need the token. Don’t
put a reverse proxy on the same Mac that rewrites Host to localhost: LAN
requests would then skip the token.
Use xiao as a Skill in an Agent
Because xiao is just a CLI, teaching an agent to drive your vacuum is really just teaching it which commands to run. Paste this prompt into OpenClaw (or any LLM agent that can run a subprocess) to bootstrap:
“Set up the Xiaomi vacuum skill”
I want you to act as my Xiaomi vacuum controller using the `xiao` CLI
(https://github.com/dacrypt/xiao). Read AGENTS.md from that repo and
follow its instructions. Then set it up end-to-end:
1. Install: pip install xiao-cli && playwright install chromium
2. Verify: xiao --help
3. Walk me through `xiao setup cloud` interactively (ask me for email,
password, region — one of us/cn/eu/ru/sg/tw/i2 — then discover device).
4. Run `xiao setup browser-login`: a Chromium window opens at
account.xiaomi.com, I'll log in once (captcha / 2FA), and xiao keeps
that session in its private profile for silent token refresh.
5. Confirm with `xiao status` and `xiao consumables`.
From now on, translate vacuum-related requests ("clean the kitchen",
"send it home", "what's the battery?", "turbo in the living room") into
the right `xiao` command using AGENTS.md's intent-mapping table. Never
guess room IDs — always run `xiao map rooms` first.
On errors, follow the Error Recovery protocol in AGENTS.md before
asking me.
Works the same way in Claude Code, Cursor, Aider, Codex. A ready-made Claude Code skill ships with the repo at .claude/skills/xiao/SKILL.md — it’s picked up automatically when you open this directory in Claude Code.
Cloud Token Refresh
Xiaomi Cloud serviceTokens expire every ~6-8h. A full re-login requires captcha + email 2FA, which can’t be automated headlessly. To avoid that, xiao keeps a long-lived Xiaomi session in its own Chromium profile (<config dir>/chromium, owner-only permissions).
Seed it once:
xiao setup browser-login # opens Chromium at account.xiaomi.com — log in, then close the window
(xiao setup cloud offers to do this at the end.)
Refresh flow (when the saved serviceToken is missing/expired, core/token_refresh.py):
- Launches the profile headless with Playwright (
launch_persistent_context).
- Navigates to
account.xiaomi.com/pass/serviceLogin?sid=xiaomiio&_json=true to obtain a fresh _sign — httpOnly cookies travel automatically.
- Submits a hidden
<form> POST to serviceLoginAuth2 (form submission because fetch/xhr don’t send httpOnly cookies). Credentials are passed to the page as data, never spliced into script source. Response yields ssecurity + location.
- Follows
location, which sets the serviceToken cookie on the redirect.
- Returns
{userId, serviceToken, ssecurity} to the caller → persisted to config.toml.
The same flow works from the CLI, the dashboard and the MCP server (Playwright’s sync API is moved off any running asyncio loop automatically).
Power users: set XIAO_CDP_PORT=<port> to reuse a Chromium you already run with --remote-debugging-port; xiao tries it first and falls back to the profile.
Full login fallback (xiao cloud-login, or automatically when the profile has no session): a Playwright login that handles the terms dialog and email verification. The 6-digit code is read from Gmail if the optional gog Gmail CLI is installed (searching only mail newer than the login attempt); otherwise xiao asks you to type it. Set XIAO_DEBUG=1 to get debug logs and login screenshots in <config dir>/debug/.
Troubleshooting
(Agents: the machine-oriented decision tree is in Error Recovery above — this section is for humans.)
Exit code 77 / “Cloud login failed”
The browser profile has no valid Xiaomi session (first run, or the session was revoked). Run xiao setup browser-login and log in once; future refreshes are silent again. As a fallback, xiao cloud-login performs a full re-login with captcha + email 2FA.
Cloud mode enabled but not configured (exit 2)
Run xiao setup cloud — the wizard asks for email / password / region and discovers your device.
Commands hang or time out
Usually a Xiaomi Cloud issue. Check connectivity (curl -I https://api.io.mi.com). The RC4-signed endpoint can also return 500s during Xiaomi maintenance windows.
xiao clean -r 3 returns OK but the vacuum doesn’t move
Known issue with room-specific cleans on some X20+ firmwares. Fallback: xiao start (full-house clean). Tracked in BACKLOG.md.
“No rooms found” from xiao map rooms
The X20+ cloud API exposes no room list, so xiao collects room IDs from the device’s schedules and your aliases. If the robot has no map yet, run xiao start once end-to-end. Then either create a schedule for the rooms in the Xiaomi Home app (xiao picks the IDs up from it) or add them by hand with xiao rooms alias <id> "Kitchen".
Fan speed setting is rejected while docked
Some MIoT properties are only writable while the robot is off the dock. Start a clean first, or pass --speed inline on the clean command.
WashingMopPause (state 21)
This is the official X20+ state name: the robot paused its job because the clean-water tank needs a refill or the dirty-water tank needs emptying. The robot can’t detect that you did it. In the Mi Home app you confirm it, and xiao does the same: xiao start (or ▶️ in Apple Home, or «Aspiradora continúa» with Siri) sends Dreame’s start-washing confirmation (siid 4, aiid 4, "1,1") through the cloud RPC passthrough, and the robot washes the mop and resumes the paused job. New room/zone jobs are refused while it’s paused (HTTP 409). If the robot still sits in state 21, press the physical play button on the top of the robot.
Cleaning Cycle
When you call xiao start, the X20+ runs the full sequence:
- Washing mop — washes pads at base station (~2-3 min).
- Cleaning — sweeps/mops the house.
- Returning — back to dock.
- Drying — auto-dries mop pads.
start always triggers a mop wash first, even in sweep-only mode.
Why this project exists
(Read this if you like origin stories. Agents can skip to AGENTS.md.)
xiao started as a specific itch: drive my Xiaomi vacuum from an LLM agent — first OpenClaw, then Claude.
The internet wasn’t encouraging. ChatGPT and most forum threads said some variant of “this model is cloud-only, the API is signed, you can’t control it from a script.” That turned out to be wrong — just tedious. Sitting down with Claude Code and poking at my own robot (sniffing requests, reversing the RC4 signing, mapping the MIoT spec for xiaomi.vacuum.c102gl), the protocol gave up pretty quickly.
Once the CLI worked, the rest followed fast. Anthropic’s general guidance for letting agents touch the real world is unsexy but effective: give them a CLI. The same binary you run by hand is the one the agent execs. So xiao is deliberately CLI-first — the dashboard, the REST API, the skill integrations are all thin layers over the same core.
At that point it stops mattering which LLM is driving, because the vacuum doesn’t care. This repo is really a skill — a small, reusable capability you can bolt onto any agent. Routines, voice assistants, cross-device automations, home dashboards — all downstream.
Development
uv sync
uv run pytest tests/ -v
uv run ruff check src/ tests/
uv run ruff format src/ tests/
uv run mypy src/xiao/
See CONTRIBUTING.md for source-tree layout, MIoT spec notes, and PR guidelines.
Privacy
xiao collects nothing. No telemetry, no analytics, no backend. Your
Xiaomi credentials and session tokens live exclusively on your machine.
See PRIVACY.md for the full statement.
License
MIT
评论