rabo-robocap 2.0.0__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.
@@ -0,0 +1,77 @@
1
+ """
2
+ rabo_robocap: ROS2 机器人能力封装
3
+
4
+ PyPI 发布的 loader shell — 核心代码从阿里云 OSS 自动拉取。
5
+ 设置环境变量 RABO_DEV=1 可跳过 loader,直接从源码导入(开发/测试用)。
6
+
7
+ Usage:
8
+ from rabo_robocap import UR5
9
+
10
+ # 仿真模式
11
+ arm = UR5(robot_id='ur5_model', mode='sim')
12
+
13
+ # 真实硬件
14
+ arm = UR5(robot_id='ur5', mode='real')
15
+
16
+ arm.move_to(0.3, 0.2, 0.4)
17
+ """
18
+
19
+ import os
20
+ import sys
21
+
22
+ if os.environ.get("RABO_DEV"):
23
+ # ---- 开发模式:直接从源码导入 ----
24
+ from .exceptions import (
25
+ RobocapError, IKSolutionError, JointLimitError,
26
+ CommunicationError, TrajectoryError,
27
+ )
28
+ from .arms import UR5, UR10, GalaxeaA1X
29
+ from .mobile import AgilexScoutMini, GalaxeaR1LiteChassis
30
+ from .clients import MobileClient, ArmClient, TorsoClient, GripperClient
31
+ from .torso import GalaxeaR1LiteTorso
32
+ from .grippers import GalaxeaG1Gripper
33
+ from .base import ArmBase, MobileBase, TorsoBase, GripperBase
34
+
35
+ __version__ = "2.0.0-dev"
36
+
37
+ def check_update():
38
+ print("rabo_robocap: dev mode (RABO_DEV=1), hot-update disabled")
39
+ else:
40
+ # ---- 生产模式:从 OSS 缓存的 zip 加载 ----
41
+ from ._updater import ensure_updated, get_core_zip_path, install_redirector, check_update
42
+
43
+ # 1. 确保核心包是最新的(检查缓存/下载)
44
+ ensure_updated()
45
+
46
+ # 2. 将 zip 加入 sys.path(zipimport)
47
+ _zip_path = get_core_zip_path()
48
+ if _zip_path not in sys.path:
49
+ sys.path.insert(0, _zip_path)
50
+
51
+ # 3. 安装 import 重定向(支持 from rabo_robocap.arms import UR5)
52
+ install_redirector()
53
+
54
+ # 4. 从 _rabo_core 导入并重新导出
55
+ from _rabo_core import __version__ # noqa: E402
56
+ from _rabo_core.arms import UR5, UR10, GalaxeaA1X # noqa: E402
57
+ from _rabo_core.mobile import AgilexScoutMini, GalaxeaR1LiteChassis # noqa: E402
58
+ from _rabo_core.clients import MobileClient, ArmClient, TorsoClient, GripperClient # noqa: E402
59
+ from _rabo_core.torso import GalaxeaR1LiteTorso # noqa: E402
60
+ from _rabo_core.grippers import GalaxeaG1Gripper # noqa: E402
61
+ from _rabo_core.exceptions import ( # noqa: E402
62
+ RobocapError, IKSolutionError, JointLimitError,
63
+ CommunicationError, TrajectoryError,
64
+ )
65
+ from _rabo_core.base import ArmBase, MobileBase, TorsoBase, GripperBase # noqa: E402
66
+
67
+ __all__ = [
68
+ '__version__',
69
+ 'UR5', 'UR10', 'GalaxeaA1X',
70
+ 'AgilexScoutMini', 'GalaxeaR1LiteChassis',
71
+ 'MobileClient', 'ArmClient', 'TorsoClient', 'GripperClient',
72
+ 'GalaxeaR1LiteTorso',
73
+ 'GalaxeaG1Gripper',
74
+ 'ArmBase', 'MobileBase', 'TorsoBase', 'GripperBase',
75
+ 'RobocapError', 'IKSolutionError', 'JointLimitError',
76
+ 'CommunicationError', 'TrajectoryError',
77
+ ]
@@ -0,0 +1,250 @@
1
+ """
2
+ Hot-update loader: 从阿里云 OSS 拉取最新核心代码 zip,本地缓存后 zipimport。
3
+
4
+ 流程:
5
+ 1. import rabo_robocap 时调用 ensure_updated()
6
+ 2. 检查本地 meta.json 的 last_check 时间戳,TTL 内跳过
7
+ 3. TTL 过期 → GET version.json,比较版本
8
+ 4. 远端更新 → 下载 core-cp3{minor}.zip
9
+ 5. 网络异常 → 静默使用本地缓存
10
+ 6. 无缓存 + 无网络 → 抛出明确错误
11
+ """
12
+
13
+ import importlib
14
+ import importlib.abc
15
+ import importlib.machinery
16
+ import importlib.util
17
+ import json
18
+ import logging
19
+ import os
20
+ import sys
21
+ import time
22
+ import types
23
+ import urllib.request
24
+ import urllib.error
25
+
26
+ logger = logging.getLogger("rabo_robocap._updater")
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # 配置
30
+ # ---------------------------------------------------------------------------
31
+ OSS_BASE_URL = (
32
+ "https://rabo-apt-repo.oss-cn-hangzhou.aliyuncs.com/rabo_robocap/"
33
+ )
34
+ CACHE_DIR = os.path.join(os.path.expanduser("~"), ".rabo_robocap")
35
+ _HTTP_TIMEOUT = 10 # 秒
36
+
37
+ # Python 版本标识
38
+ _PY_TAG = f"cp3{sys.version_info.minor}"
39
+ _ZIP_NAME = f"core-{_PY_TAG}.zip"
40
+
41
+ # 本地文件路径
42
+ _META_PATH = os.path.join(CACHE_DIR, "meta.json")
43
+ _ZIP_PATH = os.path.join(CACHE_DIR, _ZIP_NAME)
44
+
45
+ # 内部核心包名(zip 内的顶层目录)
46
+ _CORE_PACKAGE = "_rabo_core"
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # 内部工具
51
+ # ---------------------------------------------------------------------------
52
+
53
+ def _read_meta():
54
+ """读取本地 meta.json,不存在或损坏返回空 dict。"""
55
+ try:
56
+ with open(_META_PATH, "r") as f:
57
+ return json.load(f)
58
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
59
+ return {}
60
+
61
+
62
+ def _write_meta(meta):
63
+ """写入本地 meta.json。"""
64
+ os.makedirs(CACHE_DIR, exist_ok=True)
65
+ with open(_META_PATH, "w") as f:
66
+ json.dump(meta, f)
67
+
68
+
69
+ def _fetch_json(url):
70
+ """GET 请求并解析 JSON,失败返回 None。"""
71
+ try:
72
+ req = urllib.request.Request(url)
73
+ with urllib.request.urlopen(req, timeout=_HTTP_TIMEOUT) as resp:
74
+ return json.loads(resp.read().decode("utf-8"))
75
+ except (urllib.error.URLError, OSError, json.JSONDecodeError, ValueError) as e:
76
+ logger.debug("fetch %s failed: %s", url, e)
77
+ return None
78
+
79
+
80
+ def _download_file(url, dest):
81
+ """下载文件到 dest,成功返回 True。"""
82
+ os.makedirs(os.path.dirname(dest), exist_ok=True)
83
+ tmp = dest + ".tmp"
84
+ try:
85
+ req = urllib.request.Request(url)
86
+ with urllib.request.urlopen(req, timeout=60) as resp:
87
+ with open(tmp, "wb") as f:
88
+ while True:
89
+ chunk = resp.read(65536)
90
+ if not chunk:
91
+ break
92
+ f.write(chunk)
93
+ # 原子替换
94
+ if os.path.exists(dest):
95
+ os.remove(dest)
96
+ os.rename(tmp, dest)
97
+ return True
98
+ except (urllib.error.URLError, OSError) as e:
99
+ logger.debug("download %s failed: %s", url, e)
100
+ # 清理临时文件
101
+ try:
102
+ os.remove(tmp)
103
+ except OSError:
104
+ pass
105
+ return False
106
+
107
+
108
+ def _version_tuple(version_str):
109
+ """将 '1.2.3' 转成 (1, 2, 3) 用于比较。"""
110
+ try:
111
+ return tuple(int(x) for x in version_str.split("."))
112
+ except (ValueError, AttributeError):
113
+ return (0,)
114
+
115
+
116
+ # ---------------------------------------------------------------------------
117
+ # 公开 API
118
+ # ---------------------------------------------------------------------------
119
+
120
+ def ensure_updated():
121
+ """检查并更新核心包。每次 import 时调用。
122
+
123
+ - 每次都检查远端版本,有更新就下载
124
+ - 网络异常 → 静默使用本地缓存
125
+ - 无缓存 + 无网络 → RuntimeError
126
+ """
127
+ meta = _read_meta()
128
+
129
+ # 尝试获取远端版本信息
130
+ remote = _fetch_json(OSS_BASE_URL + "version.json")
131
+
132
+ if remote is not None:
133
+ remote_ver = remote.get("version", "0.0.0")
134
+ local_ver = meta.get("version", "0.0.0")
135
+
136
+ if remote_ver != local_ver or not os.path.isfile(_ZIP_PATH):
137
+ # 需要下载
138
+ zip_url = OSS_BASE_URL + _ZIP_NAME
139
+ logger.info("rabo_robocap: downloading %s (v%s)...", _ZIP_NAME, remote_ver)
140
+ if _download_file(zip_url, _ZIP_PATH):
141
+ meta["version"] = remote_ver
142
+ logger.info("rabo_robocap: updated to v%s", remote_ver)
143
+ _write_meta(meta)
144
+ else:
145
+ logger.warning("rabo_robocap: download failed, using cache if available")
146
+ else:
147
+ # 网络不可用
148
+ if os.path.isfile(_ZIP_PATH):
149
+ logger.debug("rabo_robocap: offline, using cached core")
150
+ else:
151
+ raise RuntimeError("rabo_robocap 初始化失败,请检查网络连接")
152
+
153
+
154
+ def check_update():
155
+ """打印当前版本信息并检查远端是否有更新。
156
+
157
+ Usage:
158
+ import rabo_robocap
159
+ rabo_robocap.check_update()
160
+ """
161
+ meta = _read_meta()
162
+ local_ver = meta.get("version", "unknown")
163
+ has_cache = os.path.isfile(_ZIP_PATH)
164
+
165
+ print(f"rabo_robocap hot-update status:")
166
+ print(f" local core version : {local_ver}")
167
+ print(f" cache file : {_ZIP_PATH} ({'exists' if has_cache else 'MISSING'})")
168
+
169
+ # 查询远端
170
+ print(f" checking remote : {OSS_BASE_URL}version.json ...")
171
+ remote = _fetch_json(OSS_BASE_URL + "version.json")
172
+ if remote is None:
173
+ print(f" remote version : unavailable (network error)")
174
+ else:
175
+ remote_ver = remote.get("version", "unknown")
176
+ print(f" remote version : {remote_ver}")
177
+ if remote_ver != local_ver:
178
+ print(f" status : VERSION CHANGED ({local_ver} → {remote_ver})")
179
+ print(f" restart Python to load the new version")
180
+ else:
181
+ print(f" status : up to date")
182
+
183
+
184
+ def get_core_zip_path():
185
+ """返回核心 zip 路径,不存在则抛错。"""
186
+ if os.path.isfile(_ZIP_PATH):
187
+ return _ZIP_PATH
188
+ raise RuntimeError(
189
+ f"rabo_robocap: core zip not found at '{_ZIP_PATH}'. "
190
+ "Run ensure_updated() first."
191
+ )
192
+
193
+
194
+ # ---------------------------------------------------------------------------
195
+ # Import 重定向器(使用 find_spec API,兼容 Python 3.4+)
196
+ # ---------------------------------------------------------------------------
197
+
198
+ class _CoreRedirector(importlib.abc.MetaPathFinder):
199
+ """将 rabo_robocap.* 的子包导入重定向到 _rabo_core.*
200
+
201
+ 例如:
202
+ from rabo_robocap.arms import UR5
203
+ → 实际导入 _rabo_core.arms.UR5
204
+
205
+ 仅拦截 rabo_robocap 的子模块,不拦截 rabo_robocap 本身。
206
+ """
207
+
208
+ _PREFIX = "rabo_robocap."
209
+
210
+ def find_spec(self, fullname, path, target=None):
211
+ if not fullname.startswith(self._PREFIX):
212
+ return None
213
+
214
+ # rabo_robocap.arms.ur5 → _rabo_core.arms.ur5
215
+ core_name = _CORE_PACKAGE + fullname[len("rabo_robocap"):]
216
+
217
+ return importlib.machinery.ModuleSpec(
218
+ fullname,
219
+ _CoreLoader(core_name),
220
+ )
221
+
222
+
223
+ class _CoreLoader(importlib.abc.Loader):
224
+ """将 rabo_robocap.xxx 加载为 _rabo_core.xxx 的代理。"""
225
+
226
+ def __init__(self, core_name):
227
+ self._core_name = core_name
228
+
229
+ def create_module(self, spec):
230
+ # 加载真实模块
231
+ real_mod = importlib.import_module(self._core_name)
232
+ # 注册到 sys.modules 供后续直接命中
233
+ sys.modules[spec.name] = real_mod
234
+ return real_mod
235
+
236
+ def exec_module(self, module):
237
+ # create_module 已完成所有工作,无需额外执行
238
+ pass
239
+
240
+
241
+ _redirector_installed = False
242
+
243
+
244
+ def install_redirector():
245
+ """在 sys.meta_path 中安装重定向器(幂等)。"""
246
+ global _redirector_installed
247
+ if _redirector_installed:
248
+ return
249
+ sys.meta_path.insert(0, _CoreRedirector())
250
+ _redirector_installed = True
@@ -0,0 +1,57 @@
1
+ """rabo_robocap 异常定义"""
2
+
3
+
4
+ class RobocapError(Exception):
5
+ """rabo_robocap 基础异常类"""
6
+ pass
7
+
8
+
9
+ class IKSolutionError(RobocapError):
10
+ """逆运动学无解异常"""
11
+
12
+ def __init__(self, target_pose=None, message=None):
13
+ self.target_pose = target_pose
14
+ if message is None:
15
+ message = "无法找到逆运动学解"
16
+ if target_pose is not None:
17
+ message += f": target={target_pose}"
18
+ super().__init__(message)
19
+
20
+
21
+ class JointLimitError(RobocapError):
22
+ """关节超限异常"""
23
+
24
+ def __init__(self, joint_index=None, value=None, limits=None, message=None):
25
+ self.joint_index = joint_index
26
+ self.value = value
27
+ self.limits = limits
28
+ if message is None:
29
+ message = "关节角度超出限位"
30
+ if joint_index is not None:
31
+ message += f": joint[{joint_index}]={value}, limits={limits}"
32
+ super().__init__(message)
33
+
34
+
35
+ class CommunicationError(RobocapError):
36
+ """通信超时异常"""
37
+
38
+ def __init__(self, topic=None, timeout=None, message=None):
39
+ self.topic = topic
40
+ self.timeout = timeout
41
+ if message is None:
42
+ message = "通信超时"
43
+ if topic is not None:
44
+ message += f": topic={topic}, timeout={timeout}s"
45
+ super().__init__(message)
46
+
47
+
48
+ class TrajectoryError(RobocapError):
49
+ """轨迹规划/执行异常"""
50
+
51
+ def __init__(self, reason=None, message=None):
52
+ self.reason = reason
53
+ if message is None:
54
+ message = "轨迹执行失败"
55
+ if reason is not None:
56
+ message += f": {reason}"
57
+ super().__init__(message)
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: rabo_robocap
3
+ Version: 2.0.0
4
+ Summary: rabo_robocap: ROS2 机器人能力封装
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: numpy
8
+ Provides-Extra: test
9
+ Requires-Dist: pytest>=7.0; extra == "test"
10
+ Requires-Dist: pytest-cov; extra == "test"
11
+ Provides-Extra: build
12
+ Requires-Dist: oss2; extra == "build"
13
+
14
+ # rabo_robocap
15
+
16
+ ROS2 机器人能力封装 SDK,为 Gazebo 仿真中的机器人提供高级控制接口。
17
+
18
+ 支持热更新:`pip install` 一次后,每次 `import` 自动获取最新版本。
19
+
20
+ ## 安装
21
+
22
+ ```bash
23
+ # 1. 安装 Python 包(仅需一次)
24
+ pip install rabo_robocap
25
+
26
+ # 2. 构建 ROS2 消息包(需要 ROS2 环境)
27
+ cd ~/ros2_ws/src/rabo-lib
28
+ colcon build --paths rabo_robocap_msgs
29
+ source install/setup.bash
30
+ ```
31
+
32
+ 详细构建说明请参考 [构建指南](docs/build.md)。
33
+
34
+ ## 使用
35
+
36
+ ### 移动底盘
37
+
38
+ ```python
39
+ from rabo_robocap import AgilexScoutMini
40
+
41
+ # 仿真模式(自动从 API 获取配置,rclpy 自动初始化)
42
+ base = AgilexScoutMini(robot_id='scout_model', mode='sim')
43
+
44
+ # 或 真实硬件模式
45
+ # base = AgilexScoutMini(robot_id='scout', mode='real')
46
+
47
+ base.set_velocity(linear_x=0.5, angular_z=0.0)
48
+ base.move_distance(1.0)
49
+ x, y, theta = base.get_odometry()
50
+ print(f"Position: ({x:.2f}, {y:.2f}), Heading: {theta:.2f}")
51
+
52
+ base.shutdown()
53
+ ```
54
+
55
+ ### 机械臂
56
+
57
+ ```python
58
+ import numpy as np
59
+ from rabo_robocap import UR5
60
+
61
+ # 仿真模式
62
+ arm = UR5(robot_id='ur5_model', mode='sim')
63
+
64
+ # 或 真实硬件
65
+ # arm = UR5(robot_id='ur5', mode='real')
66
+
67
+ # 移动到 ready 位置
68
+ arm.move_joints([0, -np.pi/2, np.pi/2, -np.pi/2, -np.pi/2, 0])
69
+
70
+ # 移动到目标位置
71
+ arm.move_to(0.3, 0.2, 0.4)
72
+
73
+ # 获取当前位姿
74
+ pos, ori = arm.get_pose()
75
+ print(f"Position: {pos}")
76
+
77
+ arm.shutdown()
78
+ ```
79
+
80
+ ### 版本管理
81
+
82
+ ```python
83
+ import rabo_robocap
84
+
85
+ # 查看当前版本
86
+ print(rabo_robocap.__version__)
87
+
88
+ # 查看更新状态
89
+ rabo_robocap.check_update()
90
+ ```
91
+
92
+ **注意**:
93
+ - rclpy 自动初始化,无需手动调用 `rclpy.init()`
94
+ - 节点内部自动管理 ROS2 回调,**不要**将节点添加到外部 executor
95
+
96
+ ## 文档
97
+
98
+ - [构建指南](docs/build.md)
99
+ - [整体架构](docs/architecture.md)
100
+ - [AgilexScoutMini 移动底盘](docs/robots/mobile_agilex_scout_mini.md)
101
+ - [GalaxeaR1LiteChassis 移动底盘](docs/robots/mobile_galaxea_r1_lite_chassis.md)
102
+ - [UR 系列机械臂](docs/robots/arm_ur_series.md)
103
+ - [设计规则](CLAUDE.md)
@@ -0,0 +1,7 @@
1
+ rabo_robocap/__init__.py,sha256=I1pqcyTQ_mh5P9oxpzI3xMpFajsoDZjwsZQ_Er8ScIM,2769
2
+ rabo_robocap/_updater.py,sha256=z3tS-z306jeuFvL9ftsPNhq6QRW3TFoV0G0aZ0DKrtQ,7987
3
+ rabo_robocap/exceptions.py,sha256=nLk906wmoVyuZpZuE0iYqNY8FHq3qAxnZCQbq8UPIzQ,1694
4
+ rabo_robocap-2.0.0.dist-info/METADATA,sha256=ojImKFkj6xOtHw9aB-ZGg0niCrGu0m56e5CLNf7HIbM,2404
5
+ rabo_robocap-2.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ rabo_robocap-2.0.0.dist-info/top_level.txt,sha256=XsAkBJVF6ZfyM071uWwDZbNcns-MJ7kPRoEzkLXzltI,13
7
+ rabo_robocap-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ rabo_robocap