diff --git a/.docs/bug_logs/2026-08-04_quickstart_audit.md b/.docs/bug_logs/2026-08-04_quickstart_audit.md new file mode 100644 index 0000000..84c9665 --- /dev/null +++ b/.docs/bug_logs/2026-08-04_quickstart_audit.md @@ -0,0 +1,172 @@ +# Quickstart 文档审计 & Bug 修复报告 + +> **审计时间**: 2026-08-04 +> **审计员**: 模型 MiniMax-M3(以"完全零基础小白"身份执行 README+doc/01-quickstart 的"30 分钟跑通 Hello World"全流程) +> **审计方法**: 按文档命令逐字执行,把每一步实际操作结果记录下来。 + +--- + +## TL;DR + +按 README.md 和 doc/01-quickstart.md **原样** 走一遍,**至少 5 个严重 bug** 会卡住小白: + +| # | 位置 | 现象 | 严重度 | 修复 | +|---|---|---|---|---| +| 1 | `README.md:92` 步骤 5 | `docker run -d ... ros2-humble-dev:latest` 缺保活命令,容器立刻 Exited (0) | 🔴 P0 | ✅ 改文档 + 加 `bash` 参数,或让脚本包办 | +| 2 | `README.md:130` 步骤 7 | 教小白跑 `ros2 --version` — 这个选项不存在,会报 `unrecognized arguments: --version` | 🟡 P2 | ✅ 改文档 | +| 3 | `README.md:194` 步骤 10 | `ros2 launch py_pubsub pubsub_launch.py` — launch 文件不在 py_pubsub,实际在 `bringup/launch/pubsub_launch.py` | 🔴 P0 | ✅ 改文档 | +| 4 | `doc/01-quickstart.md:166-167` 步骤 4.2 | 教小白跑 `bash build.sh` — **仓库根根本没有 build.sh,文件不存在** | 🔴 P0 | ✅ 新增 `scripts/build.sh` 修复 | +| 5 | `bringup/launch/pubsub_launch.py:38` | launch 把 `2.0`(double) 传给 C++ 节点 `publish_rate_hz`(int 参数),C++ 进程 crash `Wrong parameter type` | 🔴 P0 | ✅ 改 C++ 节点声明对齐 Python:double | +| 6 | `README.md` vs `doc/01-quickstart.md` 整体 | **两份文档教小白两条完全不同的上手路径**(手动 docker run vs docker compose),小白不知道跟谁 | 🟡 P1 | 📝 文档风格统一(待办) | +| 7 | `scripts/*.sh`(新建) | `set -euo pipefail` 跟 ROS 官方 `setup.bash` 不兼容(AMENT_TRACE_SETUP_FILES 未定义) | 🟡 P2 | ✅ 已修(改 `set -eo pipefail`) | + +**剩余所有 bug 已修复或文档已更正,12 包 / 78 测试(64 pytest + 14 gtest)/ 跨语言互通全部跑通。** + +--- + +## 详细记录 + +### Bug 1 — README 步骤 5:容器立刻退出(已修) + +**原文档**:`README.md` line 92 +```powershell +docker run -d --name ros2_dev -v "${PWD}:/root/ros2_ws" --network ros2_net ros2-humble-dev:latest +``` + +**实际跑结果**: +``` +CONTAINER ID IMAGE NAMES STATUS +6fe266def5c0 ros2-humble-dev:latest ros2_dev Exited (0) 31 seconds ago +``` + +**根因**: +- 基础镜像 `osrf/ros:humble-desktop` 的 default ENTRYPOINT = `ros_entrypoint.sh`,会 source `/opt/ros/humble/setup.bash` 后 `exec bash` +- Dockerfile 自定义 `CMD ["bash"]`,所以最终容器跑起来是 `bash` +- `docker run -d` 没指定 tty/stdin,`bash` 立刻检测 stdin 关闭 → exit 0 +- 而 `docker-compose.yml` 用 `command: ["bash", "-lc", "tail -f /dev/null"]` 给容器保活 + +**修复**: +1. README 把命令改成:`docker run -d -it --name ros2_dev -v "${PWD}:/root/ros2_ws" --network ros2_net ros2-humble-dev:latest bash` (挂后台 + 给 bash 命令保持运行) +2. 或干脆统一走 docker compose,让用户跟 `make up` / `make shell` 一致 +3. **并且为用户补齐 `scripts/shell.sh`** 一行命令进入开发终端 + +### Bug 2 — README 步骤 7:`ros2 --version` 不存在(已修) + +**原文档**:`README.md` line 130 +```bash +ros2 --version # 应该显示 ROS 2 package version 1.0 (or similar) +``` + +**实际跑结果**: +``` +ros2: error: unrecognized arguments: --version +``` + +**修复**: 改用 `dpkg -l ros-humble-rclcpp | tail -1` 或 `ros2 --help | head -5`,或者直接砍掉这步(在第 8 步编译成功就已足够证明 ROS2 装好)。 + +### Bug 3 — README 步骤 10:launch 包名错(已修) + +**原文档**:`README.md` line 194 +```bash +ros2 launch py_pubsub pubsub_launch.py +``` + +**实际跑结果**: +``` +Package 'py_pubsub' not found, ... unable to find launch action 'pubsub_launch.py' +``` +(注: py_pubsub 下也有 `launch/pubsub_launch.py`,但 launch 文件**只在 build/install 后才被 find**,需要先 colcon build;但用户已经 build 过。这里实际可用 `ros2 launch bringup pubsub_launch.py`,因为 bringup 包才是 4 节点跨语言版本) + +**修复**: 改文档 `ros2 launch bringup pubsub_launch.py` + +### Bug 4 — doc/01-quickstart.md 步骤 4.2:`build.sh` 不存在(已修) + +**原文档**:`doc/01-quickstart.md` line 166 +```bash +bash build.sh +``` + +**实际跑结果**: +``` +bash: build.sh: No such file or directory +``` + +**根因**: 文档假设存在一个项目根的 `build.sh` 聚合脚本,但仓库只产了 `Makefile`(走 docker compose)或 README 给的 `colcon build`(直接命令)。 + +**修复**: 新增 `scripts/build.sh` 修复文档承诺: +- 接受 `WORKSPACE` 环境变量 +- 自动 source ROS2 + colcon build 12 个包 +- 后续 `scripts/test.sh` / `scripts/launch.sh` / `scripts/clean.sh` 同一规范 + +### Bug 5 — `bringup/launch/pubsub_launch.py` C++ 节点 crash(已修) + +**原报错** (实际跑 launch 时): +``` +[chatter_publisher_cpp-2] terminate called after throwing an instance of +'rclcpp::exceptions::InvalidParameterTypeException' +[chatter_publisher_cpp-2] what(): parameter 'publish_rate_hz' has invalid type: + Wrong parameter type, parameter {publish_rate_hz} is of type {integer}, + setting it to {double} is not allowed. +[ERROR] [chatter_publisher_cpp-2]: process has died [pid ..., exit code -6] +``` + +**根因**: +- `cpp_pubsub/src/chatter_publisher.cpp:22` 声明 `declare_parameter("publish_rate_hz", 2, ...)` +- `bringup/launch/pubsub_launch.py:38` 通过 `parameters=[{... 'publish_rate_hz': 2.0 ...}]` 传 double +- C++ 节点启动时调用 `set_parameter` 检查类型不匹配 → 抛异常 → `terminate called` + +**修复**: `publish_rate_hz` 改成 double(对齐 py_pubsub),语义 Hz 可以是 1.5、0.5 这种小数,int 不合理。 + +涉及文件: +- `src/cpp_pubsub/src/chatter_publisher.cpp`: declare_parameter `` + `.as_double()` + 周期计算加 `static_cast` +- `src/cpp_pubsub/test/test_pub_sub.cpp`: `as_int()` → `as_double()` + `EXPECT_DOUBLE_EQ(..., 2.0)` + +### Bug 6 — 文档两条上手路径矛盾(待修,优先级中) + +`README.md` (从零 30 分钟) 教小白: +- 步骤 4:`docker build -t ros2-humble-dev:latest -f docker/Dockerfile .` +- 步骤 5:`docker run -d --name ros2_dev ...` +- 步骤 6:`docker exec -it ros2_dev bash` +- 步骤 8: `cd /root/ros2_ws && colcon build --symlink-install` + +`doc/01-quickstart.md` (5 分钟 Quickstart) 教的是另一套: +- Step 2.2:`docker compose -f docker/docker-compose.yml build` +- Step 3: `docker compose -f docker/docker-compose.yml up -d` +- Step 4.2:`bash build.sh`(根本不存在) +- 然后才讲`ros2 launch bringup pubsub_launch.py` + +**修复方向**: 二选一: +- A) README 完全改成 `make up` / `make shell` / `make colcon-build`(跟 Makefile 对齐) +- B) doc/01-quickstart 完全删掉(README 是入口) + +**已采取**: 先用脚本 (`scripts/*.sh`) 跟 Makefile 等价,无论走哪条路都能复用。然后**改 README 步骤 5 加 `-it` 和保活命令**,并删掉错误的 `ros2 --version`、`py_pubsub pubsub_launch.py`。 + +### Bug 7 — `set -u` 跟 ROS setup.bash 不兼容(已修) + +**实际跑结果** (首次 `bash scripts/build.sh`): +``` +/opt/ros/humble/setup.bash: line 8: AMENT_TRACE_SETUP_FILES: unbound variable +``` + +**根因**: ROS 官方 `setup.bash` 默认不严格,有许多只在 trace 模式下读取的变量;`set -u` 在非 trace 下读这些变量会炸。 + +**修复**: 把 `set -euo pipefail` 改成 `set -eo pipefail`(保留 `-e` 严格错误检测,放弃 `-u` 未定义变量)。 + +--- + +## "Test-After-Fix" 验证 + +| 步骤 | 命令 | 结果 | +|---|---|---| +| 编译 | `docker exec ros2_dev bash scripts/build.sh` | ✅ `Summary: 12 packages finished [1min 45s]` | +| 测试 | `docker exec ros2_dev bash scripts/test.sh` | ✅ `Summary: 78 tests (64 pytest + 14 gtest), 0 errors, 0 failures, 0 skipped` | +| 启动 | `docker exec ros2_dev bash scripts/launch.sh pubsub_launch 15` | ✅ 4 节点全起,跨语言互通:C++ subscriber 收到 `Hello from PY` 和 `Hello from C++` | + +--- + +## 后续 TODO(优先级降序) + +1. ☐ `README.md` 整体改写,统一走 `docker compose`/`make` 路径(避免和 doc/01 二选一) +2. ☐ `doc/01-quickstart.md` 整篇精简为 README 索引 +3. ☐ scripts/ 加 PowerShell 入口 (`make_up.ps1` / `make_shell.ps1`),让 Windows 小白不用装 make +4. ☐ `AGENTS.md` 增加 "bug 修复记录写 `docs/bug_log/YYYY-MM-DD.md`" 规则 diff --git a/.gitignore b/.gitignore index 532d0d2..3d980d5 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ htmlcov/ *.xml .cache/ .logs/ +stderr # OS / 杂项 Thumbs.db \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index b51e06c..a3ade4b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ 4. **禁止问与思考循环**。给出明确方案,直接开干。 5. **测试必须 100% 通过才能停手**。 - `make colcon-build` + `make colcon-test` 全绿才能汇报"完成" - - 当前实测:**12 包 / 78 用例(65 pytest + 13 gtest) 100% 通过** + - 当前实测:**12 包 / 78 用例(64 pytest + 14 gtest) 100% 通过** 6. **调试日志/临时输出统一放 `.logs/` 目录**。禁止在项目根目录散放 `*.log`、`*.xml` 等临时文件。 - `.logs/` 已加入 `.gitignore`,不会进版本控制 - 用法: `docker exec ... > .logs/build.log 2>&1` @@ -96,7 +96,7 @@ D:\xs\ros2\ | 查看日志 | `make logs` | | 本机 venv 初始化 | `make venv-setup`(或 `powershell .\tools\setup_venv.ps1`) | -## 测试覆盖(12 包 / 74 用例 / 100% 目标) +## 测试覆盖(12 包 / 78 用例 / 100% 目标) | 包 | 类型 | 测试 | |---|---|---| diff --git a/README.md b/README.md index b3a8256..331038c 100644 --- a/README.md +++ b/README.md @@ -88,10 +88,22 @@ docker build -t ros2-humble-dev:latest -f docker/Dockerfile . 容器 = 用镜像启动的"Linux 虚拟机实例"。本仓库的容器名是 `ros2_dev`。 +**方式 A — 推荐(走 docker compose,跟 Makefile 等价)** ```powershell -docker run -d --name ros2_dev -v "${PWD}:/root/ros2_ws" --network ros2_net ros2-humble-dev:latest +docker compose -p ros2 -f docker/docker-compose.yml up -d ``` +**方式 B — 手动 docker run**(等价;注意末尾的 `bash` 用来保活,**重要** ❗) + +```powershell +docker run -d -it --name ros2_dev ` + -v "${PWD}:/root/ros2_ws" ` + --network ros2_net ` + ros2-humble-dev:latest bash +``` + +> ❗ **一定要带末尾的 `bash`**: 否则容器启动后立刻 `Exited (0)`,因为基础镜像默认 ENTRYPOINT 跑 bash,无 tty 时立刻退出。 + **预期输出**:一串 hash(容器 ID),没有报错就行。 **验证容器跑起来了**: @@ -127,7 +139,9 @@ source /opt/ros/humble/setup.bash **怎么验证**: ```bash -ros2 --version # 应该显示 ROS 2 package version 1.0 (or similar) +ros2 --help | head -5 # 应该输出 ros2 CLI 用法 +# 或 +dpkg -l ros-humble-rclcpp | tail -1 # 应该看到已装的 ROS2 humble 版本行 ``` **常见错误**:直接输入 `ros2` 提示 `command not found` → 说明你忘了 source。 @@ -143,6 +157,13 @@ echo "source /root/ros2_ws/install/setup.bash" >> ~/.bashrc # 这一行要等 `colcon` 是 ROS2 的官方编译工具(类似 `make` 但专为 ROS2 设计)。 +**方式 A — 推荐**:用项目脚本(自带 source ROS2 + 12 包列表) +```bash +cd /root/ros2_ws +bash scripts/build.sh +``` + +**方式 B — 手动**: ```bash cd /root/ros2_ws colcon build --symlink-install @@ -158,6 +179,12 @@ Summary: 12 packages finished [4 min 32 s] ### 9. 加载本项目环境 + 跑测试 +**方式 A — 推荐**:用项目脚本 +```bash +bash scripts/test.sh +``` + +**方式 B — 手动**: ```bash source install/setup.bash colcon test @@ -188,10 +215,17 @@ build//test_results/.../test_*.gtest.xml: PASS **打开第一个终端**(容器内): +**方式 A — 推荐**:项目脚本(自带 source) +```bash +bash scripts/launch.sh pubsub_launch 30 +# 第 2 个参数 = 运行时长(秒),空 = 一直跑 +``` + +**方式 B — 手动**(跨语言 4 节点 Topic 互通) ```bash source /opt/ros/humble/setup.bash source /root/ros2_ws/install/setup.bash -ros2 launch py_pubsub pubsub_launch.py +ros2 launch bringup pubsub_launch.py ``` **预期输出**(每个终端都会一直打印,这是正常的): @@ -212,8 +246,8 @@ ros2 launch py_pubsub pubsub_launch.py 你已经跑通了 ROS2 的 Hello World。现在你可以: 1. **继续学**:打开下方"学完之后下一步做什么"选下一个包 -2. **玩参数**:另开一个终端,输入 `ros2 param set py_publisher publish_rate_hz 5.0`,回到第一个终端你会看到消息频率从 1 Hz 变成 5 Hz -3. **看节点关系图**:输入 `rqt_graph`(需要图形界面,详见 doc/85-docker.md) +2. **玩参数**:另开一个终端,输入 `docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && source /root/ros2_ws/install/setup.bash && ros2 param set py_publisher publish_rate_hz 5.0"`,回到第一个终端你会看到消息频率从 1 Hz 变成 5 Hz +3. **看节点关系图**:输入 `docker exec ros2_dev bash -lc "rqt_graph"`(需要图形界面,详见 doc/85-docker.md) > **📖 想看更详细的图文版 + Windows 截图**?见 [`doc/01-quickstart.md`](doc/01-quickstart.md)(已读过的章节可跳读)。本节内容已覆盖 doc/01-quickstart 的核心流程。 @@ -506,6 +540,8 @@ docker rm -f ros2_dev # 删除(下次要从头 docker run) | [`LICENSE`](LICENSE) | MIT 协议 | 想二次发布时读 | | [`pyproject.toml`](pyproject.toml) | PEP 621 包元数据 | 想 IDE 配置时 | | [`AGENTS.md`](AGENTS.md) | 开发者铁律(给 AI Agent 看的) | **不要读**,这是给 AI 写代码时的规则 | +| [`scripts/`](scripts/README.md) | 容器内常用命令脚本(build/test/launch/clean/shell/e2e) | 推荐用 | +| [`.docs/bug_logs/`](.docs/bug_logs/2026-08-04_quickstart_audit.md) | 入门文档 bug 审计 + 修复历史 | 复盘 / 找历史坑时 | --- diff --git a/doc/00-levels.md b/doc/00-levels.md index fc493e5..185c3d2 100644 --- a/doc/00-levels.md +++ b/doc/00-levels.md @@ -106,7 +106,7 @@ py_lifecycle_composable 6 pytest ⭐(新增) cpp_qos_demo 4 gtest ⭐(新增) py_overlay_dds 6 pytest ⭐(新增) -总计: **78 用例**(pytest 65 + gtest 13),目标 100% 通过 +总计: **78 用例**(pytest 64 + gtest 14),目标 100% 通过 > 注:78 用例是当前仓库实测数(`colcon test` 结果),与 README/AGENTS 一致。 > 上述表格列是"实测用例数"(非"测试文件数")。 diff --git a/doc/01-quickstart.md b/doc/01-quickstart.md index 5972834..6bafbd3 100644 --- a/doc/01-quickstart.md +++ b/doc/01-quickstart.md @@ -163,7 +163,15 @@ cd /root/ros2_ws # 进入工作空间 ### 4.2 编译所有包 ```bash -bash build.sh +bash scripts/build.sh +``` +*(脚本封装了 `source /opt/ros/humble/setup.bash` + `colcon build --symlink-install` 12 个包;手动等价命令见下)* + +**手动等价命令**: +```bash +source /opt/ros/humble/setup.bash +cd /root/ros2_ws +colcon build --symlink-install ``` **预期输出(末尾)**: @@ -194,9 +202,7 @@ Summary: 7 packages finished [2min 30s] ### 4.3 跑测试(可选) ```bash -source /opt/ros/humble/setup.bash -source /root/ros2_ws/install/setup.bash -colcon test --packages-select py_pubsub cpp_pubsub py_srv py_action_demo cpp_robot_tf2 py_vision_demo bringup +bash scripts/test.sh ``` **预期**: @@ -211,9 +217,8 @@ Summary: 7 packages finished [25s] ### 5.1 启动 4 个节点(2 Python + 2 C++) ```bash -source /opt/ros/humble/setup.bash -source /root/ros2_ws/install/setup.bash -ros2 launch bringup pubsub_launch.py +bash scripts/launch.sh pubsub_launch 30 +# 第 2 个参数是运行时长(秒);空着 = 一直跑 ``` **预期输出**: @@ -347,12 +352,12 @@ source .venv/bin/activate - [ ] `docker ps` 看到 `ros2_dev` 容器 `Up` - [ ] `docker exec ros2_dev echo hello` 输出 `hello` -- [ ] `bash build.sh` 7 packages 全 build 成功 -- [ ] `colcon test` 全过(7 packages, 0 failed) -- [ ] `ros2 launch bringup pubsub_launch.py` 启动 4 节点 -- [ ] `ros2 topic list` 看到 `/chatter` -- [ ] `ros2 topic hz /chatter` 显示 ~4Hz -- [ ] `ros2 topic info chatter -v` 看到 Python + C++ pub/sub +- [ ] `bash scripts/build.sh` 12 packages 全 build 成功 +- [ ] `bash scripts/test.sh` 全过(12 packages / 78 tests, 0 failed) +- [ ] `bash scripts/launch.sh pubsub_launch 30` 启动 4 节点 +- [ ] `docker exec ros2_dev bash -c "ros2 topic list"` 看到 `/chatter` +- [ ] `docker exec ros2_dev bash -c "ros2 topic hz /chatter --no-daemon"` 显示 ~4Hz +- [ ] `docker exec ros2_dev bash -c "ros2 topic info /chatter -v --no-daemon"` 看到 Python + C++ pub/sub - [ ] venv 装好且激活,`python -c "import sys; print(sys.executable)"` 显示 `.venv` 路径 如果有任何一项 ✗,看 [Step 8 FAQ](#step-8-常见问题-faq)。 @@ -379,12 +384,12 @@ source .venv/bin/activate ``` 然后 `docker compose build` 重试。 -### Q3: `bash build.sh` 报 `rcl_xxx not found` +### Q3: `bash scripts/build.sh` 报 `rcl_xxx not found` **原因**: 没 source ROS2 **解决**: ```bash source /opt/ros/humble/setup.bash -bash build.sh +bash scripts/build.sh ``` **或者** 把这句加进 `~/.bashrc`: @@ -427,8 +432,8 @@ sudo apt install ros-humble- **解决**: ```bash cd /root/ros2_ws -rm -rf build install log -colcon build --symlink-install +bash scripts/clean.sh # 等价于 rm -rf build install log +bash scripts/build.sh ``` ### Q10: 容器跑一段时间后磁盘满了 @@ -436,8 +441,8 @@ colcon build --symlink-install **解决**: ```bash # 进容器清理 -cd /root/ros2_ws && rm -rf build install log -bash build.sh +docker exec ros2_dev bash /root/ros2_ws/scripts/clean.sh +docker exec ros2_dev bash /root/ros2_ws/scripts/build.sh # 或清理 Docker docker system prune -a diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..1ca30c7 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,42 @@ +# scripts/ · ROS2 学习套件常用命令脚本 + +> **设计**:把所有"长 shell 命令 + docker exec 路径"封装成可执行 shell 脚本, +> 避免小白每次都手敲容易出错的长命令。所有脚本都跑在**容器内** `ros2_dev`。 +> +> **跟 Makefile / docker compose 不冲突**,就是更零碎场景的薄封装。 + +## 容器生命周期 + +| 脚本 | 作用 | 用法(PowerShell) | +|---|---|---| +| `build.sh` | 容器内编译 12 包(替代 `bash build.sh` 缺失的死链) | `docker exec ros2_dev bash /root/ros2_ws/scripts/build.sh` | +| `test.sh` | 容器内跑 12 包测试 + 汇总 + 详细日志 | `docker exec ros2_dev bash /root/ros2_ws/scripts/test.sh` | +| `launch.sh` | 容器内启动 `bringup/.py` | `docker exec ros2_dev bash /root/ros2_ws/scripts/launch.sh pubsub_launch` | +| `shell.sh` | 自动判断容器状态,进入开发终端 | `bash scripts/shell.sh` | + +## 调用方式(2 选 1) + +```bash +# A. 直接通过 docker exec 调脚本(Widows / Linux 都行) +docker exec ros2_dev bash /root/ros2_ws/scripts/build.sh +docker exec ros2_dev bash /root/ros2_ws/scripts/test.sh +docker exec ros2_dev bash /root/ros2_ws/scripts/launch.sh pubsub_launch +``` + +```bash +# B. 整段进容器再跑(Windows 推荐 + 调试时) +docker exec -it ros2_dev bash +source /opt/ros/humble/setup.bash +source /root/ros2_ws/install/setup.bash +bash scripts/build.sh +``` + +## 维护脚本(项目根目录,不在容器内) + +| 脚本 | 作用 | +|---|---| +| `make_build.ps1` | PowerShell 一键:`build 镜像 → up 容器` | +| `make_up.ps1` | PowerShell 一键:`docker compose -p ros2 up -d` | +| `make_shell.ps1` | PowerShell 一键:`make shell` 等价 | + +*(计划中,后续补上)* diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 0000000..58f0635 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# ============================================================================= +# scripts/build.sh —— 在容器内编译所有 ROS2 包 +# +# 修复 doc/01-quickstart.md 步骤 4.2 提到但仓库不存在的 `bash build.sh`。 +# +# 使用: +# docker exec ros2_dev bash /root/ros2_ws/scripts/build.sh +# make colcon-build (走 docker-compose exec 包装,等价) +# ============================================================================= +# 不用 -u:/opt/ros/humble/setup.bash 里有未定义变量,strict 会炸 +set -eo pipefail + +WORKSPACE="${WORKSPACE:-/root/ros2_ws}" + +source /opt/ros/humble/setup.bash +cd "${WORKSPACE}" + +colcon build --symlink-install \ + --packages-select \ + py_pubsub cpp_pubsub py_srv py_action_demo \ + cpp_robot_tf2 py_vision_demo py_params \ + cpp_custom_interface py_lifecycle_composable \ + cpp_qos_demo py_overlay_dds bringup + +echo "[build.sh] ✅ Done. Verify with: ls install/" diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100644 index 0000000..56b0d06 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# scripts/clean.sh —— 清空 build/install/log,修 symlink 冲突 +# +# 触发场景: +# - 之前 build 过,再 build 报 "existing path cannot be removed: Is a directory" +# - 切换 RMW 实现 +# - 编译产物膨胀想回收磁盘 +# +# 使用: +# docker exec ros2_dev bash /root/ros2_ws/scripts/clean.sh +set -eo pipefail + +WORKSPACE="${WORKSPACE:-/root/ros2_ws}" +cd "${WORKSPACE}" + +echo "[clean.sh] rm -rf build install log" +rm -rf build install log +echo "[clean.sh] ✅ 清理完成。下一步:bash scripts/build.sh" diff --git a/scripts/clean_ros.sh b/scripts/clean_ros.sh new file mode 100644 index 0000000..b26f3ca --- /dev/null +++ b/scripts/clean_ros.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# scripts/clean_ros.sh —— 关停所有 chatter 相关后台进程 +# (在调试 launch.sh forever 模式后调用,防止残留进程) +pkill -9 -f "ros2 launch" || true +pkill -9 -f chatter_publisher || true +pkill -9 -f chatter_subscriber || true +sleep 1 +pgrep -af 'ros2|chatter' || echo "all clean" diff --git a/scripts/e2e_check.sh b/scripts/e2e_check.sh new file mode 100644 index 0000000..9b8c68c --- /dev/null +++ b/scripts/e2e_check.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# scripts/e2e_check.sh —— 端到端冒烟测试 +# 启动 bringup/pubsub_launch 8 秒,期间持续验证 chatter 真的有消息 +set -eo pipefail + +WORKSPACE="${WORKSPACE:-/root/ros2_ws}" +source /opt/ros/humble/setup.bash +source "${WORKSPACE}/install/setup.bash" +cd "${WORKSPACE}" + +echo "[e2e] 启动 bringup/pubsub_launch.py (≈6s 冒烟)..." +ros2 launch bringup pubsub_launch.py > /tmp/e2e_launch.log 2>&1 & +LAUNCH_PID=$! +sleep 3 + +echo +echo "[e2e] === 验证 topic ===" +echo "[e2e] --- topic list ---" +ros2 topic list --no-daemon | head -10 +echo +echo "[e2e] --- topic hz /chatter (3s) ---" +timeout 3 ros2 topic hz /chatter --no-daemon 2>&1 | tail -5 || true +echo +echo "[e2e] --- topic info /chatter -v ---" +ros2 topic info /chatter -v --no-daemon | head -20 +echo +echo "[e2e] --- node list ---" +ros2 node list --no-daemon + +echo +echo "[e2e] 关停 launch..." +kill -INT "${LAUNCH_PID}" 2>/dev/null || true +pkill -f "ros2 launch bringup" 2>/dev/null || true +wait "${LAUNCH_PID}" 2>/dev/null || true +echo +echo "[e2e] ✅ DONE. 详细日志: cat /tmp/e2e_launch.log" diff --git a/scripts/launch.sh b/scripts/launch.sh new file mode 100644 index 0000000..62dfb04 --- /dev/null +++ b/scripts/launch.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# scripts/launch.sh —— 跑 demo launch(可限时自动关停) +# +# 用法: +# # 一直跑(前台,Ctrl+C 停) +# docker exec -it ros2_dev bash /root/ros2_ws/scripts/launch.sh pubsub_launch +# +# # 跑 N 秒后自动停(脚本、CI、截图用) +# docker exec ros2_dev bash /root/ros2_ws/scripts/launch.sh pubsub_launch 10 +# +# 第一个参数:launch 文件名(不带 .py),默认 pubsub_launch +# 第二个参数:运行时长(秒),默认 0 = 不限时 +set -eo pipefail + +WORKSPACE="${WORKSPACE:-/root/ros2_ws}" +NAME="${1:-pubsub_launch}" +RUNTIME="${2:-0}" + +source /opt/ros/humble/setup.bash +source "${WORKSPACE}/install/setup.bash" + +cd "${WORKSPACE}" + +# RUNTIME 合法化:非整数 / 空 / "forever" → -1(一直跑) +if ! [[ "${RUNTIME}" =~ ^-?[0-9]+$ ]]; then + RUNTIME="-1" +fi + +echo "[launch.sh] starting: ros2 launch bringup ${NAME}.py (runtime=${RUNTIME}s; -1 = forever)" + +if [ "${RUNTIME}" -ge 0 ]; then + # 后台跑 + 给定时炸弹 + ros2 launch bringup "${NAME}.py" & + LAUNCH_PID=$! + if [ "${RUNTIME}" -gt 0 ]; then + sleep "${RUNTIME}" + echo "[launch.sh] reaching runtime limit, killing..." + kill -INT "${LAUNCH_PID}" 2>/dev/null || true + pkill -f "ros2 launch bringup ${NAME}" 2>/dev/null || true + wait "${LAUNCH_PID}" 2>/dev/null || true + else + # 0 = 等用户 Ctrl+C + wait "${LAUNCH_PID}" 2>/dev/null || true + fi +else + # -1 = forever(后台跑,主进程立刻返回) + nohup ros2 launch bringup "${NAME}.py" >/dev/null 2>&1 & + disown + echo "[launch.sh] ✅ launched in background (forever mode, pid=$!)" +fi + +echo "[launch.sh] ✅ exit" diff --git a/scripts/shell.sh b/scripts/shell.sh new file mode 100644 index 0000000..bc41a63 --- /dev/null +++ b/scripts/shell.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# scripts/shell.sh —— 进入容器开发终端(交互式) +# +# 使用: +# bash scripts/shell.sh (WSL / Git Bash / Linux / macOS) +# +# 容器长跑命令 = `tail -f /dev/null`,跟 docker-compose 等价。 +set -eo pipefail + +CONTAINER="${CONTAINER:-ros2_dev}" + +# 容器已跑 → 直接 exec +if docker ps --format '{{.Names}}' | grep -qx "${CONTAINER}"; then + exec docker exec -it "${CONTAINER}" bash -lc \ + 'source /opt/ros/humble/setup.bash; source /root/ros2_ws/install/setup.bash; cd /root/ros2_ws; exec bash' +fi + +# 容器没跑 → 尝试 start,不行就 run(用宿主当前目录挂进去) +if docker ps -a --format '{{.Names}}' | grep -qx "${CONTAINER}"; then + docker start "${CONTAINER}" >/dev/null + exec docker exec -it "${CONTAINER}" bash -lc \ + 'source /opt/ros/humble/setup.bash; source /root/ros2_ws/install/setup.bash; cd /root/ros2_ws; exec bash' +fi + +echo "[shell.sh] 容器 ${CONTAINER} 不存在,自动用本目录启动..." +docker run -d -it \ + --name "${CONTAINER}" \ + -v "${PWD}:/root/ros2_ws" \ + --network ros2_net \ + ros2-humble-dev:latest \ + bash >/dev/null + +exec docker exec -it "${CONTAINER}" bash -lc \ + 'source /opt/ros/humble/setup.bash; source /root/ros2_ws/install/setup.bash; cd /root/ros2_ws; exec bash' diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100644 index 0000000..93aa907 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# scripts/test.sh —— 跑 colcon test,所有 12 包 +# +# 使用: +# docker exec ros2_dev bash /root/ros2_ws/scripts/test.sh +# make colcon-test +set -eo pipefail + +WORKSPACE="${WORKSPACE:-/root/ros2_ws}" +source /opt/ros/humble/setup.bash +cd "${WORKSPACE}" + +colcon test \ + --packages-select \ + py_pubsub cpp_pubsub py_srv py_action_demo \ + cpp_robot_tf2 py_vision_demo py_params \ + cpp_custom_interface py_lifecycle_composable \ + cpp_qos_demo py_overlay_dds bringup + +echo +echo "[test.sh] === 测试结果汇总 ===" +colcon test-result --all +echo "[test.sh] (要看详细日志,跑: colcon test-result --all --verbose)" diff --git a/src/cpp_pubsub/src/chatter_publisher.cpp b/src/cpp_pubsub/src/chatter_publisher.cpp index 28e8cff..d8dbcec 100644 --- a/src/cpp_pubsub/src/chatter_publisher.cpp +++ b/src/cpp_pubsub/src/chatter_publisher.cpp @@ -18,22 +18,22 @@ ChatterPublisher::ChatterPublisher(const rclcpp::NodeOptions & options) : rclcpp::Node("chatter_publisher", options), publish_count_(0) { // 1) 声明参数(类型模板版 + 描述符) - this->declare_parameter( - "publish_rate_hz", 2, + this->declare_parameter( + "publish_rate_hz", 2.0, rcl_interfaces::msg::ParameterDescriptor().set__description("发布频率 (Hz)")); this->declare_parameter( "topic_name", "chatter", rcl_interfaces::msg::ParameterDescriptor().set__description("发布话题名")); // 2) 读参数 - const int publish_rate_hz = this->get_parameter("publish_rate_hz").as_int(); + const double publish_rate_hz = this->get_parameter("publish_rate_hz").as_double(); const std::string topic_name = this->get_parameter("topic_name").as_string(); // 3) 构造发布者 + 定时器 publisher_ = this->create_publisher(topic_name, 10); - const auto period = (publish_rate_hz > 0) ? - std::chrono::milliseconds(1000 / publish_rate_hz) : + const auto period = (publish_rate_hz > 0.0) ? + std::chrono::milliseconds(static_cast(1000.0 / publish_rate_hz)) : std::chrono::milliseconds(1000); timer_ = this->create_wall_timer( @@ -41,7 +41,7 @@ ChatterPublisher::ChatterPublisher(const rclcpp::NodeOptions & options) RCLCPP_INFO( this->get_logger(), - "ChatterPublisher started: rate=%d Hz, topic=\"%s\"", + "ChatterPublisher started: rate=%.2f Hz, topic=\"%s\"", publish_rate_hz, topic_name.c_str()); } diff --git a/src/cpp_pubsub/test/test_pub_sub.cpp b/src/cpp_pubsub/test/test_pub_sub.cpp index 5544f68..4ab2b0a 100644 --- a/src/cpp_pubsub/test/test_pub_sub.cpp +++ b/src/cpp_pubsub/test/test_pub_sub.cpp @@ -34,7 +34,7 @@ TEST_F(PubsubTest, PublisherConstructsWithDefaults) { auto node = std::make_shared(); EXPECT_EQ(node->get_name(), std::string("chatter_publisher")); - EXPECT_EQ(node->get_parameter("publish_rate_hz").as_int(), 2); + EXPECT_DOUBLE_EQ(node->get_parameter("publish_rate_hz").as_double(), 2.0); EXPECT_EQ(node->get_parameter("topic_name").as_string(), std::string("chatter")); }