roborock-cli 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. roborock_cli/__init__.py +3 -0
  2. roborock_cli/__main__.py +76 -0
  3. roborock_cli/_vendor/VERSION +6 -0
  4. roborock_cli/_vendor/__init__.py +0 -0
  5. roborock_cli/_vendor/roborock/__init__.py +27 -0
  6. roborock_cli/_vendor/roborock/broadcast_protocol.py +114 -0
  7. roborock_cli/_vendor/roborock/callbacks.py +130 -0
  8. roborock_cli/_vendor/roborock/cli.py +1338 -0
  9. roborock_cli/_vendor/roborock/const.py +84 -0
  10. roborock_cli/_vendor/roborock/data/__init__.py +9 -0
  11. roborock_cli/_vendor/roborock/data/b01_q10/__init__.py +2 -0
  12. roborock_cli/_vendor/roborock/data/b01_q10/b01_q10_code_mappings.py +213 -0
  13. roborock_cli/_vendor/roborock/data/b01_q10/b01_q10_containers.py +102 -0
  14. roborock_cli/_vendor/roborock/data/b01_q7/__init__.py +2 -0
  15. roborock_cli/_vendor/roborock/data/b01_q7/b01_q7_code_mappings.py +303 -0
  16. roborock_cli/_vendor/roborock/data/b01_q7/b01_q7_containers.py +302 -0
  17. roborock_cli/_vendor/roborock/data/code_mappings.py +198 -0
  18. roborock_cli/_vendor/roborock/data/containers.py +530 -0
  19. roborock_cli/_vendor/roborock/data/dyad/__init__.py +2 -0
  20. roborock_cli/_vendor/roborock/data/dyad/dyad_code_mappings.py +102 -0
  21. roborock_cli/_vendor/roborock/data/dyad/dyad_containers.py +28 -0
  22. roborock_cli/_vendor/roborock/data/v1/__init__.py +3 -0
  23. roborock_cli/_vendor/roborock/data/v1/v1_clean_modes.py +192 -0
  24. roborock_cli/_vendor/roborock/data/v1/v1_code_mappings.py +644 -0
  25. roborock_cli/_vendor/roborock/data/v1/v1_containers.py +800 -0
  26. roborock_cli/_vendor/roborock/data/zeo/__init__.py +2 -0
  27. roborock_cli/_vendor/roborock/data/zeo/zeo_code_mappings.py +138 -0
  28. roborock_cli/_vendor/roborock/data/zeo/zeo_containers.py +0 -0
  29. roborock_cli/_vendor/roborock/device_features.py +668 -0
  30. roborock_cli/_vendor/roborock/devices/README.md +41 -0
  31. roborock_cli/_vendor/roborock/devices/__init__.py +11 -0
  32. roborock_cli/_vendor/roborock/devices/cache.py +143 -0
  33. roborock_cli/_vendor/roborock/devices/device.py +240 -0
  34. roborock_cli/_vendor/roborock/devices/device_manager.py +269 -0
  35. roborock_cli/_vendor/roborock/devices/file_cache.py +79 -0
  36. roborock_cli/_vendor/roborock/devices/rpc/__init__.py +14 -0
  37. roborock_cli/_vendor/roborock/devices/rpc/a01_channel.py +94 -0
  38. roborock_cli/_vendor/roborock/devices/rpc/b01_q10_channel.py +57 -0
  39. roborock_cli/_vendor/roborock/devices/rpc/b01_q7_channel.py +101 -0
  40. roborock_cli/_vendor/roborock/devices/rpc/v1_channel.py +457 -0
  41. roborock_cli/_vendor/roborock/devices/traits/__init__.py +28 -0
  42. roborock_cli/_vendor/roborock/devices/traits/a01/__init__.py +191 -0
  43. roborock_cli/_vendor/roborock/devices/traits/b01/__init__.py +12 -0
  44. roborock_cli/_vendor/roborock/devices/traits/b01/q10/__init__.py +76 -0
  45. roborock_cli/_vendor/roborock/devices/traits/b01/q10/command.py +32 -0
  46. roborock_cli/_vendor/roborock/devices/traits/b01/q10/common.py +115 -0
  47. roborock_cli/_vendor/roborock/devices/traits/b01/q10/status.py +32 -0
  48. roborock_cli/_vendor/roborock/devices/traits/b01/q10/vacuum.py +81 -0
  49. roborock_cli/_vendor/roborock/devices/traits/b01/q7/__init__.py +136 -0
  50. roborock_cli/_vendor/roborock/devices/traits/b01/q7/clean_summary.py +75 -0
  51. roborock_cli/_vendor/roborock/devices/traits/traits_mixin.py +64 -0
  52. roborock_cli/_vendor/roborock/devices/traits/v1/__init__.py +344 -0
  53. roborock_cli/_vendor/roborock/devices/traits/v1/child_lock.py +29 -0
  54. roborock_cli/_vendor/roborock/devices/traits/v1/clean_summary.py +83 -0
  55. roborock_cli/_vendor/roborock/devices/traits/v1/command.py +38 -0
  56. roborock_cli/_vendor/roborock/devices/traits/v1/common.py +172 -0
  57. roborock_cli/_vendor/roborock/devices/traits/v1/consumeable.py +48 -0
  58. roborock_cli/_vendor/roborock/devices/traits/v1/device_features.py +74 -0
  59. roborock_cli/_vendor/roborock/devices/traits/v1/do_not_disturb.py +41 -0
  60. roborock_cli/_vendor/roborock/devices/traits/v1/dust_collection_mode.py +13 -0
  61. roborock_cli/_vendor/roborock/devices/traits/v1/flow_led_status.py +29 -0
  62. roborock_cli/_vendor/roborock/devices/traits/v1/home.py +285 -0
  63. roborock_cli/_vendor/roborock/devices/traits/v1/led_status.py +43 -0
  64. roborock_cli/_vendor/roborock/devices/traits/v1/map_content.py +83 -0
  65. roborock_cli/_vendor/roborock/devices/traits/v1/maps.py +80 -0
  66. roborock_cli/_vendor/roborock/devices/traits/v1/network_info.py +55 -0
  67. roborock_cli/_vendor/roborock/devices/traits/v1/rooms.py +105 -0
  68. roborock_cli/_vendor/roborock/devices/traits/v1/routines.py +26 -0
  69. roborock_cli/_vendor/roborock/devices/traits/v1/smart_wash_params.py +13 -0
  70. roborock_cli/_vendor/roborock/devices/traits/v1/status.py +101 -0
  71. roborock_cli/_vendor/roborock/devices/traits/v1/valley_electricity_timer.py +44 -0
  72. roborock_cli/_vendor/roborock/devices/traits/v1/volume.py +27 -0
  73. roborock_cli/_vendor/roborock/devices/traits/v1/wash_towel_mode.py +13 -0
  74. roborock_cli/_vendor/roborock/devices/transport/__init__.py +8 -0
  75. roborock_cli/_vendor/roborock/devices/transport/channel.py +32 -0
  76. roborock_cli/_vendor/roborock/devices/transport/local_channel.py +295 -0
  77. roborock_cli/_vendor/roborock/devices/transport/mqtt_channel.py +118 -0
  78. roborock_cli/_vendor/roborock/diagnostics.py +166 -0
  79. roborock_cli/_vendor/roborock/exceptions.py +95 -0
  80. roborock_cli/_vendor/roborock/map/__init__.py +7 -0
  81. roborock_cli/_vendor/roborock/map/map_parser.py +123 -0
  82. roborock_cli/_vendor/roborock/mqtt/__init__.py +10 -0
  83. roborock_cli/_vendor/roborock/mqtt/health_manager.py +60 -0
  84. roborock_cli/_vendor/roborock/mqtt/roborock_session.py +463 -0
  85. roborock_cli/_vendor/roborock/mqtt/session.py +108 -0
  86. roborock_cli/_vendor/roborock/protocol.py +558 -0
  87. roborock_cli/_vendor/roborock/protocols/__init__.py +3 -0
  88. roborock_cli/_vendor/roborock/protocols/a01_protocol.py +74 -0
  89. roborock_cli/_vendor/roborock/protocols/b01_q10_protocol.py +87 -0
  90. roborock_cli/_vendor/roborock/protocols/b01_q7_protocol.py +81 -0
  91. roborock_cli/_vendor/roborock/protocols/v1_protocol.py +271 -0
  92. roborock_cli/_vendor/roborock/py.typed +0 -0
  93. roborock_cli/_vendor/roborock/roborock_message.py +246 -0
  94. roborock_cli/_vendor/roborock/roborock_typing.py +382 -0
  95. roborock_cli/_vendor/roborock/util.py +54 -0
  96. roborock_cli/_vendor/roborock/web_api.py +761 -0
  97. roborock_cli/cli.py +715 -0
  98. roborock_cli/connection.py +202 -0
  99. roborock_cli/helpers.py +71 -0
  100. roborock_cli/server.py +759 -0
  101. roborock_cli/setup_auth.py +92 -0
  102. roborock_cli-0.1.1.dist-info/METADATA +172 -0
  103. roborock_cli-0.1.1.dist-info/RECORD +106 -0
  104. roborock_cli-0.1.1.dist-info/WHEEL +4 -0
  105. roborock_cli-0.1.1.dist-info/entry_points.txt +2 -0
  106. roborock_cli-0.1.1.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,202 @@
1
+ """
2
+ 连接管理模块
3
+
4
+ 负责:
5
+ - 从 JSON 缓存加载 UserData
6
+ - 创建 DeviceManager
7
+ - 设备发现和模糊匹配
8
+ - 连接生命周期管理
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import ssl
14
+ from contextlib import asynccontextmanager
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ import aiohttp
19
+ import certifi
20
+ from roborock_cli._vendor.roborock.data import UserData
21
+ from roborock_cli._vendor.roborock.devices.device_manager import create_device_manager, UserParams
22
+ from roborock_cli._vendor.roborock.devices.file_cache import FileCache
23
+
24
+
25
+ class AuthExpiredError(Exception):
26
+ """认证已过期,需要重新运行 setup_auth"""
27
+
28
+ def __init__(self):
29
+ super().__init__("认证已过期,请重新运行 `roborock-cli auth`")
30
+
31
+
32
+ class NoDeviceError(Exception):
33
+ """未发现设备"""
34
+
35
+ def __init__(self):
36
+ super().__init__("未发现任何设备,请确认设备已绑定到账户")
37
+
38
+
39
+ class AmbiguousDeviceError(Exception):
40
+ """设备名称匹配到多个设备"""
41
+
42
+ def __init__(self, name: str, matches: list[str]):
43
+ self.name = name
44
+ self.matches = matches
45
+ super().__init__(
46
+ f"设备名称 '{name}' 匹配到多个设备: {matches},请使用更精确的名称"
47
+ )
48
+
49
+
50
+ class DeviceNotFoundError(Exception):
51
+ """设备名称未匹配到任何设备"""
52
+
53
+ def __init__(self, name: str, available: list[str]):
54
+ self.name = name
55
+ self.available = available
56
+ super().__init__(
57
+ f"未找到设备 '{name}',可用设备: {available}"
58
+ )
59
+
60
+
61
+ class ConnectionManager:
62
+ """连接管理器"""
63
+
64
+ def __init__(self, auth_cache_path: Path, device_cache_path: Path):
65
+ self._auth_cache_path = auth_cache_path
66
+ self._device_cache_path = device_cache_path
67
+ self._manager: Any = None # DeviceManager
68
+ self._cache: FileCache | None = None
69
+ self._user_params: UserParams | None = None
70
+ self._session: aiohttp.ClientSession | None = None
71
+
72
+ @classmethod
73
+ def from_env(cls) -> "ConnectionManager":
74
+ """从环境变量创建连接管理器"""
75
+ auth_cache = Path(
76
+ os.environ.get(
77
+ "ROBOROCK_AUTH_CACHE",
78
+ Path.home() / ".cache" / "roborock-cli" / "user_data.json"
79
+ )
80
+ )
81
+ device_cache = Path(
82
+ os.environ.get(
83
+ "ROBOROCK_DEVICE_CACHE",
84
+ Path.home() / ".cache" / "roborock-cli" / "device_cache.pkl"
85
+ )
86
+ )
87
+ return cls(auth_cache, device_cache)
88
+
89
+ async def ensure_connected(self) -> None:
90
+ """确保已连接,如果未连接则建立连接"""
91
+ if self._manager is not None:
92
+ return
93
+
94
+ # 1. 从 JSON 文件加载 UserData
95
+ if not self._auth_cache_path.exists():
96
+ raise AuthExpiredError()
97
+
98
+ try:
99
+ with open(self._auth_cache_path) as f:
100
+ data = json.load(f)
101
+ email = data["email"]
102
+ user_data = UserData.from_dict(data["user_data"])
103
+ except (json.JSONDecodeError, KeyError) as e:
104
+ raise AuthExpiredError() from e
105
+
106
+ # 2. 构建 UserParams(不传 base_url,让 SDK 自动发现)
107
+ self._user_params = UserParams(
108
+ username=email,
109
+ user_data=user_data,
110
+ )
111
+
112
+ # 3. 创建缓存目录
113
+ self._device_cache_path.parent.mkdir(parents=True, exist_ok=True)
114
+
115
+ # 4. 创建 FileCache
116
+ self._cache = FileCache(self._device_cache_path)
117
+
118
+ # 5. 创建带 SSL 证书的 session(解决 macOS 证书问题)
119
+ ssl_ctx = ssl.create_default_context(cafile=certifi.where())
120
+ connector = aiohttp.TCPConnector(ssl=ssl_ctx)
121
+ self._session = aiohttp.ClientSession(connector=connector)
122
+
123
+ # 6. 创建 DeviceManager(内部已调用 discover_devices)
124
+ self._manager = await create_device_manager(
125
+ self._user_params,
126
+ cache=self._cache,
127
+ session=self._session,
128
+ )
129
+
130
+ # 7. 检查设备列表
131
+ devices = await self._manager.get_devices()
132
+ if not devices:
133
+ raise NoDeviceError()
134
+
135
+ async def list_devices(self) -> list[Any]:
136
+ """返回所有已发现设备"""
137
+ if self._manager is None:
138
+ await self.ensure_connected()
139
+ return await self._manager.get_devices()
140
+
141
+ async def get_device(self, device_name: str | None = None) -> Any:
142
+ """
143
+ 获取指定设备
144
+
145
+ Args:
146
+ device_name: 设备名称(模糊匹配),为 None 时自动选择单设备
147
+
148
+ Returns:
149
+ RoborockDevice
150
+
151
+ Raises:
152
+ NoDeviceError: 无设备
153
+ AmbiguousDeviceError: 名称匹配到多个设备
154
+ DeviceNotFoundError: 名称未匹配到任何设备
155
+ """
156
+ devices = await self.list_devices()
157
+
158
+ # 单设备且未指定名称时,自动选择
159
+ if device_name is None:
160
+ if len(devices) == 1:
161
+ return devices[0]
162
+ else:
163
+ available = [d.name for d in devices]
164
+ raise ValueError(
165
+ f"发现 {len(devices)} 个设备,请指定 device_name。可用设备: {available}"
166
+ )
167
+
168
+ # 模糊匹配
169
+ matches = []
170
+ for device in devices:
171
+ if device_name.lower() in device.name.lower():
172
+ matches.append(device)
173
+
174
+ if len(matches) == 0:
175
+ available = [d.name for d in devices]
176
+ raise DeviceNotFoundError(device_name, available)
177
+
178
+ if len(matches) > 1:
179
+ raise AmbiguousDeviceError(
180
+ device_name,
181
+ [d.name for d in matches]
182
+ )
183
+
184
+ return matches[0]
185
+
186
+ async def close(self) -> None:
187
+ """关闭连接,先 flush 缓存再关闭"""
188
+ if self._cache is not None:
189
+ await self._cache.flush()
190
+ if self._manager is not None:
191
+ await self._manager.close()
192
+ if self._session is not None:
193
+ await self._session.close()
194
+
195
+
196
+ @asynccontextmanager
197
+ async def lifespan(server: Any):
198
+ """MCP server lifespan 管理"""
199
+ conn = ConnectionManager.from_env()
200
+ await conn.ensure_connected()
201
+ yield {"conn": conn}
202
+ await conn.close()
@@ -0,0 +1,71 @@
1
+ """
2
+ 共享辅助函数,供 server.py 和 cli.py 使用
3
+ """
4
+
5
+ import logging
6
+ from enum import Enum
7
+ from typing import Any
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ async def safe_refresh(trait, name: str) -> bool:
13
+ """安全刷新单个 trait,失败不影响其他"""
14
+ try:
15
+ await trait.refresh()
16
+ return True
17
+ except Exception as e:
18
+ logger.warning("Trait %s refresh 失败: %s", name, e)
19
+ return False
20
+
21
+
22
+ def serialize_value(value: Any) -> Any:
23
+ """将 Enum 等特殊类型转为可序列化值"""
24
+ if value is None:
25
+ return None
26
+ if isinstance(value, Enum):
27
+ return value.name
28
+ if isinstance(value, list):
29
+ return [serialize_value(v) for v in value]
30
+ return value
31
+
32
+
33
+ def field(value: Any, description: str) -> dict[str, Any]:
34
+ """包装字段值和描述"""
35
+ return {"value": value, "description": description}
36
+
37
+
38
+ # StatusV2 白名单:仅输出有意义的字段
39
+ # state/error_code 改在 computed properties 中输出(避免与 state_name/error_code_name 重复)
40
+ STATUS_FIELDS: dict[str, str] = {
41
+ # 核心状态
42
+ "battery": "电池电量 (%)",
43
+ "charge_status": "充电状态 (0=未充电, 1=充电中, 2=充电完成)",
44
+ # 清洁进度
45
+ "clean_percent": "清洁任务完成百分比 (%)",
46
+ "in_cleaning": "清洁状态 (complete/global_clean_not_complete/zone_clean_not_complete/segment_clean_not_complete)",
47
+ "in_returning": "是否在返回途中 (0=否, 1=是)",
48
+ "avoid_count": "当前任务避障次数",
49
+ "repeat": "当前任务重复遍数",
50
+ # 地图
51
+ "map_present": "是否有地图 (0=无, 1=有)",
52
+ # 基座
53
+ "dock_type": "基座类型 (no_dock/auto_empty_dock/empty_wash_fill_dock/p10_pro_dock 等)",
54
+ "dock_error_status": "基座错误状态 (ok/duct_blockage/water_empty/waste_water_tank_full 等)",
55
+ "dust_collection_status": "集尘状态 (0=未集尘, 1=集尘中)",
56
+ "auto_dust_collection": "自动集尘开关 (0=关闭, 1=开启)",
57
+ # 洗拖布/烘干
58
+ "wash_status": "洗拖布状态 (0=空闲, 1=洗涤中)",
59
+ "wash_phase": "洗拖布阶段 (0=空闲)",
60
+ "wash_ready": "是否准备好洗拖布 (0=否, 1=是)",
61
+ "dry_status": "热风烘干状态 (0=关闭, 1=烘干中)",
62
+ # 水箱
63
+ "water_box_status": "水箱安装状态 (0=未安装, 1=已安装)",
64
+ "water_box_carriage_status": "水箱托架状态 (0=正常)",
65
+ "water_shortage_status": "缺水状态 (0=正常)",
66
+ # 其他
67
+ "is_locating": "是否正在定位 (0=否, 1=是)",
68
+ "lock_status": "童锁状态 (0=未锁定, 1=锁定)",
69
+ "mop_forbidden_enable": "拖地禁区是否启用 (0=关闭, 1=启用)",
70
+ "collision_avoid_status": "避障功能 (0=关闭, 1=开启)",
71
+ }