Skip to main content

CLI tool and web dashboard to control Xiaomi/Roborock robot vacuums via cloud API

Project description

xiao — Xiaomi Vacuum CLI & Mission Control

CI Python 3.12+ License: MIT Agent-ready

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 webGET /api/status
Exit codes 0 success · 1 generic · 2 not configured · 77 auth failed · 78-80 reserved (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.
  • 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

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).


Prerequisites

Before any xiao command will work, all of these must be true:

  1. xiao installed (see above) + playwright install chromium run once.
  2. xiao setup cloud completed — writes config.toml at:
    • macOS: ~/Library/Application Support/xiao/config.toml
    • Linux: ~/.config/xiao/config.toml
  3. (For automatic token refresh) A Chromium process listening on 127.0.0.1:18800 with an active Xiaomi account session:
    chromium --remote-debugging-port=18800 --user-data-dir=~/.xiao-chromium
    # then log in manually at https://account.xiaomi.com once, leave it running
    
    If missing, xiao falls back to xiao cloud-login, which requires solving captcha + email 2FA once per expiry.

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 / pause current clean
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 at current location --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 map rooms List room IDs → names
xiao map show Show raw map metadata from the cloud
xiao rooms alias <id> "<name>" Add friendly-name alias
xiao rooms rename <id|alias> "<new>" Rename an alias
xiao schedule list Show parsed schedules
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 --port 8120
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)

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"

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

Origin (0,0) sits at the center of the map. Rooms typically fall in the 20000–40000 range on each axis. The easiest way to get real numbers is to draw a zone once inside the Xiaomi Home app and copy them.

# 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

# 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: High)

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
Stderr contains token / 401 / auth Re-run the same command once (CDP refresh fires on next call) xiao cloud-login, then retry
Stderr: Cannot connect to browser CDP on port 18800 Launch chromium --remote-debugging-port=18800 --user-data-dir=~/.xiao-chromium, user logs in at account.xiaomi.com Fall back to xiao cloud-login
State: 21 / "WashingMopPause" / "Water Tank Alert" User refills clean water / empties dirty water → xiao start 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" Vacuum hasn't mapped the house yet — user must run xiao start once first
Fan level printed as raw % (e.g. 68) Map via thresholds: ≤30=Silent, ≤55=Standard, ≤75=Medium, >75=Turbo

Mission Control (Web Dashboard)

xiao web --port 8120
# Open http://localhost:8120

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, settings panel.
  • Keyboard: S=start, X=stop, D=dock, F=find, R=refresh.
  • Mobile-first responsive, auto-refresh every 10s.

REST API

All endpoints at http://localhost:8120/api/ — use these for programmatic / JSON consumption:

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 Room list
/schedules GET Parsed schedules
/start POST Start clean
/stop POST Stop
/dock POST Return to dock
/clean/rooms POST {room_ids, fan, water}
/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}

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. Open https://account.xiaomi.com/pass/login in your own browser tab on
   remote-debugging port 18800. I'll solve the captcha + email 2FA once.
   Leave that tab open — it's the long-lived session xiao reuses.
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 reuses a long-lived Xiaomi session inside a Chromium browser you keep running in the background with CDP on port 18800.

The browser is just regular Chromium launched once with:

chromium --remote-debugging-port=18800 --user-data-dir=~/.xiao-chromium

…then logged into https://account.xiaomi.com manually. The login cookies persist in the user-data-dir, so future refreshes reuse them. No specific fork or external tool required.

Refresh flow (when the saved serviceToken is missing/expired, core/token_refresh.py):

  1. Connects via CDP at http://127.0.0.1:18800 (Playwright connect_over_cdp).
  2. Navigates to account.xiaomi.com/pass/serviceLogin?sid=xiaomiio&_json=true to obtain a fresh _sign — httpOnly cookies travel automatically.
  3. Submits a hidden <form> POST to serviceLoginAuth2 (form submission because fetch/xhr don't send httpOnly cookies). Response yields ssecurity + location.
  4. Follows location, which sets the serviceToken cookie on the redirect.
  5. Returns {userId, serviceToken, ssecurity} to the caller → persisted to config.toml.

If the browser isn't reachable, xiao falls back to a full Playwright login (headless Chromium). Manual trigger: xiao cloud-login.


Troubleshooting

(Agents: the machine-oriented decision tree is in Error Recovery above — this section is for humans.)

Cannot connect to browser CDP on port 18800 The Chromium instance that holds your Xiaomi session isn't running (or isn't listening on 18800). Launch it:

chromium --remote-debugging-port=18800 --user-data-dir=~/.xiao-chromium

Then log into https://account.xiaomi.com once in that window. Leave it running. 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 robot hasn't mapped your house yet. Run xiao start once end-to-end and let it learn the layout, then retry.

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.

Water tank alert (state 21) Refill the clean-water tank / empty the dirty-water tank at the base. Then xiao start to resume. 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:

  1. Washing mop — washes pads at base station (~2-3 min).
  2. Cleaning — sweeps/mops the house.
  3. Returning — back to dock.
  4. 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

xiao_cli-0.2.0.tar.gz (200.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

xiao_cli-0.2.0-py3-none-any.whl (83.5 kB view details)

Uploaded Python 3

File details

Details for the file xiao_cli-0.2.0.tar.gz.

File metadata

  • Download URL: xiao_cli-0.2.0.tar.gz
  • Upload date:
  • Size: 200.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for xiao_cli-0.2.0.tar.gz
Algorithm Hash digest
SHA256 9de1e1141a9cfcf230f3fbf10947be61ba0bbe6b6b4caae23aa63bb734b842bd
MD5 ff0cacc97f2af5d27cb855a2c25c19b0
BLAKE2b-256 80e36dfb95b04858865d82df28025f9dd38cbc56a408a992afc3822956909407

See more details on using hashes here.

Provenance

The following attestation bundles were made for xiao_cli-0.2.0.tar.gz:

Publisher: publish.yml on dacrypt/xiao

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xiao_cli-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: xiao_cli-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 83.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for xiao_cli-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 15e9c8dc49e52dbc052ff922e6df42efe220efc4a0ae830e1afedd93903e708c
MD5 02cf5039be5a9cb54995903da30decb9
BLAKE2b-256 8ab338ba7e664165361393b7aa1b023726ebfc4c2ca650c218e3252053f25d7b

See more details on using hashes here.

Provenance

The following attestation bundles were made for xiao_cli-0.2.0-py3-none-any.whl:

Publisher: publish.yml on dacrypt/xiao

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page