注释与提示信息全面中文化:源码 66 个 .py + 脚本/配置 45 个文件
背景:现场读代码的人只读中文。此前代码里的 docstring 是英文、行内注释是中文,
一半英文一半中文反而更难看懂 —— 新手打开一个 .py 不知道这个模块为什么存在。
范围(本仓库自己的代码,厂商上游不动)
src/arm_application/** 37 个文件:servo_node / perception_node / handeye_* / dh_pgea /
yolo_detector / kalman_filter / goto_ee_plane / dual_gripper …
src/arm_interfaces/** 11 个文件:paths / manifest / loader / config_guard / errors …
src/arm_control/** 5 个文件
src/arm_bringup/** 3 个 launch
scripts/** 6 个 .py + 29 个 .sh(scripts/ 是"唯一操作入口",用户屏幕上
看到的就是这些提示,所以提示信息也一并中文化)
config/ handeye/ devices/ src/arm_*/config/ 17 个 .yaml/.xml/.xacro/.cfg
口径(写进 docs/FRAMEWORK_RULES.md 新增的 R6.5,作为长期约束)
注释、docstring、print/日志/异常里给人看的句子 = 中文;
标识符、API 名、话题名、参数名、字段名、命令名 = 保留英文原文(用 `` `` 包起来);
单位、十六进制、协议报文、CRC 值、命令示例 = 原样保留;
src/flexiv_*/ = 厂商上游代码,不许改也不许翻译(R4.4)。
翻译保原意与原有详细程度:那些"为什么不能用另一种做法""踩过什么坑"的说明
全部完整保留 —— 它们才是这份代码里最贵的部分。
新增 scripts/check_text_only.py:只改文字时的自证工具
人工 review 靠不住(115 个文件、3000 多行 diff)。这个脚本把改动前后的源码解析成
语法树,删掉 docstring、抹平字符串常量、把 f-string 只留"插值了几次 + 插值是什么",
再比对:完全相同 = 改动范围内没有逻辑变化。
留了一个口子:f-string 的插值按内容排序后再比 —— 因为中文语序常常要和英文反着来
(f"on {port} read {idx} failed" → f"在 {port} 上读 {idx} 失败"),只换显示顺序不该判失败;
但多插一个、少插一个、换掉被插入的表达式,立刻会报出来。
本次 66 个 .py 全部通过该闸门。
验收
改动的 66 个 .py:scripts/check_text_only.py 全部通过(零逻辑变化)
45 个非 .py 文件:去注释后与 HEAD 逐字节相同(零配置变化)
docstring 总数:没有一个文件减少(不存在"翻译时顺手删了说明")
单测:arm_application 233 / arm_driver_flexiv 69 / arm_interfaces 46 /
arm_kinematics 24 / arm_sim_gazebo 236 = 608 passed,与改动前一致
全部 .sh:bash -n 通过;并额外实际执行了 --help 验证(见下一个提交:
bash -n 对 CRLF 脚本会漏报,只有真跑才暴露)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,6 +27,15 @@
|
||||
- 注意 `run_sim.sh` 不是 Gazebo,它走 mock 驱动(假数据 + RViz),永远不会产生接触力
|
||||
**环境:** `env.sh` · `build_all.sh`
|
||||
**RDK:** `test_rdk_python.sh` · `rdk_switch_tool.sh` · `rdk_drag_bridge.py`
|
||||
**改注释/写文档后自检:** `python3 scripts/check_text_only.py`
|
||||
- 用途:证明这次改动**只动了文字**(注释、docstring、提示信息),没碰逻辑。
|
||||
它把改动前后的源码解析成语法树,删掉 docstring、抹平字符串常量后比对;
|
||||
"翻译 f-string 时漏掉一个 `{}` 插值""顺手改了个变量名"这类问题会被抓出来。
|
||||
- `python3 scripts/check_text_only.py` 查所有改过的 `.py`;也可以直接跟文件名。
|
||||
基准是 `git HEAD`,所以要在**提交之前**跑。老手滑手滑的代价比跑一条命令高得多。
|
||||
- 规矩写在 [docs/FRAMEWORK_RULES.md](../docs/FRAMEWORK_RULES.md) 的 R6.5。
|
||||
一句话:**注释/docstring/提示信息用中文;标识符、话题名、字段名保留英文**
|
||||
(但 `src/flexiv_*/` 是厂商上游代码,不许翻译,见 R4.4)。
|
||||
**搁置未用:** `fetch_isaac_flexiv.sh`(拉 Flexiv 的 Isaac Sim / RDK 源码到 `thirdparty/`;Isaac 路线目前搁置,Gazebo 是选定平台)
|
||||
|
||||
`lib/parse_arm_args.sh` 解析 `--arm 89|91`。
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build entire workspace: arm stack + flexiv real drivers
|
||||
# 构建整个工作区:机械臂栈 + Flexiv 真机驱动
|
||||
set -eo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build Flexiv RDK v1.x into ~/flexiv_install (required for real robot / flexiv_hardware).
|
||||
# 把 Flexiv RDK v1.x 构建安装到 ~/flexiv_install(真机 / `flexiv_hardware` 必需)。
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
RDK="${ROOT}/src/flexiv_rdk"
|
||||
@@ -11,7 +11,7 @@ if [[ ! -d "$RDK" ]]; then
|
||||
fi
|
||||
|
||||
echo "Building RDK thirdparty deps into $PREFIX ..."
|
||||
# Prefer system /opt/ros if present; else conda env already has compilers
|
||||
# 有系统 /opt/ros 就优先用它;否则 conda 环境里已经有编译器
|
||||
if [[ -f /opt/ros/humble/setup.bash ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source /opt/ros/humble/setup.bash
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build Flexiv hardware packages for real robot (after RDK installed to ~/flexiv_install)
|
||||
# 构建真机用的 Flexiv 硬件包(前提:RDK 已装到 ~/flexiv_install)
|
||||
set -eo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Check PC ↔ Flexiv robot network (addresses from site.yaml)
|
||||
# 检查 PC ↔ Flexiv 机器人网络(地址取自 site.yaml)
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""纯文本改动的安全闸门:证明一次改动**只动了文字**,没碰逻辑。
|
||||
|
||||
=============================================================================
|
||||
为什么需要这个工具
|
||||
=============================================================================
|
||||
"把英文注释翻成中文""补一批说明文档"这类改动,看起来零风险,其实经常出事:
|
||||
改注释的人手滑动了变量名、顺手调了语句顺序、翻译 f-string 时漏掉一个 ``{}`` 插值。
|
||||
这些都不会报错,只在某天变成一句"结果怎么不对了"。
|
||||
|
||||
人工 review 靠不住(几十个文件、几千行 diff,没人真的逐行看)。
|
||||
所以这里用 AST 做机器判据:**把两份源码解析成语法树,抹掉所有文字,再比对。**
|
||||
抹掉规则:
|
||||
|
||||
1. 删掉所有 docstring
|
||||
2. 所有字符串常量换成同一个占位符
|
||||
3. f-string 只保留"插值了几次、插值的是什么表达式",
|
||||
字面量部分整段丢掉、插值按内容排序
|
||||
|
||||
第 3 条是为中文语序专门留的口子。英文 ``f"on {port} read {idx} failed"``
|
||||
翻成 ``f"在 {port} 上读 {idx} 失败"`` 只是把两个插值的**显示顺序**换了,
|
||||
语义没变,不该判失败。但只要有人**多插一个、少插一个、或换掉被插入的表达式**,
|
||||
这里立刻报出来。
|
||||
|
||||
=============================================================================
|
||||
用法
|
||||
=============================================================================
|
||||
python3 scripts/check_text_only.py # 检查所有改过的 .py
|
||||
python3 scripts/check_text_only.py <文件> [...] # 只检查指定文件
|
||||
python3 scripts/check_text_only.py --staged # 只检查已 git add 的
|
||||
|
||||
比较基准固定为 ``git HEAD``(也就是"上一次提交"),所以要用在**提交之前**。
|
||||
|
||||
退出码 0 = 改动范围内没有逻辑变化;非 0 = 有文件动了不该动的东西(会打印差异)。
|
||||
=============================================================================
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import difflib
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
# 被抹平后的字符串统一长这样。用控制字符是为了保证它不会和真实内容撞车。
|
||||
PLACEHOLDER = "\0STR\0"
|
||||
|
||||
|
||||
class _Normalize(ast.NodeTransformer):
|
||||
"""把语法树里的"文字"全部抹掉,只留下结构与标识符。"""
|
||||
|
||||
def _strip_docstring(self, node):
|
||||
"""删掉模块 / 类 / 函数的第一个字符串表达式(docstring)。"""
|
||||
self.generic_visit(node)
|
||||
body = getattr(node, "body", None)
|
||||
if body and isinstance(body[0], ast.Expr) and isinstance(body[0].value, ast.Constant) \
|
||||
and isinstance(body[0].value.value, str):
|
||||
node.body = body[1:]
|
||||
return node
|
||||
|
||||
visit_Module = _strip_docstring
|
||||
visit_FunctionDef = _strip_docstring
|
||||
visit_AsyncFunctionDef = _strip_docstring
|
||||
visit_ClassDef = _strip_docstring
|
||||
|
||||
def visit_Constant(self, node: ast.Constant):
|
||||
if isinstance(node.value, str):
|
||||
return ast.copy_location(ast.Constant(value=PLACEHOLDER), node)
|
||||
return node
|
||||
|
||||
def visit_JoinedStr(self, node: ast.JoinedStr):
|
||||
"""f-string:只留插值,不留字面量;插值按内容排序(见模块 docstring)。"""
|
||||
self.generic_visit(node)
|
||||
parts = [ast.dump(v) for v in node.values if isinstance(v, ast.FormattedValue)]
|
||||
return ast.copy_location(
|
||||
ast.Constant(value="FSTR[" + "|".join(sorted(parts)) + "]"), node)
|
||||
|
||||
|
||||
def normalize(source: str) -> str:
|
||||
"""源码 -> 抹掉文字后的规范化结构字符串。"""
|
||||
return ast.dump(_Normalize().visit(ast.parse(source)))
|
||||
|
||||
|
||||
def _git(*args: str) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(["git", *args], capture_output=True, text=True, cwd=REPO_ROOT)
|
||||
|
||||
|
||||
def head_source(path: str) -> str | None:
|
||||
"""取该文件在 HEAD 里的内容;返回 None 表示 HEAD 里没有它(新文件)。"""
|
||||
r = _git("show", f"HEAD:{path}")
|
||||
return r.stdout if r.returncode == 0 else None
|
||||
|
||||
|
||||
def changed_python_files() -> list[str]:
|
||||
r = _git("diff", "--name-only", "HEAD")
|
||||
return [p for p in r.stdout.split() if p.endswith(".py")]
|
||||
|
||||
|
||||
def staged_python_files() -> list[str]:
|
||||
r = _git("diff", "--cached", "--name-only")
|
||||
return [p for p in r.stdout.split() if p.endswith(".py")]
|
||||
|
||||
|
||||
def first_diff(old: str, new: str) -> str:
|
||||
"""给出一段人能看懂的差异上下文。"""
|
||||
matcher = difflib.SequenceMatcher(None, old, new, autojunk=False)
|
||||
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
||||
if tag != "equal":
|
||||
return (f" 结构差异类型: {tag}\n"
|
||||
f" 改前: ...{old[max(0, i1 - 70):i2 + 70]}...\n"
|
||||
f" 改后: ...{new[max(0, j1 - 70):j2 + 70]}...")
|
||||
return " (未找到差异片段——理论上不该走到这里)"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
description="证明一次改动只动了注释/字符串,没有改逻辑",
|
||||
)
|
||||
ap.add_argument("paths", nargs="*", help="要检查的文件;留空 = 所有改过的 .py")
|
||||
ap.add_argument("--staged", action="store_true", help="只检查已 git add 的文件")
|
||||
args = ap.parse_args()
|
||||
|
||||
paths = args.paths or (staged_python_files() if args.staged else changed_python_files())
|
||||
if not paths:
|
||||
print("没有需要检查的 .py 文件(工作区是干净的?)。")
|
||||
return 0
|
||||
|
||||
ok, problems, skipped = 0, [], []
|
||||
for path in paths:
|
||||
disk = REPO_ROOT / path
|
||||
if not disk.is_file():
|
||||
skipped.append((path, "工作区里不存在(被删除了?)"))
|
||||
continue
|
||||
old = head_source(path)
|
||||
if old is None:
|
||||
skipped.append((path, "HEAD 里没有它(新增文件),没有基准可比"))
|
||||
continue
|
||||
try:
|
||||
same = normalize(old) == normalize(disk.read_text(encoding="utf-8"))
|
||||
except SyntaxError as exc:
|
||||
problems.append((path, f" 语法错误,先修好再看:{exc}"))
|
||||
continue
|
||||
if same:
|
||||
ok += 1
|
||||
else:
|
||||
problems.append((path, first_diff(normalize(old),
|
||||
normalize(disk.read_text(encoding="utf-8")))))
|
||||
|
||||
print(f"通过(只改了文字): {ok}")
|
||||
for path, why in skipped:
|
||||
print(f"跳过 {path} —— {why}")
|
||||
|
||||
if problems:
|
||||
print(f"\n!! 有 {len(problems)} 个文件动了文字之外的东西:")
|
||||
for path, why in problems:
|
||||
print(f" {path}")
|
||||
print(why)
|
||||
print("\n注意:如果差异只出现在 f-string 的插值顺序上,那是允许的(见脚本说明)。")
|
||||
return 1
|
||||
|
||||
print("\n闸门通过:改动范围内没有逻辑变化。")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert Flexiv Elements .traj (text) -> arm_application YAML waypoints."""
|
||||
"""把 Flexiv Elements 的 .traj(文本)转换成 arm_application 的 YAML 路点。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import Any
|
||||
|
||||
|
||||
def parse_flexiv_traj(text: str) -> list[dict[str, Any]]:
|
||||
"""Parse Elements Studio scatter-point .traj file."""
|
||||
"""解析 Elements Studio 导出的散点 .traj 文件。"""
|
||||
blocks = re.split(r'pattern_params\s*\{', text)[1:]
|
||||
waypoints: list[dict[str, Any]] = []
|
||||
for block in blocks:
|
||||
@@ -35,7 +35,7 @@ def parse_flexiv_traj(text: str) -> list[dict[str, Any]]:
|
||||
|
||||
|
||||
def assign_times(waypoints: list[dict[str, Any]], min_seg: float = 3.0) -> None:
|
||||
"""Cumulative time_from_start from joint-space segment length."""
|
||||
"""按关节空间的段长累加出 time_from_start。"""
|
||||
t = 0.0
|
||||
prev = None
|
||||
for wp in waypoints:
|
||||
@@ -44,7 +44,7 @@ def assign_times(waypoints: list[dict[str, Any]], min_seg: float = 3.0) -> None:
|
||||
t += min_seg
|
||||
else:
|
||||
delta = max(abs(a - b) for a, b in zip(pos, prev))
|
||||
# ~4 s per radian max joint move, floored at min_seg
|
||||
# 关节最大运动量每弧度约 4 秒,且不低于 min_seg
|
||||
t += max(min_seg, delta * 4.0)
|
||||
wp['time'] = round(t, 2)
|
||||
prev = pos
|
||||
@@ -65,17 +65,17 @@ def to_yaml(
|
||||
pos_line = ', '.join(f'{v:.6f}' for v in flat)
|
||||
time_line = ', '.join(str(t) for t in times)
|
||||
names = ', '.join(f' # {wp.get("name", "")}' if i == 0 else '' for i, wp in enumerate(waypoints))
|
||||
_ = names # names kept in comments below
|
||||
_ = names # 名称保留在下面的注释里
|
||||
lines = [
|
||||
'# Auto-converted from Flexiv Elements .traj (joints: deg -> rad)',
|
||||
'# ROS params: flat waypoint_positions + waypoint_times (no nested dicts)',
|
||||
'# Edit velocity_scale / loop_count as needed (no rebuild).',
|
||||
'# 由 Flexiv Elements .traj 自动转换而来(关节角:度 -> 弧度)',
|
||||
'# ROS 参数:扁平的 waypoint_positions + waypoint_times(不使用嵌套字典)',
|
||||
'# 按需修改 velocity_scale / loop_count(无需重新编译)。',
|
||||
'run_joint_trajectory:',
|
||||
' ros__parameters:',
|
||||
f' joint_prefix: "{joint_prefix}"',
|
||||
' joint_names: [joint1, joint2, joint3, joint4, joint5, joint6, joint7]',
|
||||
f' velocity_scale: {velocity_scale}',
|
||||
' loop_count: 1 # 1=once, 0=forever, N=repeat N',
|
||||
' loop_count: 1 # 1=执行一次,0=无限循环,N=重复 N 次',
|
||||
' pause_between_loops: 0.5',
|
||||
f' waypoint_times: [{time_line}]',
|
||||
' waypoint_positions: [',
|
||||
@@ -89,16 +89,16 @@ def to_yaml(
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument('input', type=Path, help='Flexiv Elements .traj file')
|
||||
ap.add_argument('-o', '--output', type=Path, help='Output YAML path')
|
||||
ap.add_argument('--prefix', default='Rizon4-063989_', help='Joint name prefix (site SN + _)')
|
||||
ap.add_argument('--min-seg', type=float, default=3.0, help='Min seconds per segment')
|
||||
ap.add_argument('input', type=Path, help='Flexiv Elements 的 .traj 文件')
|
||||
ap.add_argument('-o', '--output', type=Path, help='输出的 YAML 路径')
|
||||
ap.add_argument('--prefix', default='Rizon4-063989_', help='关节名前缀(现场 SN + _)')
|
||||
ap.add_argument('--min-seg', type=float, default=3.0, help='每段的最小秒数')
|
||||
args = ap.parse_args()
|
||||
|
||||
text = args.input.read_text(encoding='utf-8')
|
||||
wps = parse_flexiv_traj(text)
|
||||
if not wps:
|
||||
print('ERROR: no waypoints parsed', file=sys.stderr)
|
||||
print('错误:没有解析到任何路点', file=sys.stderr)
|
||||
return 1
|
||||
assign_times(wps, min_seg=args.min_seg)
|
||||
yaml_text = to_yaml(wps, joint_prefix=args.prefix)
|
||||
@@ -107,7 +107,7 @@ def main() -> int:
|
||||
if out:
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(yaml_text, encoding='utf-8')
|
||||
print(f'Wrote {len(wps)} waypoints -> {out}')
|
||||
print(f'已写入 {len(wps)} 个路点 -> {out}')
|
||||
else:
|
||||
print(yaml_text)
|
||||
return 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Convert Flexiv Elements .traj → arm_application YAML (standard entry for all trajectories).
|
||||
# Usage:
|
||||
# 把 Flexiv Elements 的 .traj 转成 `arm_application` 的 YAML(所有轨迹的统一入口)。
|
||||
# 用法:
|
||||
# ./scripts/convert_traj.sh ~/文档/my_task.traj
|
||||
# ./scripts/convert_traj.sh ~/文档/my_task.traj -o /path/to/out.yaml
|
||||
# ./scripts/convert_traj.sh ~/文档/my_task.traj --min-seg 2.5 --no-copy
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Source this workspace environment (conda + ROS + flexiv libs).
|
||||
# Usage: source scripts/env.sh
|
||||
# 加载本工作区环境(conda + ROS + flexiv 库)。
|
||||
# 用法:source scripts/env.sh
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
if [[ -f /opt/ros/humble/setup.bash ]]; then
|
||||
@@ -33,7 +33,7 @@ fi
|
||||
# shellcheck disable=SC1091
|
||||
source "${ROOT}/install/setup.bash"
|
||||
export LD_LIBRARY_PATH="${HOME}/flexiv_install/lib:${LD_LIBRARY_PATH:-}"
|
||||
# Pip TensorRT (tensorrt-cu12) — needed to load YOLO .engine
|
||||
# pip 版 TensorRT(tensorrt-cu12)—— 加载 YOLO `.engine` 需要它
|
||||
_TRT_SITE="${CONDA_PREFIX:-${HOME}/anaconda3/envs/ros_flexiv}/lib/python3.11/site-packages"
|
||||
for _d in "${_TRT_SITE}/tensorrt" "${_TRT_SITE}/tensorrt_libs" "${_TRT_SITE}/nvidia/tensorrt/lib"; do
|
||||
if [[ -d "${_d}" ]]; then
|
||||
@@ -44,6 +44,6 @@ unset _d _TRT_SITE
|
||||
export YOLO_AUTOINSTALL=false
|
||||
export ROS2_WS="${ROOT}"
|
||||
|
||||
# Master site config → SITE_* (YOLO weights, camera, robot SN, …)
|
||||
# 主站点配置 → `SITE_*`(YOLO 权重、相机、机器人 SN……)
|
||||
# shellcheck disable=SC1091
|
||||
source "${ROOT}/scripts/lib/load_site_config.sh" 2>/dev/null || true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Clone official Flexiv ROS 2 stack for Rizon (RDK v1.x → humble-v1 branch).
|
||||
# 克隆 Flexiv 官方 ROS 2 栈(Rizon 用,RDK v1.x → humble-v1 分支)。
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
@@ -9,7 +9,7 @@ BRANCH="${FLEXIV_ROS2_BRANCH:-humble-v1}"
|
||||
mkdir -p "${SRC}"
|
||||
cd "${SRC}"
|
||||
|
||||
# Always ensure official URDF package is present (meshes + xacro)
|
||||
# 无论如何都要保证官方 URDF 包存在(mesh + xacro)
|
||||
if [[ ! -d flexiv_description ]]; then
|
||||
echo "Fetching flexiv_description (${BRANCH})..."
|
||||
if ! git clone --depth 1 -b "${BRANCH}" https://github.com/flexivrobotics/flexiv_description.git; then
|
||||
@@ -58,7 +58,7 @@ if ! command -v vcs >/dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Prefer local Flexiv_Rizon4 RDK (avoids GitHub clone when present)
|
||||
# 优先用本地 Flexiv_Rizon4 里的 RDK(有本地副本时就不去 GitHub 克隆)
|
||||
LOCAL_FLEXIV="${LOCAL_FLEXIV:-$HOME/文档/Flexiv_Rizon4}"
|
||||
if [[ -d "${LOCAL_FLEXIV}/flexiv_rdk" ]]; then
|
||||
echo "Using local RDK: ${LOCAL_FLEXIV}/flexiv_rdk"
|
||||
@@ -73,7 +73,7 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# Always import remaining deps from repos (description already handled; skip-existing)
|
||||
# 其余依赖一律从 .repos 导入(description 已单独处理;skip-existing 跳过已有)
|
||||
vcs import "${SRC}" < "${REPOS_FILE}" --recursive --skip-existing || true
|
||||
|
||||
cat <<EOF
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Clone Flexiv Isaac Sim integration repos into thirdparty/
|
||||
# 把 Flexiv Isaac Sim 集成仓库克隆到 thirdparty/
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
@@ -23,7 +23,7 @@ clone_or_update https://github.com/flexivrobotics/flexiv_sim_plugin.git flexiv_s
|
||||
clone_or_update https://github.com/flexivrobotics/isaac_sim_ws.git isaac_sim_ws
|
||||
clone_or_update https://github.com/flexivrobotics/flexiv_description.git flexiv_description
|
||||
|
||||
# Optional: RDK sources next to sim (if not already under src/)
|
||||
# 可选:在仿真目录旁放一份 RDK 源码(如果 src/ 下还没有)
|
||||
if [[ ! -d "${ROOT}/src/flexiv_rdk" ]]; then
|
||||
clone_or_update https://github.com/flexivrobotics/flexiv_rdk.git flexiv_rdk
|
||||
fi
|
||||
|
||||
@@ -15,7 +15,7 @@ source "${ROOT}/scripts/lib/load_site_config.sh" 2>/dev/null || true
|
||||
PC_IP_PRIMARY="${SITE_PC_IP:-192.168.2.10}"
|
||||
PC_ADDRS=("192.168.2.10/24" "192.168.100.10/24")
|
||||
ROBOT_IPS=("${SITE_ROBOT_IP:-192.168.2.100}")
|
||||
# Catalog extras (second arm)
|
||||
# catalog 里的额外项(第二条臂)
|
||||
if [[ -n "${SITE_ROBOT_CATALOG:-}" ]]; then
|
||||
:
|
||||
fi
|
||||
@@ -132,7 +132,7 @@ for ip in 192.168.2.100 192.168.101.1 192.168.2.101 "${SITE_ROBOT_IP:-}"; do
|
||||
[[ -z "$ip" ]] && continue
|
||||
ping_one "$ip" && ok=1 || true
|
||||
done
|
||||
# unique
|
||||
# 去重
|
||||
echo
|
||||
if ping -c 1 -W 1 "${SITE_ROBOT_IP:-192.168.2.100}" >/dev/null 2>&1; then
|
||||
echo "OK — 当前臂 ${SITE_ROBOT_SN:-} @ ${SITE_ROBOT_IP} 可达。RDK 用 SN 连接。"
|
||||
|
||||
@@ -88,7 +88,7 @@ REGEX_REPLACEMENTS: list[tuple[str, str]] = [
|
||||
|
||||
|
||||
def render(site_text: str) -> str:
|
||||
"""Turn the live config text into the anonymised template text."""
|
||||
"""把现场配置文本转换成匿名化后的模板文本。"""
|
||||
out = site_text
|
||||
for old, new in LITERAL_REPLACEMENTS:
|
||||
out = out.replace(old, new)
|
||||
|
||||
+15
-15
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Move Rizon4 to a named pose (default: home) and FORCE stop.
|
||||
# 把 Rizon4 移动到指定命名的位姿(默认:home)并强制停止。
|
||||
#
|
||||
# Critical: visual_servo.launch / servo_node must be killed first, otherwise
|
||||
# they keep streaming JointTrajectory and the arm never settles.
|
||||
# 关键:必须先杀掉 visual_servo.launch / servo_node,否则
|
||||
# 它们会一直发 JointTrajectory,机械臂永远稳定不下来。
|
||||
#
|
||||
# Usage:
|
||||
# 用法:
|
||||
# bash ~/ros2/scripts/go_home.sh
|
||||
# bash ~/ros2/scripts/go_home.sh --pose ready --speed 0.3
|
||||
|
||||
@@ -39,7 +39,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--pose) POSE="${2:?}"; shift 2 ;;
|
||||
--speed) SPEED="${2:?}"; shift 2 ;;
|
||||
--keep-servo) KEEP_SERVO=1; shift ;;
|
||||
--stop-track) shift ;; # legacy
|
||||
--stop-track) shift ;; # 历史遗留
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
@@ -56,10 +56,10 @@ SPEED="${SPEED:-${SITE_GO_HOME_SPEED:-0.25}}"
|
||||
|
||||
kill_streamers() {
|
||||
echo "=== killing visual-servo streamers ==="
|
||||
# Prefer disable control before kill (if node still up)
|
||||
# 能先禁用控制就禁用(如果节点还活着),再动手 kill
|
||||
ros2 param set /servo_node enable_control false >/dev/null 2>&1 || true
|
||||
|
||||
# Kill launch parent first, then executables (SIGTERM then SIGKILL)
|
||||
# 先杀 launch 父进程,再杀可执行文件(先 SIGTERM 再 SIGKILL)
|
||||
local patterns=(
|
||||
'visual_servo.launch.py'
|
||||
'/lib/arm_application/servo_node'
|
||||
@@ -115,7 +115,7 @@ n.destroy_node(); rclpy.shutdown()
|
||||
PY
|
||||
}
|
||||
|
||||
# Strong settle via controller ACTION (not topic — matches JTC reliably)
|
||||
# 用控制器的 ACTION 做强力稳定(不走话题 —— 这样能可靠地匹配 JTC)
|
||||
settle_hold_action() {
|
||||
local pose="$1"
|
||||
python3 - "$pose" <<'PY'
|
||||
@@ -160,7 +160,7 @@ def spin_until(pred, timeout=3.0):
|
||||
|
||||
spin_until(lambda: js_box[0] is not None, 3.0)
|
||||
|
||||
# Two-point: current -> target (or target hold), BOTH with zero velocity
|
||||
# 两点轨迹:当前位姿 -> 目标位姿(或保持在目标位姿),两点速度都为零
|
||||
q_now = list(target)
|
||||
if js_box[0] is not None:
|
||||
m = dict(zip(js_box[0].name, js_box[0].position))
|
||||
@@ -172,14 +172,14 @@ if js_box[0] is not None:
|
||||
def send_hold(q, T=0.40):
|
||||
traj = JointTrajectory()
|
||||
traj.joint_names = list(JOINTS)
|
||||
# start
|
||||
# 起点
|
||||
p0 = JointTrajectoryPoint()
|
||||
p0.positions = [float(v) for v in q]
|
||||
p0.velocities = [0.0] * 7
|
||||
p0.accelerations = [0.0] * 7
|
||||
p0.time_from_start.sec = 0
|
||||
p0.time_from_start.nanosec = 50_000_000
|
||||
# end same pose, still zero vel (pin)
|
||||
# 终点是同一姿势,速度仍为零(钉住)
|
||||
p1 = JointTrajectoryPoint()
|
||||
p1.positions = [float(v) for v in q]
|
||||
p1.velocities = [0.0] * 7
|
||||
@@ -203,11 +203,11 @@ def send_hold(q, T=0.40):
|
||||
spin_until(lambda: rfut.done(), T + 3.0)
|
||||
return True
|
||||
|
||||
# 1) snap toward named pose with zero end vel
|
||||
# 1) 先朝命名位姿快速靠拢,末端速度为零
|
||||
print(f" action hold → {name} (zero vel)")
|
||||
send_hold(target, T=0.60)
|
||||
time.sleep(0.4)
|
||||
# 2–4) re-pin measured pose several times (align JTC open-loop cmd to encoder)
|
||||
# 2–4) 反复把实测位姿重新钉住(让 JTC 的开环指令与编码器读数对齐)
|
||||
for i in range(3):
|
||||
spin_until(lambda: js_box[0] is not None, 1.0)
|
||||
if js_box[0] is None:
|
||||
@@ -218,7 +218,7 @@ for i in range(3):
|
||||
send_hold(q_pin, T=0.45)
|
||||
time.sleep(0.25)
|
||||
|
||||
# Wait until |qd| small
|
||||
# 等到 |qd| 足够小
|
||||
print(" waiting for joints to stop ...")
|
||||
t0 = time.monotonic()
|
||||
while time.monotonic() - t0 < 5.0:
|
||||
@@ -270,7 +270,7 @@ sleep "${WAIT_SEC}"
|
||||
echo "=== settle (action, vel=0) ==="
|
||||
settle_hold_action "${POSE}"
|
||||
|
||||
# Final check: servo must still be dead
|
||||
# 最后确认:servo 必须仍然是死的
|
||||
if pgrep -f '/lib/arm_application/servo_node' >/dev/null 2>&1; then
|
||||
echo "WARNING: servo_node came back — killing again"
|
||||
kill_streamers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Offline-friendly Flexiv RDK v1.9.1 install for ROS (humble-v1 / flexiv_hardware).
|
||||
# CMakeLists already supports FLEXIV_RDK_OFFLINE_LIB / vendor/rdk_libs/.
|
||||
# 面向离线场景的 Flexiv RDK v1.9.1 安装脚本(供 ROS 的 humble-v1 / `flexiv_hardware` 使用)。
|
||||
# CMakeLists 已经支持 `FLEXIV_RDK_OFFLINE_LIB` / `vendor/rdk_libs/`。
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
@@ -11,7 +11,7 @@ EXPECTED_HASH="$(tr -d '[:space:]' < "${RDK_SRC}/lib/${LIB_NAME}.sha256")"
|
||||
VENDOR_LIB="${ROOT}/vendor/rdk_libs/${LIB_NAME}"
|
||||
JOBS="${JOBS:-$(nproc)}"
|
||||
|
||||
# Prefer conda ros_flexiv toolchain (cmake/g++ often only there)
|
||||
# 优先用 conda 的 ros_flexiv 工具链(cmake/g++ 往往只在那里有)
|
||||
if ! command -v cmake >/dev/null 2>&1; then
|
||||
# shellcheck disable=SC1091
|
||||
source "${HOME}/anaconda3/etc/profile.d/conda.sh" 2>/dev/null \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Load ~/ros2/config/site.yaml and print bash export lines or JSON.
|
||||
"""读取 ``~/ros2/config/site.yaml``,打印 bash 的 export 行或 JSON。
|
||||
|
||||
Usage:
|
||||
用法:
|
||||
eval "$(python3 ~/ros2/scripts/lib/export_site_env.py)"
|
||||
python3 ~/ros2/scripts/lib/export_site_env.py --json
|
||||
python3 ~/ros2/scripts/lib/export_site_env.py --print
|
||||
@@ -35,18 +35,18 @@ def site_path() -> Path:
|
||||
def load_site(path: Path | None = None) -> dict:
|
||||
path = path or site_path()
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"site config not found: {path}")
|
||||
raise FileNotFoundError(f"找不到 site 配置: {path}")
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if yaml is None:
|
||||
raise RuntimeError("PyYAML required: pip/conda install pyyaml")
|
||||
raise RuntimeError("需要 PyYAML:pip/conda install pyyaml")
|
||||
data = yaml.safe_load(text) or {}
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f"invalid site.yaml: {path}")
|
||||
raise ValueError(f"无效的 site.yaml: {path}")
|
||||
return data
|
||||
|
||||
|
||||
def list_robots(data: dict) -> list[dict]:
|
||||
"""Catalog entries from robot.robots (list or SN-keyed map)."""
|
||||
"""从 robot.robots 取清单条目(列表或按 SN 索引的映射)。"""
|
||||
raw = (data.get("robot") or {}).get("robots") or []
|
||||
out: list[dict] = []
|
||||
if isinstance(raw, dict):
|
||||
@@ -90,7 +90,7 @@ def _deep_merge(base: dict, overlay: dict) -> dict:
|
||||
|
||||
|
||||
def normalize_robot_sn(data: dict, want: str) -> str:
|
||||
"""Map 89/91/left/right/label → full Flexiv SN."""
|
||||
"""把 89/91/left/right/label 映射成完整的 Flexiv SN。"""
|
||||
w = (want or "").strip()
|
||||
if not w:
|
||||
return w
|
||||
@@ -109,7 +109,7 @@ def normalize_robot_sn(data: dict, want: str) -> str:
|
||||
|
||||
|
||||
def resolve_robot(data: dict, sn: str | None = None) -> dict:
|
||||
"""Active arm: ROBOT_SN env overrides robot.sn; catalog fills ip/iface/frames."""
|
||||
"""当前生效的机械臂:环境变量 ``ROBOT_SN`` 覆盖 robot.sn;清单补齐 ip/iface/frames。"""
|
||||
robot = dict(data.get("robot") or {})
|
||||
catalog = list_robots(data)
|
||||
want = normalize_robot_sn(
|
||||
@@ -146,7 +146,7 @@ def resolve_robot(data: dict, sn: str | None = None) -> dict:
|
||||
|
||||
|
||||
def apply_active_arm(data: dict, sn: str | None = None) -> tuple[dict, dict]:
|
||||
"""Merge robots[] camera/gripper/paths/servo onto site defaults for the active SN."""
|
||||
"""把 robots[] 里的 camera/gripper/paths/servo 合并到当前 SN 对应的 site 默认值上。"""
|
||||
robot = resolve_robot(data, sn)
|
||||
merged = dict(data)
|
||||
for key in _ARM_SECTION_KEYS:
|
||||
@@ -157,14 +157,14 @@ def apply_active_arm(data: dict, sn: str | None = None) -> tuple[dict, dict]:
|
||||
|
||||
|
||||
def camera_ns(data: dict) -> str:
|
||||
"""Topic namespace for the active wrist camera (d405_left / d405_right)."""
|
||||
"""当前腕部相机(d405_left / d405_right)的话题命名空间。"""
|
||||
cam = data.get("camera") or {}
|
||||
name = str(cam.get("name") or "d405").strip().strip("/")
|
||||
return name or "d405"
|
||||
|
||||
|
||||
def camera_frames(data: dict) -> tuple[str, str, str]:
|
||||
"""(namespace, optical_frame, depth_topic) for the active arm."""
|
||||
"""当前机械臂的(命名空间, 光学坐标系, 深度话题)。"""
|
||||
ns = camera_ns(data)
|
||||
yolo = data.get("yolo") or {}
|
||||
frame = str(yolo.get("camera_frame") or "").strip()
|
||||
@@ -177,7 +177,7 @@ def camera_frames(data: dict) -> tuple[str, str, str]:
|
||||
|
||||
|
||||
def flatten(data: dict) -> dict:
|
||||
"""Flat SITE_* env map for bash scripts."""
|
||||
"""供 bash 脚本使用的扁平 ``SITE_*`` 环境变量映射。"""
|
||||
data, robot = apply_active_arm(data)
|
||||
paths = data.get("paths") or {}
|
||||
cam = data.get("camera") or {}
|
||||
@@ -341,7 +341,7 @@ def flatten(data: dict) -> dict:
|
||||
|
||||
|
||||
def gripper_overrides(data: dict) -> dict:
|
||||
"""ROS parameter dict for dh_pgea_node (DH Modbus, not Flexiv gripper)."""
|
||||
"""供 dh_pgea_node 使用的 ROS 参数字典(DH Modbus,不是 Flexiv 夹爪)。"""
|
||||
data, _robot = apply_active_arm(data)
|
||||
g = data.get("gripper") or {}
|
||||
return {
|
||||
@@ -362,7 +362,7 @@ def gripper_overrides(data: dict) -> dict:
|
||||
|
||||
|
||||
def perception_overrides(data: dict) -> dict:
|
||||
"""ROS parameter dict for perception_node."""
|
||||
"""供 perception_node 使用的 ROS 参数字典。"""
|
||||
data, _robot = apply_active_arm(data)
|
||||
paths = data.get("paths") or {}
|
||||
yolo = data.get("yolo") or {}
|
||||
@@ -415,7 +415,7 @@ def perception_overrides(data: dict) -> dict:
|
||||
|
||||
|
||||
def servo_overrides(data: dict) -> dict:
|
||||
"""ROS parameter dict for servo_node."""
|
||||
"""供 servo_node 使用的 ROS 参数字典。"""
|
||||
data, robot = apply_active_arm(data)
|
||||
paths = data.get("paths") or {}
|
||||
yolo = data.get("yolo") or {}
|
||||
@@ -431,7 +431,7 @@ def servo_overrides(data: dict) -> dict:
|
||||
"arm_label": str(robot.get("label") or robot.get("arm") or ""),
|
||||
"z_ref_m": float(servo.get("z_ref_m") or yolo.get("z_ref_m") or 0.40),
|
||||
}
|
||||
# Pass through remaining servo keys that match node params
|
||||
# 透传其余与节点参数同名的 servo 键
|
||||
keys = [
|
||||
"control_hz", "watchdog_timeout_sec", "lost_timeout_sec", "stale_hold_sec",
|
||||
"max_extrapolate_sec", "predict_enable", "predict_sec",
|
||||
@@ -460,7 +460,7 @@ def main() -> int:
|
||||
ap.add_argument("--json", action="store_true")
|
||||
ap.add_argument("--print", action="store_true", dest="do_print")
|
||||
ap.add_argument("--config", default="")
|
||||
ap.add_argument("--sn", "--arm", dest="sn", default="", help="89|91|left|right|full SN")
|
||||
ap.add_argument("--sn", "--arm", dest="sn", default="", help="89|91|left|right|完整 SN")
|
||||
args = ap.parse_args()
|
||||
if args.sn:
|
||||
os.environ["ROBOT_SN"] = str(args.sn)
|
||||
@@ -471,35 +471,35 @@ def main() -> int:
|
||||
print(json.dumps({"site": data, "env": flat}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if args.do_print:
|
||||
print(f"site config: {path}")
|
||||
print(f"site 配置: {path}")
|
||||
print(
|
||||
f" robot SN : {flat['SITE_ROBOT_SN']}"
|
||||
f" 机械臂 SN : {flat['SITE_ROBOT_SN']}"
|
||||
f"{(' (' + flat['SITE_ROBOT_LABEL'] + ')') if flat.get('SITE_ROBOT_LABEL') else ''}"
|
||||
f" arm={flat.get('SITE_ROBOT_ARM') or '?'}"
|
||||
)
|
||||
print(f" catalog : {flat.get('SITE_ROBOT_CATALOG') or '(none)'}")
|
||||
print(f" robot IP : {flat['SITE_ROBOT_IP']} pc={flat['SITE_PC_IP']} iface={flat.get('SITE_NET_IFACE') or '?'}")
|
||||
print(f" frames : prefix={flat['SITE_JOINT_PREFIX']} ee={flat['SITE_EE_FRAME']}")
|
||||
print(f" tool name : {flat.get('SITE_ROBOT_TOOL_NAME') or '(keep current)'}")
|
||||
print(f" 清单 : {flat.get('SITE_ROBOT_CATALOG') or '(无)'}")
|
||||
print(f" 机械臂 IP : {flat['SITE_ROBOT_IP']} pc={flat['SITE_PC_IP']} iface={flat.get('SITE_NET_IFACE') or '?'}")
|
||||
print(f" 坐标系 : prefix={flat['SITE_JOINT_PREFIX']} ee={flat['SITE_EE_FRAME']}")
|
||||
print(f" 工具名 : {flat.get('SITE_ROBOT_TOOL_NAME') or '(保持当前)'}")
|
||||
catalog_sns = [str(r.get("sn") or "") for r in list_robots(data)]
|
||||
if catalog_sns and flat["SITE_ROBOT_SN"] not in catalog_sns:
|
||||
print(
|
||||
f" WARNING: robot.sn {flat['SITE_ROBOT_SN']!r} is not in robots[]",
|
||||
f" 警告:robot.sn {flat['SITE_ROBOT_SN']!r} 不在 robots[] 里",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(f" yolo weights : {flat['SITE_YOLO_WEIGHTS']}")
|
||||
print(f" resolved : {flat['SITE_YOLO_WEIGHTS_RESOLVED']}")
|
||||
print(f" handeye : {flat['SITE_HANDEYE_FILE']}")
|
||||
print(f" handeye wp : {flat['SITE_HANDEYE_WAYPOINTS']}")
|
||||
print(f" handeye dir : {flat['SITE_HANDEYE_WORKDIR']}")
|
||||
print(f" named poses : {flat.get('SITE_NAMED_POSES_FILE')}")
|
||||
print(f" YOLO 权重 : {flat['SITE_YOLO_WEIGHTS']}")
|
||||
print(f" 解析后 : {flat['SITE_YOLO_WEIGHTS_RESOLVED']}")
|
||||
print(f" 手眼 : {flat['SITE_HANDEYE_FILE']}")
|
||||
print(f" 手眼路点 : {flat['SITE_HANDEYE_WAYPOINTS']}")
|
||||
print(f" 手眼目录 : {flat['SITE_HANDEYE_WORKDIR']}")
|
||||
print(f" 命名位姿 : {flat.get('SITE_NAMED_POSES_FILE')}")
|
||||
print(
|
||||
" board : "
|
||||
" 标定板 : "
|
||||
f"{flat['SITE_HANDEYE_BOARD_COLS']}x{flat['SITE_HANDEYE_BOARD_ROWS']} "
|
||||
f"inner, square={flat['SITE_HANDEYE_SQUARE_M']} m"
|
||||
f"内角点, square={flat['SITE_HANDEYE_SQUARE_M']} m"
|
||||
)
|
||||
print(
|
||||
f" camera : serial={flat['SITE_CAM_SERIAL'] or '(empty)'} "
|
||||
f" 相机 : serial={flat['SITE_CAM_SERIAL'] or '(空)'} "
|
||||
f"{flat['SITE_CAM_WIDTH']}x{flat['SITE_CAM_HEIGHT']}@{flat['SITE_CAM_FPS']} "
|
||||
f"ae={flat.get('SITE_CAM_AUTO_EXPOSURE')} "
|
||||
f"exp={flat.get('SITE_CAM_EXPOSURE_US')}us "
|
||||
@@ -507,26 +507,26 @@ def main() -> int:
|
||||
f"ae_limit={flat.get('SITE_CAM_AE_LIMIT_US')}us emitter={flat.get('SITE_CAM_EMITTER')}"
|
||||
)
|
||||
print(
|
||||
f" catalog cam : leftD405={flat.get('SITE_D405_LEFT_SERIAL')} "
|
||||
f" 清单相机 : leftD405={flat.get('SITE_D405_LEFT_SERIAL')} "
|
||||
f"rightD405={flat.get('SITE_D405_RIGHT_SERIAL')} "
|
||||
f"D435={flat.get('SITE_D435_SERIAL')}"
|
||||
)
|
||||
print(
|
||||
f" gripper : {flat.get('SITE_GRIP_MODEL')} port={flat.get('SITE_GRIP_PORT')}"
|
||||
f" 夹爪 : {flat.get('SITE_GRIP_MODEL')} port={flat.get('SITE_GRIP_PORT')}"
|
||||
)
|
||||
print(
|
||||
f" hole_detect : backend={flat.get('SITE_HOLE_BACKEND')} "
|
||||
f"weights={flat.get('SITE_HOLE_WEIGHTS')}"
|
||||
)
|
||||
print(
|
||||
f" tactile : backend={flat.get('SITE_TACTILE_BACKEND')} "
|
||||
f" 触觉 : backend={flat.get('SITE_TACTILE_BACKEND')} "
|
||||
f"L={flat.get('SITE_TACTILE_LEFT')} R={flat.get('SITE_TACTILE_RIGHT')} "
|
||||
f"cfg={flat.get('SITE_TACTILE_CONFIG')}"
|
||||
)
|
||||
print(f" go_home : pose={flat['SITE_GO_HOME_POSE']} speed={flat['SITE_GO_HOME_SPEED']}")
|
||||
he_path = Path(flat["SITE_HANDEYE_FILE"]).expanduser()
|
||||
if not he_path.is_file():
|
||||
print(f" WARNING: hand-eye file missing: {he_path}", file=sys.stderr)
|
||||
print(f" 警告:手眼文件缺失: {he_path}", file=sys.stderr)
|
||||
else:
|
||||
try:
|
||||
he_data = yaml.safe_load(he_path.read_text(encoding="utf-8")) or {}
|
||||
@@ -536,28 +536,28 @@ def main() -> int:
|
||||
he_data.get("uncalibrated") or "T_4x4" not in he_data
|
||||
):
|
||||
print(
|
||||
f" WARNING: {he_path.name} is not calibrated for this arm "
|
||||
f" 警告:{he_path.name} 没有针对该机械臂标定 "
|
||||
f"(run_handeye.sh teach/calibrate --arm {flat.get('SITE_ROBOT_ARM') or '91'})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if "063991" in flat["SITE_ROBOT_SN"] and flat.get("SITE_CAM_SERIAL") == flat.get("SITE_D405_LEFT_SERIAL"):
|
||||
print(
|
||||
" WARNING: 91 is using the LEFT wrist D405 serial — check robots[].camera.serial",
|
||||
" 警告:91 用的是左腕 D405 的序列号 — 请检查 robots[].camera.serial",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if "RGI" in str(flat.get("SITE_GRIP_MODEL") or ""):
|
||||
print(
|
||||
" NOTE: 本臂是 RGI。CLI: bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli open|rotate",
|
||||
" 提示:本臂是 RGI。CLI: bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli open|rotate",
|
||||
file=sys.stderr,
|
||||
)
|
||||
w = Path(flat["SITE_YOLO_WEIGHTS"]).expanduser()
|
||||
if not w.is_file():
|
||||
print(f" WARNING: weights file missing: {w}", file=sys.stderr)
|
||||
print(f" 警告:权重文件缺失: {w}", file=sys.stderr)
|
||||
return 2
|
||||
return 0
|
||||
# bash exports
|
||||
# bash 导出
|
||||
for k, v in flat.items():
|
||||
# escape for eval
|
||||
# 为 eval 转义
|
||||
vv = str(v).replace("'", "'\"'\"'")
|
||||
print(f"export {k}='{vv}'")
|
||||
return 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Load ~/ros2/config/site.yaml into SITE_* environment variables.
|
||||
# Usage (from other scripts):
|
||||
# 把 ~/ros2/config/site.yaml 加载成 `SITE_*` 环境变量。
|
||||
# 用法(在其它脚本里):
|
||||
# source "${ROOT}/scripts/lib/load_site_config.sh"
|
||||
|
||||
_SITE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -13,7 +13,7 @@ else
|
||||
# shellcheck disable=SC1090
|
||||
eval "$(python3 "${_SITE_EXPORTER}" --config "${_SITE_FILE}")"
|
||||
export SITE_CONFIG="${_SITE_FILE}"
|
||||
# Resolve 89/91/left/right aliases to the full Flexiv SN for RDK.
|
||||
# 把 89/91/left/right 别名解析成完整的 Flexiv SN,供 RDK 使用。
|
||||
if [[ -n "${SITE_ROBOT_SN:-}" ]]; then
|
||||
export ROBOT_SN="${SITE_ROBOT_SN}"
|
||||
fi
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Parse --sn / --arm from argv. Must be sourced BEFORE load_site_config.sh
|
||||
# so ROBOT_SN is visible to export_site_env.py.
|
||||
# 从 argv 里解析 --sn / --arm。必须在 `load_site_config.sh` 之前 source,
|
||||
# 这样 `ROBOT_SN` 才能被 export_site_env.py 看到。
|
||||
#
|
||||
# source "${ROOT}/scripts/lib/parse_arm_args.sh"
|
||||
# parse_arm_args "$@"
|
||||
# set -- "${PARSE_ARM_REST[@]}"
|
||||
#
|
||||
# Aliases (89|91|left|right) are resolved in export_site_env.py.
|
||||
# 别名(89|91|left|right)在 export_site_env.py 里解析。
|
||||
|
||||
parse_arm_args() {
|
||||
PARSE_ARM_REST=()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# List plugged RealSense cameras (serials used in site.yaml).
|
||||
# 列出已插上的 RealSense 相机(序列号在 site.yaml 里使用)。
|
||||
set -eo pipefail
|
||||
python3 - <<'PY'
|
||||
import pyrealsense2 as rs
|
||||
|
||||
@@ -73,7 +73,7 @@ def format_positions_block(poses: list[list[float]], indent: str = " ") -> st
|
||||
|
||||
def merge(yaml_path: Path, poses: list[list[float]]) -> None:
|
||||
text = yaml_path.read_text(encoding="utf-8").replace("\r\n", "\n").replace("\r", "\n")
|
||||
# Detect indent of waypoint_positions (2 or 4 spaces typical)
|
||||
# 探测 waypoint_positions 的缩进(通常 2 空格或 4 空格)
|
||||
m_ind = re.search(r"^([ \t]*)waypoint_positions:\s*\[", text, re.MULTILINE)
|
||||
indent = m_ind.group(1) if m_ind else " "
|
||||
block = format_positions_block(poses, indent=indent)
|
||||
@@ -82,7 +82,7 @@ def merge(yaml_path: Path, poses: list[list[float]]) -> None:
|
||||
rf"{re.escape(indent)}waypoint_positions:\s*\[[\s\S]*?{re.escape(indent)}\]",
|
||||
re.MULTILINE,
|
||||
)
|
||||
# Also match a one-line empty list: waypoint_positions: []
|
||||
# 也要匹配"单行空列表"的写法:waypoint_positions: []
|
||||
one_line = re.compile(
|
||||
rf"{re.escape(indent)}waypoint_positions:\s*\[\s*\]",
|
||||
re.MULTILINE,
|
||||
@@ -94,7 +94,7 @@ def merge(yaml_path: Path, poses: list[list[float]]) -> None:
|
||||
else:
|
||||
raise RuntimeError("waypoint_positions block not found in YAML")
|
||||
|
||||
# Keep num_poses in sync when present
|
||||
# 顺手把 num_poses 同步成实际点数(如果原本就有这个键)
|
||||
n = len(poses)
|
||||
if re.search(r"^\s*num_poses:\s*\d+", new_text, re.MULTILINE):
|
||||
new_text = re.sub(
|
||||
@@ -105,7 +105,7 @@ def merge(yaml_path: Path, poses: list[list[float]]) -> None:
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
else:
|
||||
# Insert num_poses just before waypoint_positions
|
||||
# 原本没有 num_poses,就插在 waypoint_positions 前面
|
||||
new_text = new_text.replace(
|
||||
block,
|
||||
f"{indent}num_poses: {n}\n{block}",
|
||||
|
||||
+24
-24
@@ -105,13 +105,13 @@ class SoftDrag:
|
||||
robot.SetJointImpedance(g, soft)
|
||||
log(f'SetJointImpedance {g} K={soft}')
|
||||
except Exception as exc:
|
||||
log(f'SetJointImpedance group {g}: {exc}')
|
||||
log(f'SetJointImpedance 组 {g}: {exc}')
|
||||
else:
|
||||
n = max(7, len(self.latest_q))
|
||||
try:
|
||||
robot.SetJointImpedance([35.0] * n, [0.7] * n)
|
||||
except Exception as exc:
|
||||
log(f'SetJointImpedance flat: {exc}')
|
||||
log(f'SetJointImpedance flat(不分组): {exc}')
|
||||
|
||||
self._stop.clear()
|
||||
self._thread = threading.Thread(target=self._loop, name='soft-drag', daemon=True)
|
||||
@@ -144,9 +144,9 @@ class SoftDrag:
|
||||
n = len(q)
|
||||
robot.SendJointPosition(q, [0.0] * n, [1.5] * n, [2.0] * n)
|
||||
except Exception as exc:
|
||||
log(f'soft loop: {exc}')
|
||||
log(f'soft 循环: {exc}')
|
||||
time.sleep(0.15)
|
||||
# ~20 Hz — keep lock released most of the time
|
||||
# 约 20 Hz —— 大部分时间让锁保持释放状态
|
||||
for _ in range(10):
|
||||
if self._stop.is_set():
|
||||
return
|
||||
@@ -158,7 +158,7 @@ class SoftDrag:
|
||||
if th is not None:
|
||||
th.join(timeout=4.0)
|
||||
if th.is_alive():
|
||||
log('soft loop still alive after join (will idle anyway)')
|
||||
log('join 之后 soft 循环仍然存活(无论如何都会切到 IDLE)')
|
||||
self._thread = None
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ def _ensure_operational(robot) -> Optional[str]:
|
||||
|
||||
|
||||
def _idle_async(robot, lock: threading.Lock, flexivrdk, timeout: float = 4.0) -> bool:
|
||||
"""Switch to IDLE without blocking the stdin loop forever."""
|
||||
"""切到 IDLE,同时避免把 stdin 循环永久阻塞住。"""
|
||||
done = threading.Event()
|
||||
|
||||
def run() -> None:
|
||||
@@ -193,7 +193,7 @@ def _idle_async(robot, lock: threading.Lock, flexivrdk, timeout: float = 4.0) ->
|
||||
threading.Thread(target=run, name='idle', daemon=True).start()
|
||||
ok = done.wait(timeout)
|
||||
if not ok:
|
||||
log('IDLE/Stop timed out — stdin loop continues')
|
||||
log('IDLE/Stop 超时 —— stdin 循环继续运行')
|
||||
return ok
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ def main() -> int:
|
||||
try:
|
||||
msg = json.loads(line)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
reply({'ok': False, 'error': f'bad json: {exc}'})
|
||||
reply({'ok': False, 'error': f'JSON 解析失败: {exc}'})
|
||||
continue
|
||||
|
||||
cmd = str(msg.get('cmd', '')).strip()
|
||||
@@ -233,17 +233,17 @@ def main() -> int:
|
||||
for args in attempts:
|
||||
try:
|
||||
robot = flexivrdk.Robot(*args)
|
||||
log(f'connected {sn} args={args!r}')
|
||||
log(f'已连接 {sn} args={args!r}')
|
||||
last_err = None
|
||||
break
|
||||
except TypeError:
|
||||
continue
|
||||
except Exception as exc:
|
||||
last_err = exc
|
||||
log(f'connect {args!r}: {exc}')
|
||||
log(f'连接 {args!r}: {exc}')
|
||||
robot = None
|
||||
if robot is None:
|
||||
reply({'ok': False, 'error': str(last_err or 'connect failed')})
|
||||
reply({'ok': False, 'error': str(last_err or '连接失败')})
|
||||
continue
|
||||
err = _ensure_operational(robot)
|
||||
if err:
|
||||
@@ -256,7 +256,7 @@ def main() -> int:
|
||||
|
||||
elif cmd == 'drag_on':
|
||||
if robot is None:
|
||||
reply({'ok': False, 'error': 'not connected'})
|
||||
reply({'ok': False, 'error': '尚未连接'})
|
||||
continue
|
||||
err = _ensure_operational(robot)
|
||||
if err:
|
||||
@@ -274,7 +274,7 @@ def main() -> int:
|
||||
reply({'ok': True, 'drag': True, 'mode': 'floating', 'primitive': name})
|
||||
continue
|
||||
except Exception as exc:
|
||||
log(f'Floating failed, fallback soft: {exc}')
|
||||
log(f'Floating 失败,退回 soft 模式: {exc}')
|
||||
|
||||
try:
|
||||
if soft is not None:
|
||||
@@ -284,18 +284,18 @@ def main() -> int:
|
||||
if soft.latest_q:
|
||||
cached_q = list(soft.latest_q)
|
||||
drag_on = True
|
||||
log('soft drag ON')
|
||||
log('软拖拽已开启')
|
||||
reply({
|
||||
'ok': True,
|
||||
'drag': True,
|
||||
'mode': 'soft',
|
||||
'hint': 'Push the arm gently; low joint impedance. Enter=photo (s=hold).',
|
||||
'hint': '轻推机械臂即可,关节阻抗已调低。Enter=拍照(s=保持)。',
|
||||
})
|
||||
except Exception as exc:
|
||||
reply({'ok': False, 'error': f'soft drag failed: {exc}'})
|
||||
reply({'ok': False, 'error': f'软拖拽失败: {exc}'})
|
||||
|
||||
elif cmd == 'drag_off':
|
||||
log('drag_off requested')
|
||||
log('收到 drag_off 请求')
|
||||
if soft is not None:
|
||||
if soft.latest_q:
|
||||
cached_q = list(soft.latest_q)
|
||||
@@ -304,7 +304,7 @@ def main() -> int:
|
||||
if robot is not None:
|
||||
_idle_async(robot, rlock, flexivrdk, timeout=4.0)
|
||||
drag_on = False
|
||||
log('drag_off done')
|
||||
log('drag_off 已完成')
|
||||
reply({'ok': True, 'drag': False, 'q': cached_q})
|
||||
|
||||
elif cmd == 'joints':
|
||||
@@ -314,14 +314,14 @@ def main() -> int:
|
||||
elif len(cached_q) >= 7:
|
||||
q = list(cached_q)
|
||||
if q is None or len(q) < 7:
|
||||
reply({'ok': False, 'error': 'no joint cache'})
|
||||
reply({'ok': False, 'error': '没有关节缓存'})
|
||||
continue
|
||||
cached_q = q
|
||||
reply({'ok': True, 'q': q, 'drag': drag_on})
|
||||
|
||||
elif cmd == 'enable':
|
||||
if robot is None:
|
||||
reply({'ok': False, 'error': '尚未 connect'})
|
||||
reply({'ok': False, 'error': '尚未连接'})
|
||||
continue
|
||||
err = ensure_operational(robot)
|
||||
reply({
|
||||
@@ -332,7 +332,7 @@ def main() -> int:
|
||||
|
||||
elif cmd == 'clear_fault':
|
||||
if robot is None:
|
||||
reply({'ok': False, 'error': '尚未 connect'})
|
||||
reply({'ok': False, 'error': '尚未连接'})
|
||||
continue
|
||||
err = rdk_clear_fault(robot)
|
||||
if err:
|
||||
@@ -349,7 +349,7 @@ def main() -> int:
|
||||
|
||||
elif cmd == 'switch_tool':
|
||||
if robot is None:
|
||||
reply({'ok': False, 'error': '尚未 connect'})
|
||||
reply({'ok': False, 'error': '尚未连接'})
|
||||
continue
|
||||
want = str(msg.get('name', '')).strip()
|
||||
|
||||
@@ -428,9 +428,9 @@ def main() -> int:
|
||||
return 0
|
||||
|
||||
else:
|
||||
reply({'ok': False, 'error': f'unknown cmd {cmd}'})
|
||||
reply({'ok': False, 'error': f'未知命令 {cmd}'})
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log(f'cmd={cmd} error: {exc}')
|
||||
log(f'cmd={cmd} 出错: {exc}')
|
||||
reply({'ok': False, 'error': str(exc)})
|
||||
return 0
|
||||
|
||||
|
||||
+27
-27
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Control Studio device GripperDahuan / GripperDahuanModbus via Flexiv RDK.
|
||||
"""通过 Flexiv RDK 控制 Studio 设备 GripperDahuan / GripperDahuanModbus。
|
||||
|
||||
Must run under RDK Python (rdk_py310). Uses a *lite* RDK instance so it can
|
||||
share the robot with run_real.sh (ros2_control holds the normal RDK).
|
||||
必须在 RDK 的 Python(rdk_py310)下运行。使用 *lite*(轻量)RDK 实例,
|
||||
以便和 run_real.sh 共用机器人(常规 RDK 被 ros2_control 占用)。
|
||||
|
||||
The controller talks RS485 itself (its own /dev/ttyUSB0), not the IPC serial.
|
||||
控制器自己走 RS485(用自带的 /dev/ttyUSB0),不走工控机的串口。
|
||||
|
||||
/home/cc/rdk_py310/bin/python ~/ros2/scripts/rdk_gripper.py --list
|
||||
/home/cc/rdk_py310/bin/python ~/ros2/scripts/rdk_gripper.py status
|
||||
@@ -65,28 +65,28 @@ def _move(grip, params, width: float, vel: float, force: float) -> None:
|
||||
vmax = float(params.max_vel)
|
||||
if vmax <= 0:
|
||||
vmax = 0.05
|
||||
# Move velocity cannot be 0 (RDK).
|
||||
# 移动速度不能为 0(RDK 限制)。
|
||||
vel = _clamp(vel, max(vmin, 1e-4), vmax)
|
||||
force = _clamp(force, float(params.min_force), float(params.max_force))
|
||||
width = _clamp(width, float(params.min_width), float(params.max_width))
|
||||
print(f"Move width={width:.4f} m vel={vel:.4f} m/s force={force:.1f} N")
|
||||
print(f"移动 width={width:.4f} m vel={vel:.4f} m/s force={force:.1f} N")
|
||||
grip.Move(width, vel, force)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="Flexiv RDK gripper (GripperDahuanModbus)")
|
||||
ap = argparse.ArgumentParser(description="Flexiv RDK 夹爪(GripperDahuanModbus)")
|
||||
ap.add_argument("--sn", default="")
|
||||
ap.add_argument("--iface", default="")
|
||||
ap.add_argument("--device", default="", help="Studio Device name")
|
||||
ap.add_argument("--force-n", type=float, default=0.0, help="force limit [N]; 0=mid")
|
||||
ap.add_argument("--vel", type=float, default=0.0, help="speed [m/s]; 0=half max")
|
||||
ap.add_argument("--device", default="", help="Studio 设备名")
|
||||
ap.add_argument("--force-n", type=float, default=0.0, help="力上限 [N];0=取中值")
|
||||
ap.add_argument("--vel", type=float, default=0.0, help="速度 [m/s];0=最大速度的一半")
|
||||
ap.add_argument(
|
||||
"cmd",
|
||||
nargs="?",
|
||||
default="status",
|
||||
choices=["list", "status", "init", "open", "close", "pos", "enable"],
|
||||
)
|
||||
ap.add_argument("value", nargs="?", default="", help="pos: meters or 0–1000")
|
||||
ap.add_argument("value", nargs="?", default="", help="pos: 米,或 0–1000")
|
||||
args = ap.parse_args()
|
||||
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "lib"))
|
||||
@@ -99,18 +99,18 @@ def main() -> int:
|
||||
iface = (args.iface or robot_cfg.get("iface") or "").strip()
|
||||
device = (args.device or g.get("device_name") or "GripperDahuanModbus").strip()
|
||||
if not sn:
|
||||
print("ERROR: robot SN missing", file=sys.stderr)
|
||||
print("错误:没拿到机械臂序列号(SN)", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
import flexivrdk
|
||||
|
||||
print(f"=== RDK lite gripper SN={sn} device={device} iface={iface or 'auto'} ===")
|
||||
print(f"=== RDK lite 夹爪 SN={sn} device={device} iface={iface or 'auto'} ===")
|
||||
robot = connect_lite(sn, iface)
|
||||
dev = flexivrdk.Device(robot)
|
||||
names = list(dev.list() or [])
|
||||
|
||||
if args.cmd == "list":
|
||||
print("Studio devices:")
|
||||
print("Studio 设备列表:")
|
||||
for n in names:
|
||||
try:
|
||||
en = dev.enabled(n)
|
||||
@@ -123,7 +123,7 @@ def main() -> int:
|
||||
|
||||
if device not in names:
|
||||
print(
|
||||
f"ERROR: device {device!r} not in Studio pool.\n available: {', '.join(names)}",
|
||||
f"错误:设备 {device!r} 不在 Studio 设备池里。\n 可用: {', '.join(names)}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
@@ -133,7 +133,7 @@ def main() -> int:
|
||||
)
|
||||
if not dev.connected(device):
|
||||
print(
|
||||
"WARN: connected=False — 控制器自己的 /dev/ttyUSB0 没接到爪。\n"
|
||||
"警告:connected=False —— 控制器自己的 /dev/ttyUSB0 没接到夹爪。\n"
|
||||
" USB–RS485 要插 Flexiv 机箱 USB(不是工控机),A/B + 24V 到 PGEA。\n"
|
||||
" 或用法兰工具口 485(腕部白绿+/绿-)。"
|
||||
)
|
||||
@@ -142,14 +142,14 @@ def main() -> int:
|
||||
if args.cmd in ("enable", "status", "init", "open", "close", "pos"):
|
||||
try:
|
||||
grip.Enable(device)
|
||||
print(f"Enable [{device}] OK")
|
||||
print(f"使能 [{device}] 成功")
|
||||
except Exception as exc:
|
||||
print(f"ERROR: Enable failed: {exc}", file=sys.stderr)
|
||||
print(f"错误:使能失败: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
params = grip.params()
|
||||
print(" params:", _fmt_params(params))
|
||||
print(" states:", _fmt_states(grip.states()))
|
||||
print(" 参数:", _fmt_params(params))
|
||||
print(" 状态:", _fmt_states(grip.states()))
|
||||
|
||||
force = float(args.force_n or g.get("force_n") or 0.0)
|
||||
if force <= 0:
|
||||
@@ -164,27 +164,27 @@ def main() -> int:
|
||||
return 0
|
||||
|
||||
if args.cmd == "init":
|
||||
print("Init (fingers home, ~10 s) …")
|
||||
print("初始化(手指回零,约 10 秒)…")
|
||||
grip.Init()
|
||||
time.sleep(2.0)
|
||||
print(" states:", _fmt_states(grip.states()))
|
||||
print(" 状态:", _fmt_states(grip.states()))
|
||||
return 0
|
||||
|
||||
if args.cmd == "open":
|
||||
_move(grip, params, float(params.max_width), vel, force)
|
||||
time.sleep(0.4)
|
||||
print(" states:", _fmt_states(grip.states()))
|
||||
print(" 状态:", _fmt_states(grip.states()))
|
||||
return 0
|
||||
|
||||
if args.cmd == "close":
|
||||
_move(grip, params, float(params.min_width), vel, force)
|
||||
time.sleep(0.4)
|
||||
print(" states:", _fmt_states(grip.states()))
|
||||
print(" 状态:", _fmt_states(grip.states()))
|
||||
return 0
|
||||
|
||||
if args.cmd == "pos":
|
||||
if args.value == "":
|
||||
print("usage: pos <meters|0-1000>", file=sys.stderr)
|
||||
print("用法: pos <米|0-1000>", file=sys.stderr)
|
||||
return 2
|
||||
raw = float(args.value)
|
||||
if raw > 1.5:
|
||||
@@ -194,7 +194,7 @@ def main() -> int:
|
||||
width = raw
|
||||
_move(grip, params, width, vel, force)
|
||||
time.sleep(0.4)
|
||||
print(" states:", _fmt_states(grip.states()))
|
||||
print(" 状态:", _fmt_states(grip.states()))
|
||||
return 0
|
||||
return 0
|
||||
|
||||
@@ -203,5 +203,5 @@ if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as exc:
|
||||
print(f"ERROR: {exc}", file=sys.stderr)
|
||||
print(f"错误:{exc}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
+38
-38
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Switch the Flexiv active tool (TCP + payload) to a Studio-configured name.
|
||||
"""把 Flexiv 当前工具(TCP + 负载)切换成 Studio 里配置好的名字。
|
||||
|
||||
Must run under RDK Python (rdk_py310). Robot must be IDLE; stop run_real.sh first
|
||||
if ros2_control already owns the RDK connection.
|
||||
必须在 RDK 的 Python(rdk_py310)下运行。机器人必须处于 IDLE;如果
|
||||
ros2_control 已经占住了 RDK 连接,请先停掉 run_real.sh。
|
||||
|
||||
/home/cc/rdk_py310/bin/python ~/ros2/scripts/rdk_switch_tool.py --list
|
||||
/home/cc/rdk_py310/bin/python ~/ros2/scripts/rdk_switch_tool.py --name Flange
|
||||
@@ -17,7 +17,7 @@ import time
|
||||
|
||||
|
||||
def _single_arm_groups(robot):
|
||||
"""RDK 1.9 Tool API is single-arm (no JointGroup). Older builds use groups."""
|
||||
"""RDK 1.9 的 Tool API 是单臂的(没有 JointGroup)。老版本用 groups。"""
|
||||
info = robot.info()
|
||||
groups = getattr(info, "single_arm_groups", None) or {}
|
||||
if isinstance(groups, dict):
|
||||
@@ -33,7 +33,7 @@ def _single_arm_groups(robot):
|
||||
return [flexivrdk.JointGroup.ARM_1]
|
||||
except Exception:
|
||||
pass
|
||||
return [None] # sentinel: call Tool.name()/Switch() without a group
|
||||
return [None] # 哨兵:调用 Tool.name()/Switch() 时不带 group
|
||||
|
||||
|
||||
def _tool_name(tool, group):
|
||||
@@ -57,7 +57,7 @@ def _tool_switch(tool, group, target: str) -> None:
|
||||
|
||||
def _group_name(group) -> str:
|
||||
if group is None:
|
||||
return "arm"
|
||||
return "单臂"
|
||||
try:
|
||||
import flexivrdk
|
||||
|
||||
@@ -91,7 +91,7 @@ def _ensure_idle(robot, mode) -> None:
|
||||
time.sleep(0.25)
|
||||
if not robot.operational():
|
||||
raise RuntimeError(
|
||||
"robot not operational — AUTO Remote + Enable, release E-stop"
|
||||
"机器人未处于 operational 状态 —— 请在示教器切 AUTO Remote 并使能,松开急停"
|
||||
)
|
||||
robot.SwitchMode(mode.IDLE)
|
||||
|
||||
@@ -108,13 +108,13 @@ def _resolve_name(tool, want: str) -> str:
|
||||
if hit:
|
||||
return hit
|
||||
raise SystemExit(
|
||||
"tool %r not found in Studio pool.\n available: %s"
|
||||
% (want, ", ".join(names) or "(empty)")
|
||||
"工具 %r 不在 Studio 工具池里。\n 可选: %s"
|
||||
% (want, ", ".join(names) or "(空)")
|
||||
)
|
||||
|
||||
|
||||
def _iface_ipv4s(name: str) -> list:
|
||||
"""FastDDS whitelist wants IPv4 addresses, not NIC names like enp4s0."""
|
||||
"""FastDDS 白名单要的是 IPv4 地址,而不是 enp4s0 这类网卡名。"""
|
||||
import socket
|
||||
import fcntl
|
||||
import struct
|
||||
@@ -147,7 +147,7 @@ def _iface_ipv4s(name: str) -> list:
|
||||
addrs.append(cidr.split("/")[0])
|
||||
except Exception:
|
||||
pass
|
||||
# unique, keep order
|
||||
# 去重,保持原有顺序
|
||||
seen = set()
|
||||
uniq = []
|
||||
for a in addrs:
|
||||
@@ -170,7 +170,7 @@ def _rdk_whitelist(iface: str) -> list[list[str]]:
|
||||
robot_lan = [a for a in _iface_ipv4s(iface) if a.startswith("192.168.2.")]
|
||||
if robot_lan:
|
||||
lists.append(robot_lan)
|
||||
# unique
|
||||
# 去重
|
||||
seen: set[tuple[str, ...]] = set()
|
||||
out: list[list[str]] = []
|
||||
for item in lists:
|
||||
@@ -185,7 +185,7 @@ def connect_rdk(sn: str, iface: str = ""):
|
||||
import flexivrdk
|
||||
|
||||
for listen in _rdk_whitelist(iface):
|
||||
print("RDK whitelist:", ", ".join(listen))
|
||||
print("RDK 白名单:", ", ".join(listen))
|
||||
for args in ((sn, listen), (sn, listen, True), (sn, listen, True, False)):
|
||||
try:
|
||||
return flexivrdk.Robot(*args)
|
||||
@@ -194,51 +194,51 @@ def connect_rdk(sn: str, iface: str = ""):
|
||||
except RuntimeError as exc:
|
||||
msg = str(exc)
|
||||
if "whitelist" in msg.lower() or "filtered out" in msg.lower():
|
||||
print(" whitelist rejected, try next")
|
||||
print(" 白名单被拒,试下一个")
|
||||
break
|
||||
if "Failed to connect" in msg or "failed to connect" in msg.lower():
|
||||
print(" search timeout with this whitelist")
|
||||
print(" 用这个白名单搜索超时")
|
||||
break
|
||||
raise
|
||||
print("retry without iface filter")
|
||||
print("不带 iface 过滤再试一次")
|
||||
return flexivrdk.Robot(sn)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="Switch Flexiv Studio tool / TCP")
|
||||
ap.add_argument("--sn", default="", help="robot serial (default: site.yaml)")
|
||||
ap.add_argument("--iface", default="", help="NIC whitelist for RDK (e.g. enp4s0)")
|
||||
ap = argparse.ArgumentParser(description="切换 Flexiv Studio 工具 / TCP")
|
||||
ap.add_argument("--sn", default="", help="机械臂序列号(默认取 site.yaml)")
|
||||
ap.add_argument("--iface", default="", help="RDK 用的网卡白名单(如 enp4s0)")
|
||||
ap.add_argument(
|
||||
"--name",
|
||||
default="",
|
||||
help="Studio tool name (Flange = no tool). Empty = site.yaml robot.tool_name",
|
||||
help="Studio 工具名(Flange = 不装工具)。留空 = 用 site.yaml 的 robot.tool_name",
|
||||
)
|
||||
ap.add_argument("--list", action="store_true", help="list tools and exit")
|
||||
ap.add_argument("--list", action="store_true", help="列出工具后退出")
|
||||
args = ap.parse_args()
|
||||
|
||||
sn = (args.sn or "").strip()
|
||||
want = (args.name or "").strip()
|
||||
if not sn:
|
||||
print("ERROR: --sn is required (run via run_real.sh / rdk_switch_tool.sh)", file=sys.stderr)
|
||||
print("错误:必须提供 --sn(请通过 run_real.sh / rdk_switch_tool.sh 运行)", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
import flexivrdk
|
||||
|
||||
mode = flexivrdk.Mode
|
||||
iface = (args.iface or "").strip()
|
||||
print(f"=== RDK tool SN={sn} iface={iface or 'auto'} ===")
|
||||
print(f"=== RDK 工具 SN={sn} iface={iface or 'auto'} ===")
|
||||
robot = None
|
||||
try:
|
||||
robot = connect_rdk(sn, iface)
|
||||
if robot.fault():
|
||||
print("fault present, ClearFault ...")
|
||||
print("检测到故障,正在 ClearFault ...")
|
||||
if not robot.ClearFault():
|
||||
print("ERROR: fault cannot be cleared", file=sys.stderr)
|
||||
print("错误:故障无法清除", file=sys.stderr)
|
||||
return 1
|
||||
tool = flexivrdk.Tool(robot)
|
||||
names = list(tool.list() or [])
|
||||
groups = _single_arm_groups(robot)
|
||||
print("Studio tools:", ", ".join(names) or "(none)")
|
||||
print("Studio 工具:", ", ".join(names) or "(无)")
|
||||
for g in groups:
|
||||
cur = _tool_name(tool, g)
|
||||
extra = ""
|
||||
@@ -246,23 +246,23 @@ def main() -> int:
|
||||
extra = " " + _fmt_params(_tool_params(tool, g))
|
||||
except Exception:
|
||||
pass
|
||||
print(f" active [{_group_name(g)}]: {cur}{extra}")
|
||||
print(f" 当前 [{_group_name(g)}]: {cur}{extra}")
|
||||
|
||||
if args.list:
|
||||
return 0
|
||||
if not want:
|
||||
print("tool_name empty — keep current (set robot.tool_name in site.yaml to switch)")
|
||||
print("RDK OK")
|
||||
print("tool_name 为空 —— 保持当前工具(要切换请在 site.yaml 里设置 robot.tool_name)")
|
||||
print("RDK 正常")
|
||||
return 0
|
||||
|
||||
target = _resolve_name(tool, want)
|
||||
already = all(_tool_name(tool, g) == target for g in groups)
|
||||
if already:
|
||||
print(f"already on [{target}]")
|
||||
print("RDK OK")
|
||||
print(f"已经处于 [{target}]")
|
||||
print("RDK 正常")
|
||||
return 0
|
||||
|
||||
print(f"switching → [{target}] (requires IDLE)")
|
||||
print(f"正在切换到 → [{target}](需要 IDLE)")
|
||||
_ensure_idle(robot, mode)
|
||||
for g in groups:
|
||||
_tool_switch(tool, g, target)
|
||||
@@ -273,16 +273,16 @@ def main() -> int:
|
||||
extra = " " + _fmt_params(_tool_params(tool, g))
|
||||
except Exception:
|
||||
pass
|
||||
print(f" now [{_group_name(g)}]: {cur}{extra}")
|
||||
print(f" 现在 [{_group_name(g)}]: {cur}{extra}")
|
||||
if cur != target:
|
||||
print(f"ERROR: switch failed, still {cur!r}", file=sys.stderr)
|
||||
print(f"错误:切换失败,当前仍是 {cur!r}", file=sys.stderr)
|
||||
return 1
|
||||
print("RDK OK")
|
||||
print("RDK 正常")
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"ERROR: {exc}", file=sys.stderr)
|
||||
print(" Stop run_real.sh first (RDK is exclusive).", file=sys.stderr)
|
||||
print(" Pendant: AUTO Remote + Enable Ethernet/RDK.", file=sys.stderr)
|
||||
print(f"错误:{exc}", file=sys.stderr)
|
||||
print(" 请先停掉 run_real.sh(RDK 是独占的)。", file=sys.stderr)
|
||||
print(" 示教器:切 AUTO Remote,并使能 Ethernet/RDK。", file=sys.stderr)
|
||||
return 1
|
||||
finally:
|
||||
robot = None
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Switch / list Flexiv Studio tools via RDK (reads name from site.yaml).
|
||||
# 通过 RDK 切换 / 列出 Flexiv Studio 工具(工具名从 site.yaml 读取)。
|
||||
#
|
||||
# bash ~/ros2/scripts/rdk_switch_tool.sh # switch to robot.tool_name
|
||||
# bash ~/ros2/scripts/rdk_switch_tool.sh --list # print Studio tool pool
|
||||
# bash ~/ros2/scripts/rdk_switch_tool.sh # 切换到 robot.tool_name
|
||||
# bash ~/ros2/scripts/rdk_switch_tool.sh --list # 打印 Studio 工具池
|
||||
# bash ~/ros2/scripts/rdk_switch_tool.sh --name Flange
|
||||
#
|
||||
# Stop run_real.sh first — RDK allows only one client.
|
||||
# 先停掉 `run_real.sh` —— RDK 只允许一个客户端。
|
||||
|
||||
set -eo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Terminal: D405 via official pyrealsense2 SDK — color + depth + stereo IR topics.
|
||||
# With --preview the Qt window runs in a SEPARATE viewer process (realsense_view)
|
||||
# so painting never slows the camera stream. Closing the window ≠ stop camera.
|
||||
# Do NOT start view_realsense_depth.sh at the same time (USB is exclusive).
|
||||
# 终端方式运行 D405:走官方 pyrealsense2 SDK —— 发布彩色 + 深度 + 双目 IR 话题。
|
||||
# 带 --preview 时,Qt 窗口跑在**独立的**查看器进程(`realsense_view`)里,
|
||||
# 这样绘制界面永远不会拖慢相机数据流。关掉窗口 ≠ 停止相机。
|
||||
# 不要同时启动 `view_realsense_depth.sh`(USB 是独占的)。
|
||||
#
|
||||
# Usage:
|
||||
# 用法:
|
||||
# bash ~/ros2/scripts/run_camera_d405.sh
|
||||
# bash ~/ros2/scripts/run_camera_d405.sh --preview
|
||||
# bash ~/ros2/scripts/run_camera_d405.sh --arm 91 --preview
|
||||
|
||||
set -eo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PREVIEW=-1 # -1 = use site; 0/1 = force
|
||||
PREVIEW=-1 # -1 = 沿用 site;0/1 = 强制
|
||||
HIGH_FPS=0
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
@@ -143,8 +143,8 @@ fi
|
||||
echo "=== next: bash ~/ros2/scripts/run_visual_servo_rt.sh --arm ${SITE_ROBOT_ARM:-89} [--control] ==="
|
||||
|
||||
if [[ "${DO_PREVIEW}" == "1" ]]; then
|
||||
# Camera publishes only; the Qt viewer runs in its own process so painting
|
||||
# can never starve the capture loop (shared GIL cost ~70 ms/frame before).
|
||||
# 相机进程只负责发布;Qt 查看器跑在自己的进程里,所以界面绘制
|
||||
# 永远不会把采集循环饿死(以前共用 GIL 时每帧要花约 70 ms)。
|
||||
pkill -f 'lib/arm_application/realsense_view' 2>/dev/null || true
|
||||
ros2 run arm_application realsense_color_pub "${CAM_ARGS[@]}" -p show_preview:=false &
|
||||
CAM_PID=$!
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Overhead D435 between the two arms (eye-to-hand). Does not open either D405.
|
||||
# 两臂之间的顶置 D435(`eye-to-hand`,相机固定在外)。不会打开任何一个 D405。
|
||||
#
|
||||
# bash ~/ros2/scripts/run_camera_d435.sh --preview
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# View URDF only (joint_state_publisher_gui + RViz)
|
||||
# 只看 URDF(joint_state_publisher_gui + RViz)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck disable=SC1091
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Both DH grippers (left PGEA + right RGI) as one ROS 2 node.
|
||||
# 把两个 DH 夹爪(左 PGEA + 右 RGI)作为同一个 ROS 2 节点启动。
|
||||
# bash ~/ros2/scripts/run_dual_gripper.sh
|
||||
# Topics: /dual_gripper_driver/left|right/{command,status}
|
||||
# Services: /dual_gripper_driver/left|right/execute (arm_msgs/srv/GripperExecute)
|
||||
# 话题:/dual_gripper_driver/left|right/{command,status}
|
||||
# 服务:/dual_gripper_driver/left|right/execute (arm_msgs/srv/GripperExecute)
|
||||
|
||||
set -eo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# DH grippers on the IPC (Modbus RTU). Not Flexiv Studio / RDK.
|
||||
# 工控机上的 DH 夹爪(Modbus RTU)。不是 Flexiv Studio / RDK 那一套。
|
||||
#
|
||||
# Left 89 PGEA parallel /dev/ttyUSB0
|
||||
# Right 91 RGI rotary /dev/ttyUSB1
|
||||
# 左臂 89 PGEA 平行夹爪 /dev/ttyUSB0
|
||||
# 右臂 91 RGI 旋转夹爪 /dev/ttyUSB1
|
||||
#
|
||||
# bash ~/ros2/scripts/run_gripper.sh --arm 89 --cli probe
|
||||
# bash ~/ros2/scripts/run_gripper.sh --arm 89 --cli init
|
||||
@@ -13,9 +13,9 @@
|
||||
# bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli close
|
||||
# bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli rotate 90
|
||||
# bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli revolutions 2
|
||||
# bash ~/ros2/scripts/run_gripper.sh --arm 89 # ROS node PGEA
|
||||
# bash ~/ros2/scripts/run_gripper.sh --arm 91 # ROS node RGI
|
||||
# bash ~/ros2/scripts/run_dual_gripper.sh # both ROS nodes
|
||||
# bash ~/ros2/scripts/run_gripper.sh --arm 89 # ROS 节点 PGEA
|
||||
# bash ~/ros2/scripts/run_gripper.sh --arm 91 # ROS 节点 RGI
|
||||
# bash ~/ros2/scripts/run_dual_gripper.sh # 两个 ROS 节点都起
|
||||
|
||||
set -eo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Hand-eye calibration helpers.
|
||||
# Scripts: ~/ros2/scripts/run_handeye.sh
|
||||
# Config & results: ~/ros2/handeye/ ← 见 handeye/README.md
|
||||
# 手眼标定辅助脚本。
|
||||
# 脚本:~/ros2/scripts/run_handeye.sh
|
||||
# 配置与结果:~/ros2/handeye/ ← 见 handeye/README.md
|
||||
# poses/<臂号>_<模式>.yaml 采点(输入)
|
||||
# results/<臂号>_<模式>.yaml 标定结果(输出)
|
||||
# calibrate.yaml 板尺寸 / 运动 / 稳定性参数
|
||||
# tmp/ 临时产物
|
||||
# Site overrides: ~/ros2/config/site.yaml → handeye:
|
||||
# 站点覆盖项:~/ros2/config/site.yaml → handeye:
|
||||
#
|
||||
# Usage:
|
||||
# 用法:
|
||||
# bash ~/ros2/scripts/run_handeye.sh calibrate
|
||||
# bash ~/ros2/scripts/run_handeye.sh waypoints
|
||||
# bash ~/ros2/scripts/run_handeye.sh teach # RDK 软拖拽(推荐)
|
||||
@@ -61,7 +61,7 @@ source "${ROOT}/scripts/env.sh"
|
||||
# shellcheck disable=SC1091
|
||||
source "${ROOT}/scripts/lib/load_site_config.sh"
|
||||
|
||||
# Prefer package source tree (editable); fall back to install share
|
||||
# 优先用包的源码树(editable 模式);退而用 install 里的 share
|
||||
APP_CFG="${ROOT}/src/arm_application/config"
|
||||
if [[ ! -d "${APP_CFG}" ]]; then
|
||||
APP_CFG="$(ros2 pkg prefix arm_application 2>/dev/null)/share/arm_application/config" || true
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# Hole detection on the current-arm wrist D405. Detect only — no insert motion.
|
||||
# 用当前臂腕部 D405 做孔检测。只检测 —— 不做插入动作。
|
||||
#
|
||||
# bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview
|
||||
# bash ~/ros2/scripts/run_hole_detect.sh --arm 89 --preview
|
||||
#
|
||||
# backend from site.yaml hole_detect.backend:
|
||||
# two_stage YOLO workpiece bbox + HoughCircles (camera_test hole_detect.py)
|
||||
# yolo YOLO boxes treated as holes
|
||||
# hough full-image HoughCircles
|
||||
# 后端取自 site.yaml 的 hole_detect.backend:
|
||||
# two_stage YOLO 工件 bbox + HoughCircles(camera_test 的 hole_detect.py)
|
||||
# yolo 把 YOLO 框直接当作孔
|
||||
# hough 整图 HoughCircles
|
||||
|
||||
set -eo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Real Rizon4 bringup — sets RDK runtime libs + launches stack backend:=real
|
||||
# Note: no `set -u` — conda/RoboStack hooks reference unset vars (CONDA_BUILD, COLCON_TRACE)
|
||||
# 真机 Rizon4 启动入口 —— 设置 RDK 运行时库,然后以 backend:=real 启动整栈
|
||||
# 注意:这里不加 `set -u` —— conda/RoboStack 的钩子会引用未绑定的变量(CONDA_BUILD、COLCON_TRACE)
|
||||
#
|
||||
# bash ~/ros2/scripts/run_real.sh
|
||||
# bash ~/ros2/scripts/run_real.sh --arm 91
|
||||
@@ -31,10 +31,10 @@ source "${ROOT}/install/setup.bash"
|
||||
# shellcheck disable=SC1091
|
||||
source "${ROOT}/scripts/lib/load_site_config.sh"
|
||||
|
||||
# flexiv_hardware must load spdlog/RBDyn from flexiv_install (not conda fmt v11)
|
||||
# `flexiv_hardware` 必须从 flexiv_install 加载 spdlog/RBDyn(不能用 conda 的 fmt v11)
|
||||
export LD_LIBRARY_PATH="${FLEXIV_PREFIX}/lib:${LD_LIBRARY_PATH:-}"
|
||||
|
||||
# SITE_ROBOT_SN is the resolved Flexiv serial (89/91 aliases already mapped).
|
||||
# `SITE_ROBOT_SN` 是解析后的 Flexiv 序列号(89/91 别名已经映射好了)。
|
||||
ROBOT_SN="${SITE_ROBOT_SN:-${ROBOT_SN:-Rizon4-063989}}"
|
||||
ROBOT_IP="${ROBOT_IP:-${SITE_ROBOT_IP:-192.168.100.1}}"
|
||||
NET_IFACE="${SITE_NET_IFACE:-}"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Quick-start: mock arm + RViz
|
||||
# 快速启动:mock 机械臂 + RViz
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck disable=SC1091
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Orisys visuo-tactile (finger cameras, not D405). Config: site.yaml tactile:
|
||||
# Orisys 视触觉(指端相机,不是 D405)。配置:site.yaml 的 tactile:
|
||||
#
|
||||
# bash ~/ros2/scripts/run_tactile.sh --cli
|
||||
# bash ~/ros2/scripts/run_tactile.sh # ROS node /tactile/left|right/status
|
||||
# bash ~/ros2/scripts/run_tactile.sh # ROS 节点 /tactile/left|right/status
|
||||
|
||||
set -eo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Real-time visual servo (reads ALL tunables from ~/ros2/config/site.yaml).
|
||||
# 实时视觉伺服(所有可调参数全部从 ~/ros2/config/site.yaml 读取)。
|
||||
#
|
||||
# bash ~/ros2/scripts/run_visual_servo_rt.sh
|
||||
# bash ~/ros2/scripts/run_visual_servo_rt.sh --control
|
||||
# bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 91 --control
|
||||
#
|
||||
# --control starts tracking from the current pose (no lift, no return-to-home).
|
||||
# --control 从当前位姿直接开始跟踪(不抬升,也不回 home)。
|
||||
# 89/91 手眼与相机各自独立:必须与 run_real.sh 同一 --arm。
|
||||
#
|
||||
# Switch YOLO weights: edit paths.yolo_weights in site.yaml
|
||||
# 切换 YOLO 权重:改 site.yaml 里的 paths.yolo_weights
|
||||
|
||||
set -eo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
@@ -42,9 +42,9 @@ fi
|
||||
|
||||
if [[ "${CONTROL}" == true ]]; then
|
||||
echo "=== waiting for rizon_arm_controller ACTIVE (T1: run_real.sh --arm ${SITE_ROBOT_ARM:-?}) ==="
|
||||
# NOTE: action-list can show zombie entries from a crashed ros2_control,
|
||||
# and `ros2 control` CLI is broken in this env (xmlrpc Fault). A raw
|
||||
# service call only answers when the controller manager is actually alive.
|
||||
# 注意:action-list 可能显示出崩溃过的 ros2_control 留下的僵尸条目,
|
||||
# 而且这个环境里的 `ros2 control` CLI 是坏的(xmlrpc Fault)。直接发
|
||||
# 服务请求只有控制器管理器真的活着时才会应答。
|
||||
ready=false
|
||||
for _i in $(seq 1 40); do
|
||||
if timeout 5 ros2 service call /controller_manager/list_controllers \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Show / validate the master site config.
|
||||
# 显示 / 校验主站点配置。
|
||||
# bash ~/ros2/scripts/show_site_config.sh
|
||||
# SITE_CONFIG=/path/to/other.yaml bash ~/ros2/scripts/show_site_config.sh
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke-test Flexiv RDK Python against site robot (no ROS / no GitHub).
|
||||
# 针对站点机器人对 Flexiv RDK Python 做冒烟测试(不需要 ROS / 不访问 GitHub)。
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck disable=SC1091
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Wire local Flexiv trees under ~/文档/Flexiv_Rizon4 into this workspace.
|
||||
# Avoids re-cloning RDK / sim plugin from GitHub.
|
||||
# 把 ~/文档/Flexiv_Rizon4 下的本地 Flexiv 代码树接入本工作区。
|
||||
# 免得再从 GitHub 重新克隆 RDK / 仿真插件。
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
@@ -30,7 +30,7 @@ if [[ -d "${LOCAL_FLEXIV}/flexiv_sim_plugin" ]]; then
|
||||
echo "Linked thirdparty/flexiv_sim_plugin -> ${LOCAL_FLEXIV}/flexiv_sim_plugin"
|
||||
fi
|
||||
|
||||
# humble-v1 ROS hardware needs RDK 1.9.x (kept separately if present)
|
||||
# humble-v1 的 ROS 硬件需要 RDK 1.9.x(如果存在,会单独保留一份)
|
||||
if [[ -d "${SRC}/flexiv_rdk.bak.1787646725" ]]; then
|
||||
ln -sfn "${SRC}/flexiv_rdk.bak.1787646725" "${SRC}/flexiv_rdk_v1"
|
||||
touch "${SRC}/flexiv_rdk.bak.1787646725/COLCON_IGNORE"
|
||||
|
||||
Reference in New Issue
Block a user