Metadata-Version: 2.4
Name: roborock-cli
Version: 0.1.11
Summary: Roborock 扫地机 CLI 控制工具
License-Expression: GPL-3.0-only
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: aiohttp<4,>=3.8.2
Requires-Dist: aiomqtt<3,>=2.5.0
Requires-Dist: certifi
Requires-Dist: click-shell~=2.1
Requires-Dist: click>=8
Requires-Dist: construct<3,>=2.10.57
Requires-Dist: mcp[cli]>=1.6.0
Requires-Dist: paho-mqtt<3.0.0,>=1.6.1
Requires-Dist: pycryptodomex~=3.18; sys_platform == 'darwin'
Requires-Dist: pycryptodome~=3.18
Requires-Dist: pyrate-limiter<5,>=4.0.0
Requires-Dist: vacuum-map-parser-roborock
Description-Content-Type: text/markdown

# roborock-cli

> Roborock 扫地机 CLI 控制工具

---

## 安装

### 前置：安装 uv

本工具通过 [uv](https://docs.astral.sh/uv/) 管理，需要先安装：

**macOS / Linux**

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

**Windows**

```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

安装后终端输入 `uv --version` 验证。

### 安装 roborock-cli

```bash
uv tool install roborock-cli
```

安装完成后即可直接使用：

```bash
roborock-cli --version           # 验证安装（短参数：-V）
roborock-cli auth                # 首次认证
roborock-cli devices             # 列出设备
```

---

## 更新

```bash
uv tool upgrade roborock-cli
```

---

## 卸载

### 移除程序

```bash
uv tool uninstall roborock-cli
```

### 清理认证和缓存数据

```bash
rm -rf ~/.cache/roborock-cli/
```

该目录包含认证信息和设备缓存，卸载程序后不会自动删除。

---

## 首次认证

所有命令（除 `auth` 本身）都需要先完成认证。

```bash
roborock-cli auth
```

**流程**：
1. 输入 Roborock 账号邮箱（可在Roborock APP上查看和修改账号邮箱）
2. 收到验证码邮件后输入验证码
3. 认证信息保存到 `~/.cache/roborock-cli/user_data.json`

**注意**：需要 TTY 终端，不支持非交互式环境。

---

## 命令间关系

```
auth ──────────────► 所有命令的前置条件（认证信息缓存在 ~/.cache/roborock-cli/）

devices ───────────► 获取设备名称 ──► 其他命令的 -d DEVICE 参数（模糊匹配）

status ────────────► rooms[].segment_id ──► start-clean -s（指定房间清洁）
       ────────────► dnd_enabled / dnd_start_time / dnd_end_time ──► set-dnd（修改勿扰）
       ────────────► volume ──► set-volume（修改音量）

routines ──────────► routines[].id ──► execute-routine（执行场景）
```

**典型数据流**：
1. `devices` 获取设备名 → 传给 `-d` 参数
2. `status` 获取房间 segment_id → 传给 `start-clean -s`
3. `routines` 获取场景 id → 传给 `execute-routine`

---

## 通用说明

### 输出格式
- **正常输出**：格式化 JSON（`indent=2, ensure_ascii=False`），中文原样输出，可直接用 `jq` 处理。
- **错误输出**：紧凑单行 JSON（无 indent），中文字符转义为 `\uXXXX`。

### 设备选择（`-d` 参数）
- **单设备时可省略**：自动选择唯一设备
- **多设备时必需**：否则报 DeviceError
- **模糊匹配**：子串匹配，不区分大小写。例如 `-d 客厅` 可匹配 "客厅扫地机"
- **匹配歧义**：如果多个设备名都包含关键词，报 AmbiguousDeviceError

### V1 协议要求

除 `auth` 和 `devices` 外，所有命令都要求设备支持 V1 协议。如果设备不支持，返回 DeviceError（退出码 3）：`"设备不支持 V1 协议"`。绝大多数近年 Roborock 设备均支持 V1 协议。

### 退出码

| 退出码 | 含义 | 触发条件 |
|--------|------|----------|
| 0 | 成功 | — |
| 1 | 通用业务错误 | 设备通信失败等 |
| 2 | 认证错误 | 认证过期或缓存丢失 |
| 3 | 设备错误 | 设备未找到/多设备未指定/匹配歧义 |
| 4 | 参数无效 | 音量越界、时间格式错误等 |
| 5 | 内部错误 | 未预期异常 |
| 130 | 用户中断 | Ctrl+C / SIGINT（128 + 信号 2） |

错误输出格式：
```json
{"error": "错误描述", "exit_code": 3}
```

### 查看帮助

所有命令都支持 `-h` 查看用法：

```bash
roborock-cli -h              # 查看所有可用命令
roborock-cli start-clean -h  # 查看子命令的参数说明
```

---

## 查询命令（5 个）

### auth — 交互式认证

首次使用或认证过期时运行，需要 TTY 终端。

```
usage: roborock-cli auth [-h]
```

| 选项 | 说明 |
|------|------|
| `-h, --help` | 显示帮助 |

**无 JSON 输出**（交互式命令）。

---

### devices — 列出所有设备

```
usage: roborock-cli devices [-h]
```

| 选项 | 说明 |
|------|------|
| `-h, --help` | 显示帮助 |

**输出示例**（实际运行）：
```json
{
  "count": 1,
  "devices": [
    {
      "name": "Pearl Plus V5",
      "duid": "1Nky7XEsNJn75gTGWtxiIs",
      "model": "roborock.vacuum.a86v5",
      "firmware": "02.34.02"
    }
  ]
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| `count` | int | 设备总数 |
| `devices[].name` | string | 设备名称 → **用于其他命令的 `-d` 参数** |
| `devices[].duid` | string | 设备唯一 ID |
| `devices[].model` | string\|null | 设备型号 |
| `devices[].firmware` | string\|null | 固件版本 |

---

### status — 获取设备状态

```
usage: roborock-cli status [-h] [-d DEVICE]
```

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称（模糊匹配） |
| `-h, --help` | 显示帮助 |

并行刷新 8 个 trait（status, dnd, clean_summary, sound_volume, rooms, maps, consumables, device_features），某个 trait 失败不影响其他。其中 `device_features` 仅用于 SDK 内部状态更新，不产生输出字段。

**输出示例**（实际运行，完整字段）：
```json
{
  "device_name": "Pearl Plus V5",

  "battery": {"value": 100, "description": "电池电量 (%)"},
  "charge_status": {"value": 1, "description": "充电状态 (0=未充电, 1=充电中, 2=充电完成)"},
  "clean_percent": {"value": 0, "description": "清洁任务完成百分比 (%)"},
  "in_cleaning": {"value": "complete", "description": "清洁状态 (complete/global_clean_not_complete/zone_clean_not_complete/segment_clean_not_complete)"},
  "in_returning": {"value": 0, "description": "是否在返回途中 (0=否, 1=是)"},
  "avoid_count": {"value": 0, "description": "当前任务避障次数"},
  "repeat": {"value": 1, "description": "当前任务重复遍数"},
  "map_present": {"value": 1, "description": "是否有地图 (0=无, 1=有)"},
  "dock_type": {"value": "p10_pro_dock", "description": "基座类型 (no_dock/auto_empty_dock/empty_wash_fill_dock/p10_pro_dock 等)"},
  "dock_error_status": {"value": "ok", "description": "基座错误状态 (ok/duct_blockage/water_empty/waste_water_tank_full 等)"},
  "dust_collection_status": {"value": 0, "description": "集尘状态 (0=未集尘, 1=集尘中)"},
  "auto_dust_collection": {"value": 1, "description": "自动集尘开关 (0=关闭, 1=开启)"},
  "wash_status": {"value": 0, "description": "洗拖布状态 (0=空闲, 1=洗涤中)"},
  "wash_phase": {"value": 0, "description": "洗拖布阶段 (0=空闲)"},
  "wash_ready": {"value": 1, "description": "是否准备好洗拖布 (0=否, 1=是)"},
  "dry_status": {"value": 0, "description": "热风烘干状态 (0=关闭, 1=烘干中)"},
  "water_box_status": {"value": 1, "description": "水箱安装状态 (0=未安装, 1=已安装)"},
  "water_box_carriage_status": {"value": 0, "description": "水箱托架状态 (0=正常)"},
  "water_shortage_status": {"value": 0, "description": "缺水状态 (0=正常)"},
  "is_locating": {"value": 0, "description": "是否正在定位 (0=否, 1=是)"},
  "lock_status": {"value": 0, "description": "童锁状态 (0=未锁定, 1=锁定)"},
  "mop_forbidden_enable": {"value": 0, "description": "拖地禁区是否启用 (0=关闭, 1=启用)"},
  "collision_avoid_status": {"value": 1, "description": "避障功能 (0=关闭, 1=开启)"},

  "state": {"value": "charging", "description": "设备状态"},
  "error_code": {"value": "none", "description": "错误状态"},
  "clean_area_m2": {"value": 0.3, "description": "清洁面积 (m²)"},
  "clean_time_minutes": {"value": 0.4, "description": "清洁时长 (分钟)"},
  "fan_speed_name": {"value": "balanced", "description": "吸力档位"},
  "water_mode_name": {"value": "low", "description": "拖布湿度"},
  "mop_route_name": {"value": "standard", "description": "拖地路线"},
  "clear_water_box_status": {"value": "out_of_water", "description": "清水箱状态"},
  "dirty_water_box_status": {"value": "okay", "description": "污水箱状态"},
  "dust_bag_status": {"value": "okay", "description": "尘袋状态"},

  "dnd_enabled": {"value": true, "description": "勿扰模式"},
  "dnd_start_time": {"value": "22:00", "description": "勿扰开始时间"},   // dnd_enabled=false 时 value 为 null
  "dnd_end_time": {"value": "08:00", "description": "勿扰结束时间"},     // dnd_enabled=false 时 value 为 null

  "total_clean_time_hours": {"value": 2.0, "description": "累计清洁时长 (小时)"},
  "total_clean_area_m2": {"value": 73.7, "description": "累计清洁面积 (m²)"},
  "total_clean_count": {"value": 66, "description": "累计清洁次数"},
  "last_clean_begin": {"value": "2026-03-16T14:09:10+00:00", "description": "最近清洁开始时间"},  // 条件性：无清洁记录时不出现
  "last_clean_end": {"value": "2026-03-16T14:09:37+00:00", "description": "最近清洁结束时间"},    // 条件性：无清洁记录时不出现

  "volume": {"value": 45, "description": "音量"},

  "room_count": {"value": 1, "description": "房间数量"},
  "rooms": {"value": [{"segment_id": 16, "iot_id": "25350665", "name": "<未命名>"}], "description": "房间列表"},

  "current_map_flag": {"value": 0, "description": "当前地图 ID"},
  "maps": {"value": [{"map_flag": 0, "name": "<未命名>", "is_current": true}], "description": "地图列表"},

  "main_brush_hours": {"value": 3.1, "description": "主刷使用时长 (小时)"},
  "side_brush_hours": {"value": 2.1, "description": "边刷使用时长 (小时)"},
  "filter_hours": {"value": 3.1, "description": "滤网使用时长 (小时)"}
}
```

**字段分组说明**：

| 分组 | 字段 | 说明 |
|------|------|------|
| **基本信息** | `device_name` | 设备名称 |
| **status trait** | `battery` ~ `collision_avoid_status`（23 个白名单字段） | 设备实时状态 |
| **computed** | `state`, `error_code`, `clean_area_m2`, `clean_time_minutes`, `fan_speed_name`, `water_mode_name`, `mop_route_name` | 计算/转换后的友好值 |
| **dss** | `clear_water_box_status`, `dirty_water_box_status`, `dust_bag_status` | 水箱/尘袋状态 |
| **dnd trait** | `dnd_enabled`, `dnd_start_time`, `dnd_end_time` | 勿扰模式（关闭时 `start/end_time` 的 value 为 null） |
| **clean_summary** | `total_clean_time_hours` ~ `last_clean_end` | 清洁统计（`last_clean_begin/end` 条件性：无清洁记录时不出现） |
| **sound_volume** | `volume` | 音量（0-100） |
| **rooms trait** | `room_count`, `rooms` | 房间列表（**segment_id → start-clean -s**） |
| **maps trait** | `current_map_flag`, `maps` | 地图列表 |
| **consumables** | `main_brush_hours`, `side_brush_hours`, `filter_hours` | 耗材使用时长 |

> 每个字段格式统一为 `{"value": <值>, "description": "中文说明"}`，`device_name` 除外。

---

### map — 获取地图

```
usage: roborock-cli map [-h] [-d DEVICE] [--save SAVE]
```

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `--save SAVE` | 保存地图图片到指定文件（PNG 格式） |
| `-h, --help` | 显示帮助 |

**不含 `--save` 的输出**（实际运行）：
```json
{
  "device_name": "Pearl Plus V5",
  "image_size_bytes": 43699,
  "has_map_data": true,
  "vacuum_position": {
    "x": 25598,
    "y": 26133
  },
  "charger_position": {
    "x": 25602,
    "y": 26311
  }
}
```

**含 `--save` 的输出**：
```json
{
  "saved": "map.png",
  "metadata": {
    "device_name": "Pearl Plus V5",
    "image_size_bytes": 43699,
    "has_map_data": true,
    "vacuum_position": {"x": 25598, "y": 26133},
    "charger_position": {"x": 25602, "y": 26311}
  }
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| `device_name` | string | 设备名称 |
| `image_size_bytes` | int | 地图图片大小（字节） |
| `has_map_data` | bool | 是否包含地图数据 |
| `vacuum_position.x/y` | int\|null | 扫地机位置坐标（**条件性**：仅 `has_map_data=true` 时出现） |
| `charger_position.x/y` | int\|null | 充电座位置坐标（**条件性**：仅 `has_map_data=true` 时出现） |
| `saved` | string | 保存路径（仅 `--save` 时） |

---

### routines — 列出智能场景

```
usage: roborock-cli routines [-h] [-d DEVICE]
```

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

**输出示例**（实际运行）：
```json
{
  "device_name": "Pearl Plus V5",
  "count": 4,
  "routines": [
    {"id": 13258044, "name": "饭后清洁"},
    {"id": 13258039, "name": "深度清洁"},
    {"id": 13258029, "name": "先扫后拖"},
    {"id": 13258027, "name": "全屋清洁"}
  ]
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| `device_name` | string | 设备名称 |
| `count` | int | 场景总数 |
| `routines[].id` | int | 场景 ID → **用于 `execute-routine` 命令** |
| `routines[].name` | string | 场景名称 |

---

## 控制命令（10 个）

所有控制命令成功时返回 `{"status": "ok", ...}`。

### start-clean — 开始清洁

```
usage: roborock-cli start-clean [-h] [-d DEVICE] [-s SEGMENTS [SEGMENTS ...]] [-r REPEAT]
```

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-s SEGMENTS [SEGMENTS ...], --segments SEGMENTS [SEGMENTS ...]` | 房间 segment_id 列表（不指定则全屋清洁） |
| `-r REPEAT, --repeat REPEAT` | 清洁遍数（默认 1） |
| `-h, --help` | 显示帮助 |

**全屋清洁**：
```bash
roborock-cli start-clean
```
```json
{"status": "ok", "action": "full_clean"}
```

**指定房间清洁**（segment_id 从 `status` 命令的 `rooms` 字段获取）：
```bash
roborock-cli start-clean -s 16 -r 2
```
```json
{"status": "ok", "action": "segment_clean", "segments": [16], "repeat": 2}
```

---

### stop-clean — 停止清洁

```
usage: roborock-cli stop-clean [-h] [-d DEVICE]
```

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

```json
{"status": "ok", "action": "stop"}
```

---

### pause-clean — 暂停清洁

```
usage: roborock-cli pause-clean [-h] [-d DEVICE]
```

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

```json
{"status": "ok", "action": "pause"}
```

---

### resume-clean — 恢复清洁

```
usage: roborock-cli resume-clean [-h] [-d DEVICE]
```

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

```json
{"status": "ok", "action": "resume"}
```

> 技术细节：`resume-clean` 底层发送 `APP_START` 命令（与 `start-clean` 全屋清洁相同）。这是 Roborock 协议的设计 — `APP_START` 在暂停状态下等同于恢复当前任务。

---

### go-home — 回充电座

```
usage: roborock-cli go-home [-h] [-d DEVICE]
```

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

```json
{"status": "ok", "action": "go_home"}
```

---

### find-robot — 寻找机器人

让扫地机发出声音，方便寻找位置。

```
usage: roborock-cli find-robot [-h] [-d DEVICE]
```

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

```json
{"status": "ok", "action": "find_robot"}
```

---

### dock-action — 基座操作

控制充电基座的集尘和洗拖布功能。

```
usage: roborock-cli dock-action [-h] [-d DEVICE]
                                {start_collect_dust,stop_collect_dust,start_wash,stop_wash}
```

| 参数 | 类型 | 说明 |
|------|------|------|
| `action` | 位置参数（必填） | `start_collect_dust` / `stop_collect_dust` / `start_wash` / `stop_wash` |

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

```bash
roborock-cli dock-action start_collect_dust   # 开始集尘
roborock-cli dock-action stop_collect_dust    # 停止集尘
roborock-cli dock-action start_wash           # 开始洗拖布
roborock-cli dock-action stop_wash            # 停止洗拖布
```

```json
{"status": "ok", "action": "start_collect_dust"}
```

---

### set-volume — 设置音量

```
usage: roborock-cli set-volume [-h] [-d DEVICE] volume
```

| 参数 | 类型 | 说明 |
|------|------|------|
| `volume` | 位置参数（必填） | 音量值，整数 0-100 |

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

```bash
roborock-cli set-volume 50
```
```json
{"status": "ok", "volume": 50}
```

> 音量超出 0-100 范围时返回 ParamError（退出码 4）。

---

### set-dnd — 设置勿扰模式

```
usage: roborock-cli set-dnd [-h] [--start START] [--end END] [-d DEVICE]
                            {enable,disable}
```

| 参数 | 类型 | 说明 |
|------|------|------|
| `action` | 位置参数（必填） | `enable` 或 `disable` |

| 选项 | 说明 |
|------|------|
| `--start START` | 开始时间，HH:MM 格式（enable 时必需） |
| `--end END` | 结束时间，HH:MM 格式（enable 时必需） |
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

**启用勿扰**：
```bash
roborock-cli set-dnd enable --start 22:00 --end 08:00
```
```json
{"status": "ok", "action": "enable", "time": "22:00-08:00"}
```

**关闭勿扰**：
```bash
roborock-cli set-dnd disable
```
```json
{"status": "ok", "action": "disable"}
```

> enable 时必须同时提供 `--start` 和 `--end`，否则返回 ParamError。
> 时间格式验证：0 ≤ 小时 ≤ 23，0 ≤ 分钟 ≤ 59。

---

### execute-routine — 执行智能场景

```
usage: roborock-cli execute-routine [-h] [-d DEVICE] routine_id
```

| 参数 | 类型 | 说明 |
|------|------|------|
| `routine_id` | 位置参数（必填） | 场景 ID（从 `routines` 命令获取） |

| 选项 | 说明 |
|------|------|
| `-d DEVICE, --device DEVICE` | 设备名称 |
| `-h, --help` | 显示帮助 |

```bash
# 先查询场景 ID
roborock-cli routines
# 执行指定场景
roborock-cli execute-routine 13258044
```
```json
{"status": "ok", "routine_id": 13258044}
```

---

## 快速参考表

| 命令 | 位置参数 | 可选参数 | 输出类型 |
|------|---------|---------|---------|
| `auth` | — | `-h` | 交互式（无 JSON） |
| `devices` | — | `-h` | `{count, devices[]}` |
| `status` | — | `-d`, `-h` | `{device_name, ~50 fields}` |
| `map` | — | `-d`, `--save FILE`, `-h` | `{device_name, image_size, positions}` |
| `routines` | — | `-d`, `-h` | `{device_name, count, routines[]}` |
| `start-clean` | — | `-d`, `-s ID [ID...]`, `-r N`, `-h` | `{status, action, ...}` |
| `stop-clean` | — | `-d`, `-h` | `{status, action}` |
| `pause-clean` | — | `-d`, `-h` | `{status, action}` |
| `resume-clean` | — | `-d`, `-h` | `{status, action}` |
| `go-home` | — | `-d`, `-h` | `{status, action}` |
| `find-robot` | — | `-d`, `-h` | `{status, action}` |
| `dock-action` | `ACTION`（4 选 1） | `-d`, `-h` | `{status, action}` |
| `set-volume` | `VOLUME`（0-100） | `-d`, `-h` | `{status, volume}` |
| `set-dnd` | `{enable,disable}` | `-d`, `--start HH:MM`, `--end HH:MM`, `-h` | `{status, action, ...}` |
| `execute-routine` | `ROUTINE_ID` | `-d`, `-h` | `{status, routine_id}` |

---

## 典型工作流

### 单设备全屋清洁

```bash
# 只有一个设备时，-d 可省略
roborock-cli status | jq '.state.value'      # 确认设备空闲
roborock-cli start-clean                       # 开始全屋清洁
roborock-cli status | jq '.battery.value'      # 查看电量
roborock-cli go-home                           # 清洁完成后回充
```

### 多设备指定房间清洁

```bash
# 1. 查看所有设备
roborock-cli devices | jq '.devices[].name'

# 2. 查看目标设备的房间列表
roborock-cli status -d "Pearl" | jq '.rooms.value'
# 输出: [{"segment_id": 16, "iot_id": "25350665", "name": "<未命名>"}]

# 3. 指定房间清洁 2 遍
roborock-cli start-clean -d "Pearl" -s 16 -r 2

# 4. 暂停/恢复/停止
roborock-cli pause-clean -d "Pearl"
roborock-cli resume-clean -d "Pearl"
roborock-cli stop-clean -d "Pearl"
```

### 勿扰模式管理

```bash
# 查看当前勿扰设置
roborock-cli status | jq '{dnd_enabled, dnd_start_time, dnd_end_time}'

# 设置 22:00-08:00 勿扰
roborock-cli set-dnd enable --start 22:00 --end 08:00

# 关闭勿扰
roborock-cli set-dnd disable
```

### 智能场景执行

```bash
# 1. 列出可用场景
roborock-cli routines | jq '.routines[] | "\(.id) → \(.name)"'
# 输出:
# "13258044 → 饭后清洁"
# "13258039 → 深度清洁"

# 2. 执行场景
roborock-cli execute-routine 13258044
```

### 基座操作

```bash
# 手动触发集尘
roborock-cli dock-action start_collect_dust
# 手动洗拖布
roborock-cli dock-action start_wash
```

### 脚本集成（检查退出码）

```bash
if roborock-cli start-clean -s 16 >/dev/null; then
  echo "清洁已启动"
else
  echo "启动失败，退出码: $?"
fi
```

---

## 缓存与环境变量

### 缓存目录

默认路径：`~/.cache/roborock-cli/`

```
~/.cache/roborock-cli/
├── user_data.json    — 认证信息（邮箱 + token），权限 0o600（仅所有者读写）
└── device_cache.pkl  — 设备信息缓存（避免 API 频率限制），约 3KB，权限由 umask 决定（通常 0o644）
```

- `user_data.json`：`auth` 命令写入，其他命令读取。删除后需重新认证。
- `device_cache.pkl`：缓存 `home_data`（设备列表、房间信息等），避免每次调用都请求 Roborock API（API 限制 5 次/小时、40 次/天）。删除后下次调用会自动重建。

### 环境变量

| 环境变量 | 默认值 | 说明 |
|---------|--------|------|
| `ROBOROCK_AUTH_CACHE` | `~/.cache/roborock-cli/user_data.json` | 认证缓存文件路径 |
| `ROBOROCK_DEVICE_CACHE` | `~/.cache/roborock-cli/device_cache.pkl` | 设备缓存文件路径 |

可用于自定义缓存位置，例如：

```bash
export ROBOROCK_AUTH_CACHE=/path/to/my/auth.json
export ROBOROCK_DEVICE_CACHE=/path/to/my/cache.pkl
roborock-cli devices
```

---

## 故障排除

### Windows：应用程序控制策略阻止运行

如果在 Windows 上运行 `roborock-cli` 出现以下错误：

```
error: Failed to query Python interpreter at `...\uv\cache\...\python.exe`
  Caused by: 应用程序控制策略已阻止此文件。 (os error 4551)
```

这是 Windows 安全策略阻止了 `uv` 缓存目录下的特定 Python 解释器。解决方法：

**方法 1：清理 uv 缓存（推荐）**

```bash
uv cache clean
uv tool install roborock-cli
roborock-cli auth
```

**方法 2：指定系统 Python**

先从 [python.org](https://www.python.org/downloads/) 安装 Python 3.12+，然后：

```bash
uv tool install --python python3.12 roborock-cli
```

### 连接超时（退出码 1）

**表现**：命令返回 `{"error": "连接超时（30.0秒）", "exit_code": 1}`

**原因**：CLI 在 30 秒内未能连接到 Roborock 云服务器或设备。可能原因包括网络不稳定、设备离线、防火墙阻挡。

**解决**：
1. 确认网络连接正常
2. 确认设备在线（通过 Roborock App 检查）
3. 重试命令

---

### 认证过期（退出码 2）

**表现**：命令返回 `{"error": "认证已过期，请重新运行 roborock-cli auth", "exit_code": 2}`

**解决**：重新运行认证命令：

```bash
roborock-cli auth
```

### API 频率限制（9002 错误）

**表现**：命令返回包含 "request too frequency" 的错误

**原因**：Roborock API 限制 `get_home_data` 调用频率（5 次/小时、40 次/天）。通常是设备缓存未正常工作导致每次都重新请求 API。

**诊断**：

```bash
ls -la ~/.cache/roborock-cli/device_cache.pkl
```

如果文件大小为 0 字节，说明缓存写入失败。

**解决**：删除缓存文件后重试：

```bash
rm ~/.cache/roborock-cli/device_cache.pkl
roborock-cli devices
```

### 设备未找到（退出码 3）

**表现**：`{"error": "未找到设备 'xxx'，可用设备: [...]", "exit_code": 3}`

**检查**：
1. 确认设备已绑定到 Roborock 国际版账户
2. 确认设备在线（通过 Roborock App 检查）
3. `-d` 参数使用的名称是否与 `devices` 输出的名称匹配（子串即可，不区分大小写）

### 命令执行缓慢（约 5 秒延迟）

**原因**：python-roborock SDK 会先尝试局域网直连（端口 58867，超时 5 秒），失败后回退到 MQTT 云端。如果设备不在同一网段或防火墙阻挡，每个命令都会多等约 5 秒。

**说明**：这是 SDK 的设计行为，非 roborock-cli 的 bug。

---

## 要求

- Python >= 3.12
- Roborock 账户（在Roborock APP中绑定邮箱）
- 网络连接（需要访问 Roborock 云服务器）

## 许可证

GPL-3.0-only（vendoring 了 [python-roborock](https://github.com/humbertogontijo/python-roborock)）
