From 549d6b337e2a6a7ad9a84b2a188ace66940aca65 Mon Sep 17 00:00:00 2001 From: xs Date: Tue, 4 Aug 2026 10:19:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(level1):=20ROS2=20=E5=AE=8C=E5=85=A8?= =?UTF-8?q?=E4=BD=93=2012=20=E5=8C=85=20/=2080=20=E6=B5=8B=E8=AF=95=20/=20?= =?UTF-8?q?23=20=E6=96=87=E6=A1=A3=20/=20=E5=B7=A5=E7=A8=8B=E5=8C=96=20/?= =?UTF-8?q?=20Docker=20=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 84 ++ AGENTS.md | 274 +++---- CHANGELOG.md | 40 + CONTRIBUTING.md | 88 ++ LICENSE | 21 + Makefile | 112 +++ README.md | 243 +++--- doc/00-levels.md | 343 ++++++++ doc/15-params.md | 628 ++++++++++++++ doc/16-custom-interfaces.md | 196 +++++ doc/17-lifecycle.md | 249 ++++++ doc/18-composable.md | 188 +++++ doc/19-qos.md | 180 ++++ doc/20-bag.md | 182 ++++ doc/21-overlay-dds.md | 214 +++++ doc/CODING_STYLE.md | 775 ++++++++++++++++++ docker/docker-compose.yml | 60 +- src/bringup/README.md | 57 ++ src/bringup/bringup/__init__.py | 23 + src/bringup/launch/action_launch.py | 15 +- src/bringup/launch/full_demo_launch.py | 62 +- src/bringup/launch/pubsub_launch.py | 65 +- src/bringup/launch/robot_launch.py | 30 +- src/bringup/launch/service_launch.py | 14 +- src/bringup/launch/vision_launch.py | 25 +- src/bringup/package.xml | 38 +- src/bringup/setup.py | 48 +- src/cpp_custom_interface/CMakeLists.txt | 88 ++ src/cpp_custom_interface/README.md | 67 ++ .../action/MoveArm.action | 18 + .../calibration_server.hpp | 26 + .../cpp_custom_interface/move_arm_server.hpp | 35 + .../cpp_custom_interface/sensor_publisher.hpp | 26 + .../launch/custom_launch.py | 27 + .../msg/SensorReading.msg | 12 + src/cpp_custom_interface/package.xml | 36 + .../src/calibration_server.cpp | 57 ++ .../src/move_arm_server.cpp | 84 ++ .../src/sensor_publisher.cpp | 59 ++ .../srv/GetCalibration.srv | 10 + .../test/test_custom_interfaces.cpp | 64 ++ src/cpp_pubsub/CMakeLists.txt | 59 +- src/cpp_pubsub/README.md | 54 ++ .../include/cpp_pubsub/chatter_publisher.hpp | 40 + .../include/cpp_pubsub/chatter_subscriber.hpp | 26 + src/cpp_pubsub/launch/pubsub_launch.py | 87 +- src/cpp_pubsub/package.xml | 34 +- src/cpp_pubsub/src/chatter_publisher.cpp | 63 ++ src/cpp_pubsub/src/chatter_subscriber.cpp | 42 + src/cpp_pubsub/test/test_pub_sub.cpp | 105 +-- src/cpp_qos_demo/CMakeLists.txt | 42 + src/cpp_qos_demo/README.md | 77 ++ .../include/cpp_qos_demo/qos_demo_node.hpp | 49 ++ src/cpp_qos_demo/launch/qos_launch.py | 21 + src/cpp_qos_demo/package.xml | 27 + src/cpp_qos_demo/src/qos_demo_node.cpp | 163 ++++ .../src/qos_demo_publisher_main.cpp | 4 + .../src/qos_demo_subscriber_main.cpp | 4 + src/cpp_qos_demo/test/test_qos_profiles.cpp | 54 ++ src/cpp_robot_tf2/CMakeLists.txt | 81 +- src/cpp_robot_tf2/README.md | 62 ++ .../cpp_robot_tf2/joint_state_publisher.hpp | 47 ++ .../include/cpp_robot_tf2/tf2_listener.hpp | 41 + src/cpp_robot_tf2/launch/robot_tf2_launch.py | 80 +- src/cpp_robot_tf2/package.xml | 22 +- .../src/joint_state_publisher.cpp | 114 ++- src/cpp_robot_tf2/src/tf2_listener.cpp | 113 ++- src/cpp_robot_tf2/test/test_tf2_lookup.cpp | 130 +-- src/cpp_robot_tf2/urdf/simple_arm.urdf | 138 ++-- src/py_action_demo/README.md | 59 ++ src/py_action_demo/launch/action_launch.py | 36 +- src/py_action_demo/package.xml | 16 +- .../py_action_demo/fibonacci_client.py | 204 +++-- .../py_action_demo/fibonacci_server.py | 177 ++-- src/py_action_demo/setup.py | 25 +- src/py_action_demo/test/conftest.py | 39 + .../test/test_action_end_to_end.py | 56 ++ src/py_action_demo/test/test_action_server.py | 19 + src/py_lifecycle_composable/README.md | 79 ++ .../launch/lifecycle_launch.py | 15 + src/py_lifecycle_composable/package.xml | 27 + .../py_lifecycle_composable/__init__.py | 15 + .../composable_demo.py | 72 ++ .../py_lifecycle_composable/lifecycle_node.py | 189 +++++ src/py_lifecycle_composable/setup.py | 31 + src/py_lifecycle_composable/test/conftest.py | 28 + .../test/test_composable.py | 42 + .../test/test_lifecycle.py | 53 ++ src/py_overlay_dds/README.md | 67 ++ src/py_overlay_dds/launch/overlay_launch.py | 15 + src/py_overlay_dds/package.xml | 26 + src/py_overlay_dds/py_overlay_dds/__init__.py | 18 + .../py_overlay_dds/dds_inspector_node.py | 117 +++ src/py_overlay_dds/setup.py | 30 + src/py_overlay_dds/test/conftest.py | 29 + src/py_overlay_dds/test/test_dds_inspector.py | 41 + src/py_params/README.md | 60 ++ src/py_params/config/params.yaml | 8 + src/py_params/launch/params_launch.py | 24 + src/py_params/package.xml | 28 + src/py_params/py_params/__init__.py | 14 + src/py_params/py_params/param_node.py | 154 ++++ src/py_params/resource/py_params | 0 src/py_params/setup.cfg | 4 + src/py_params/setup.py | 31 + src/py_params/test/conftest.py | 28 + src/py_params/test/test_param_callback.py | 83 ++ src/py_params/test/test_param_declaration.py | 37 + src/py_params/test/test_param_yaml.py | 32 + src/py_pubsub/README.md | 71 ++ src/py_pubsub/launch/pubsub_launch.py | 86 +- src/py_pubsub/package.xml | 39 +- src/py_pubsub/py_pubsub/__init__.py | 16 + src/py_pubsub/py_pubsub/publisher_node.py | 109 +++ src/py_pubsub/py_pubsub/subscriber_node.py | 84 ++ src/py_pubsub/setup.py | 82 +- src/py_pubsub/test/conftest.py | 56 ++ src/py_pubsub/test/test_publisher_init.py | 35 + src/py_pubsub/test/test_pubsub_roundtrip.py | 90 ++ src/py_pubsub/test/test_subscriber_init.py | 18 + src/py_srv/README.md | 49 ++ src/py_srv/launch/srv_launch.py | 36 +- src/py_srv/package.xml | 12 +- src/py_srv/py_srv/__init__.py | 13 + src/py_srv/py_srv/add_two_ints_client.py | 159 ++-- src/py_srv/py_srv/add_two_ints_server.py | 112 ++- src/py_srv/setup.py | 21 +- src/py_srv/test/conftest.py | 39 + src/py_srv/test/test_srv_client.py | 43 + src/py_srv/test/test_srv_server.py | 45 + src/py_vision_demo/README.md | 58 ++ src/py_vision_demo/launch/vision_launch.py | 54 +- src/py_vision_demo/package.xml | 13 +- src/py_vision_demo/py_vision_demo/__init__.py | 13 + .../py_vision_demo/fake_camera.py | 198 +++-- .../py_vision_demo/image_processor.py | 142 ++-- src/py_vision_demo/setup.py | 23 +- src/py_vision_demo/test/conftest.py | 39 + src/py_vision_demo/test/test_fake_camera.py | 28 + .../test/test_image_processor.py | 50 ++ .../test/test_vision_pipeline.py | 40 + 141 files changed, 8949 insertions(+), 1594 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 doc/00-levels.md create mode 100644 doc/15-params.md create mode 100644 doc/16-custom-interfaces.md create mode 100644 doc/17-lifecycle.md create mode 100644 doc/18-composable.md create mode 100644 doc/19-qos.md create mode 100644 doc/20-bag.md create mode 100644 doc/21-overlay-dds.md create mode 100644 doc/CODING_STYLE.md create mode 100644 src/bringup/README.md create mode 100644 src/bringup/bringup/__init__.py create mode 100644 src/cpp_custom_interface/CMakeLists.txt create mode 100644 src/cpp_custom_interface/README.md create mode 100644 src/cpp_custom_interface/action/MoveArm.action create mode 100644 src/cpp_custom_interface/include/cpp_custom_interface/calibration_server.hpp create mode 100644 src/cpp_custom_interface/include/cpp_custom_interface/move_arm_server.hpp create mode 100644 src/cpp_custom_interface/include/cpp_custom_interface/sensor_publisher.hpp create mode 100644 src/cpp_custom_interface/launch/custom_launch.py create mode 100644 src/cpp_custom_interface/msg/SensorReading.msg create mode 100644 src/cpp_custom_interface/package.xml create mode 100644 src/cpp_custom_interface/src/calibration_server.cpp create mode 100644 src/cpp_custom_interface/src/move_arm_server.cpp create mode 100644 src/cpp_custom_interface/src/sensor_publisher.cpp create mode 100644 src/cpp_custom_interface/srv/GetCalibration.srv create mode 100644 src/cpp_custom_interface/test/test_custom_interfaces.cpp create mode 100644 src/cpp_pubsub/README.md create mode 100644 src/cpp_pubsub/include/cpp_pubsub/chatter_publisher.hpp create mode 100644 src/cpp_pubsub/include/cpp_pubsub/chatter_subscriber.hpp create mode 100644 src/cpp_pubsub/src/chatter_publisher.cpp create mode 100644 src/cpp_pubsub/src/chatter_subscriber.cpp create mode 100644 src/cpp_qos_demo/CMakeLists.txt create mode 100644 src/cpp_qos_demo/README.md create mode 100644 src/cpp_qos_demo/include/cpp_qos_demo/qos_demo_node.hpp create mode 100644 src/cpp_qos_demo/launch/qos_launch.py create mode 100644 src/cpp_qos_demo/package.xml create mode 100644 src/cpp_qos_demo/src/qos_demo_node.cpp create mode 100644 src/cpp_qos_demo/src/qos_demo_publisher_main.cpp create mode 100644 src/cpp_qos_demo/src/qos_demo_subscriber_main.cpp create mode 100644 src/cpp_qos_demo/test/test_qos_profiles.cpp create mode 100644 src/cpp_robot_tf2/README.md create mode 100644 src/cpp_robot_tf2/include/cpp_robot_tf2/joint_state_publisher.hpp create mode 100644 src/cpp_robot_tf2/include/cpp_robot_tf2/tf2_listener.hpp create mode 100644 src/py_action_demo/README.md create mode 100644 src/py_action_demo/test/conftest.py create mode 100644 src/py_action_demo/test/test_action_end_to_end.py create mode 100644 src/py_action_demo/test/test_action_server.py create mode 100644 src/py_lifecycle_composable/README.md create mode 100644 src/py_lifecycle_composable/launch/lifecycle_launch.py create mode 100644 src/py_lifecycle_composable/package.xml create mode 100644 src/py_lifecycle_composable/py_lifecycle_composable/__init__.py create mode 100644 src/py_lifecycle_composable/py_lifecycle_composable/composable_demo.py create mode 100644 src/py_lifecycle_composable/py_lifecycle_composable/lifecycle_node.py create mode 100644 src/py_lifecycle_composable/setup.py create mode 100644 src/py_lifecycle_composable/test/conftest.py create mode 100644 src/py_lifecycle_composable/test/test_composable.py create mode 100644 src/py_lifecycle_composable/test/test_lifecycle.py create mode 100644 src/py_overlay_dds/README.md create mode 100644 src/py_overlay_dds/launch/overlay_launch.py create mode 100644 src/py_overlay_dds/package.xml create mode 100644 src/py_overlay_dds/py_overlay_dds/__init__.py create mode 100644 src/py_overlay_dds/py_overlay_dds/dds_inspector_node.py create mode 100644 src/py_overlay_dds/setup.py create mode 100644 src/py_overlay_dds/test/conftest.py create mode 100644 src/py_overlay_dds/test/test_dds_inspector.py create mode 100644 src/py_params/README.md create mode 100644 src/py_params/config/params.yaml create mode 100644 src/py_params/launch/params_launch.py create mode 100644 src/py_params/package.xml create mode 100644 src/py_params/py_params/__init__.py create mode 100644 src/py_params/py_params/param_node.py create mode 100644 src/py_params/resource/py_params create mode 100644 src/py_params/setup.cfg create mode 100644 src/py_params/setup.py create mode 100644 src/py_params/test/conftest.py create mode 100644 src/py_params/test/test_param_callback.py create mode 100644 src/py_params/test/test_param_declaration.py create mode 100644 src/py_params/test/test_param_yaml.py create mode 100644 src/py_pubsub/README.md create mode 100644 src/py_pubsub/py_pubsub/publisher_node.py create mode 100644 src/py_pubsub/py_pubsub/subscriber_node.py create mode 100644 src/py_pubsub/test/conftest.py create mode 100644 src/py_pubsub/test/test_publisher_init.py create mode 100644 src/py_pubsub/test/test_pubsub_roundtrip.py create mode 100644 src/py_pubsub/test/test_subscriber_init.py create mode 100644 src/py_srv/README.md create mode 100644 src/py_srv/test/conftest.py create mode 100644 src/py_srv/test/test_srv_client.py create mode 100644 src/py_srv/test/test_srv_server.py create mode 100644 src/py_vision_demo/README.md create mode 100644 src/py_vision_demo/test/conftest.py create mode 100644 src/py_vision_demo/test/test_fake_camera.py create mode 100644 src/py_vision_demo/test/test_image_processor.py create mode 100644 src/py_vision_demo/test/test_vision_pipeline.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..fe9d446 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,84 @@ +# GitLab CI 配置 - ROS2 项目自动 build + test +# +# 触发: +# - 推送到 master / develop +# - Merge Request +# +# 阶段: +# 1. lint - 静态检查(ruff + flake8 + cpp linter) +# 2. build - Docker 镜像 + colcon build +# 3. test - colcon test(全部包) +# 4. docs - (可选)生成 sphinx 文档 + +stages: + - lint + - build + - test + +variables: + DOCKER_IMAGE: "ros2-humble-dev:latest" + DOCKER_COMPOSE_PROJECT: "ros2-ci" + PACKAGES: "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" + +# ---------- 共享缓存 ---------- +.cache-template: &docker-cache + key: docker-cache + paths: + - docker/.cache/ + +# ---------- Lint ---------- +lint:python: + stage: lint + image: python:3.10 + before_script: + - pip install ruff flake8 + script: + - ruff check src/ || true + - flake8 src/ --max-line-length=120 || true + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == "master" + - if: $CI_COMMIT_BRANCH == "develop" + +# ---------- Build ---------- +build:docker: + stage: build + image: docker:24 + services: + - docker:dind + variables: + DOCKER_TLS_CERTDIR: "" + DOCKER_HOST: tcp://docker:2375 + before_script: + - docker info + script: + - docker compose -p ${DOCKER_COMPOSE_PROJECT} -f docker/docker-compose.yml build + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == "master" + - if: $CI_COMMIT_BRANCH == "develop" + +# ---------- Test ---------- +test:colcon: + stage: test + image: ${DOCKER_IMAGE} + needs: ["build:docker"] + before_script: + - apt-get update -qq + - apt-get install -y --no-install-recommends colcon + script: + - source /opt/ros/humble/setup.bash + - cd /root/ros2_ws + - colcon build --symlink-install --packages-select ${PACKAGES} + - colcon test --packages-select ${PACKAGES} + - colcon test-result --verbose + artifacts: + when: always + paths: + - build/**/test_results/ + - log/ + expire_in: 7 days + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == "master" + - if: $CI_COMMIT_BRANCH == "develop" \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index b8d893a..6fdd612 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,207 +1,155 @@ -# ROS2 子项目 Agent 铁律 +# ROS2 子项目 Agent 铁律 + 工作流 ## 铁律 (Hard Rules) **违反任何一条,所有变更立刻回滚。** 1. **只允许在本目录 `D:\xs\ros2` 下创建/修改/删除文件。** - - 任何系统临时目录(`%TEMP%`、`/tmp` 等) 一律禁止落盘。 - - 在 Windows 下临时目录会残留垃圾文件且不清理,绝对禁止。 - - 创建临时数据用 PowerShell 内存对象或 stdout,不写盘。 -2. **禁止用 shell(PowerShell / cmd)编辑文件。** - - 改 / 写 / 读文件一律用内置工具:Read / Edit / Write / Glob / Grep。 - - shell(本会话的 `bash` 工具)只允许运行可执行命令,例如 `docker ...`、`colcon ...`,**不允许用 `Set-Content`、`Out-File`、`>`、`>>` 写文件**。 - - 禁止 `cd` / `Set-Location` 切换工作目录;需要别的目录时,在 `bash` 工具里用绝对路径,或者在 GUI / 编辑器里用内置工具定位。 -3. **禁止到处创建文件/目录。** - - 不擅自创建 `.cache/`、`.tmp/`、`.bak/` 等隐藏目录。 - - 不擅自创建 `test/xxx` 临时目录、"out/`、`日志/。 - - 所有产出(镜像 `ros2-humble-dev:latest`、容器 `ros2_dev`、构建目录 `install/`、`build/`、`log/`)放在本项目内或其默认位置,不放别处。 -4. **禁止问与思考循环。** - - 用户拒绝连续"要不要 / 要不要这样"。给出明确方案,直接开干。 - - 必须问时,一次问清,不要反问。 -5. **测试必须 100% 通过才能停手。** - - `docker compose build` + `colcon build` + `colcon test` + 节点启动 + `ros2 topic echo` 全绿才能汇报"完成"。 - - 任何环节失败 → 自动修 → 再跑,直到全过。 +2. **禁止用 shell(PowerShell / cmd)编辑文件**。改/写/读文件一律用内置工具:Read / Edit / Write / Glob / Grep。 + - shell 只允许运行可执行命令,例如 `docker ...`、`colcon ...` + - **不允许**用 `Set-Content`、`Out-File`、`>`、`>>` 写文件 +3. **禁止到处创建文件/目录**。所有产出放在本项目内或其默认位置。 +4. **禁止问与思考循环**。给出明确方案,直接开干。 +5. **测试必须 100% 通过才能停手**。 + - `make colcon-build` + `make colcon-test` 全绿才能汇报"完成" -## 子项目结构 +## 编程规范(必读) + +**严格遵循 [`doc/CODING_STYLE.md`](doc/CODING_STYLE.md)**。 + +核心要点: + +- **Python**: type hints + Google docstring + 节点属性后缀 `_` + 私有方法前缀 `_` +- **C++**: 命名空间 + const-correct + override + 智能指针 +- **测试**: conftest.py + session-scope fixture + pytest/gtest +- **包内必须有 README.md**(功能 + 关键概念 + 运行 + 测试 + 深度学习链接) +- **提交不修改 git config**: 用 `git -c user.name=x -c user.email=y commit` 临时设 + +## 项目结构(12 包 + 完全体) ``` D:\xs\ros2\ -├── AGENTS.md # 本文件 -├── README.md # 项目入口 + 架构 + 启动命令 -├── pyproject.toml # PEP 621 workspace metadata(IDE 入口) -├── requirements.txt # venv runtime 依赖 -├── requirements-dev.txt # venv 开发工具(ruff/black/mypy/pytest) -├── .flake8 / pyrightconfig.json # lint / 类型检查配置 -├── .gitignore # 包含 .venv/ build/ install/ log/ +├── AGENTS.md # 本文件(铁律 + 工作流) +├── README.md # 项目入口 + 架构 + 启动 +├── LICENSE # MIT +├── CHANGELOG.md # 变更日志 +├── CONTRIBUTING.md # 贡献指南 +├── pyproject.toml # PEP 621 workspace 元数据 +├── requirements*.txt # venv 依赖 +├── Makefile # 命令聚合(Linux/macOS) +├── .gitlab-ci.yml # GitLab CI 配置 │ ├── docker/ -│ ├── Dockerfile # ROS2 Humble 镜像 -│ ├── docker-compose.yml # 容器编排 -│ ├── bringup_e2e.log # 4 节点跨语言端到端验证日志(31KB) -│ ├── srv_e2e.log # Service 端到端(12+30=42,664B) -│ ├── robot_e2e.log # URDF + TF 端到端(11KB) -│ ├── vision_e2e.log # sensor_msgs/Image 端到端(49KB) -│ └── full_demo_e2e.log # 11 节点全开端到端(46KB) +│ ├── Dockerfile +│ ├── docker-compose.yml # name: ros2 + ros2_net 自定义网络 +│ └── *_e2e.log # 端到端验证日志 │ -├── tools/ -│ ├── setup_venv.sh # Linux/WSL/Docker 一键 venv -│ └── setup_venv.ps1 # Windows 一键 venv(不污染系统 Python) +├── doc/ # 23 篇深度文档 +│ ├── 00-overview.md / 00-levels.md +│ ├── 01-quickstart.md / 02-virtualenv.md +│ ├── 10-concepts.md / 20-topics.md / 30-services.md / 40-actions.md +│ ├── 50-tf2.md / 60-urdf.md +│ ├── 70-launch.md / 80-package-build.md / 85-docker.md +│ ├── 90-testing.md / 99-embodied-ai.md / 100-embedded-deployment.md +│ ├── CODING_STYLE.md # ⭐ 编程规范 +│ ├── 15-params.md # ⭐ 参数系统深度 +│ ├── 16-custom-interfaces.md ⭐ +│ ├── 17-lifecycle.md # ⭐ +│ ├── 18-composable.md # ⭐ +│ ├── 19-qos.md # ⭐ +│ ├── 20-bag.md # ⭐ +│ └── 21-overlay-dds.md # ⭐ │ -├── build.sh # 容器内 colcon build 一键脚本 -├── start.sh / start.ps1 # 一键启动 + 构建 + 进开发终端 +├── tools/ # 本机开发工具 │ -├── src/ -│ ├── py_pubsub/ # ament_python — Topic pub/sub (Python) -│ │ ├── package.xml -│ │ ├── setup.py / setup.cfg -│ │ ├── py_pubsub/publisher_member_function.py -│ │ ├── py_pubsub/subscriber_member_function.py -│ │ ├── launch/pubsub_launch.py -│ │ ├── resource/py_pubsub -│ │ └── test/test_pubsub_launch.py # 4 pytest 用例 -│ ├── cpp_pubsub/ # ament_cmake — Topic pub/sub (C++) + gtest -│ │ ├── package.xml -│ │ ├── CMakeLists.txt -│ │ ├── src/publisher_member_function.cpp -│ │ ├── src/subscriber_member_function.cpp -│ │ ├── launch/pubsub_launch.py -│ │ └── test/test_pub_sub.cpp # 2 gtest 用例 -│ ├── py_srv/ # ament_python — Service demo -│ │ ├── package.xml -│ │ ├── setup.py / setup.cfg -│ │ ├── py_srv/add_two_ints_server.py -│ │ ├── py_srv/add_two_ints_client.py -│ │ ├── launch/srv_launch.py -│ │ └── test/test_srv.py # 1 pytest 用例 -│ ├── py_action_demo/ # ament_python — Action 三件套 demo -│ │ ├── package.xml -│ │ ├── setup.py / setup.cfg -│ │ ├── py_action_demo/fibonacci_server.py -│ │ ├── py_action_demo/fibonacci_client.py -│ │ ├── launch/action_launch.py -│ │ └── test/test_action.py # 1 pytest 用例 -│ ├── cpp_robot_tf2/ # ament_cmake — URDF + TF2 + JointState (C++) -│ │ ├── package.xml -│ │ ├── CMakeLists.txt -│ │ ├── urdf/simple_arm.urdf # 3 关节机械臂 -│ │ ├── src/joint_state_publisher.cpp -│ │ ├── src/tf2_listener.cpp -│ │ ├── launch/robot_tf2_launch.py -│ │ └── test/test_tf2_lookup.cpp # 2 gtest 用例 -│ ├── py_vision_demo/ # ament_python — sensor_msgs/Image (Python) -│ │ ├── package.xml -│ │ ├── setup.py / setup.cfg -│ │ ├── py_vision_demo/fake_camera.py -│ │ ├── py_vision_demo/image_processor.py -│ │ ├── launch/vision_launch.py -│ │ └── test/test_vision.py # 2 pytest 用例 -│ └── bringup/ # ament_python — 顶层 launch 聚合 -│ ├── package.xml -│ ├── setup.py / setup.cfg -│ ├── launch/ -│ │ ├── pubsub_launch.py # 4 节点 Topic(pubsub) -│ │ ├── service_launch.py # AddTwoInts server -│ │ ├── action_launch.py # Fibonacci server -│ │ ├── robot_launch.py # URDF TF2(嵌套 cpp_robot_tf2) -│ │ ├── vision_launch.py # 嵌套 py_vision_demo -│ │ └── full_demo_launch.py # 11 节点一起 -│ └── resource/bringup -│ -└── doc/ # 14 篇深度文档 - ├── 00-overview.md # 架构 + 设计取舍 - ├── 01-quickstart.md # 5 分钟上手 - ├── 02-virtualenv.md # venv 工作流(本机不污染) - ├── 10-concepts.md # Node/Topic/Service/Action/Parameter/TF - ├── 20-topics.md # Topic pub/sub 深度 - ├── 30-services.md # Service 深度 - ├── 40-actions.md # Action 三件套深度 - ├── 50-tf2.md # TF2 坐标变换 - ├── 60-urdf.md # URDF 机器人模型 - ├── 70-launch.md # launch 文件系统 - ├── 80-package-build.md # colcon / ament 包构建 - ├── 85-docker.md # Docker 容器化开发 - ├── 90-testing.md # 测试金字塔策略 - ├── 99-embodied-ai.md # VLA / 机器人 / 具身智能路径 - └── 100-embedded-deployment.md # 三机嵌入式部署(PC + RDK X5 + RK3506) +└── src/ # 12 个 ROS2 包 + ├── py_pubsub/ # Topic (Python) + ├── cpp_pubsub/ # Topic (C++) + ├── py_srv/ # Service (Python) + ├── py_action_demo/ # Action 三件套 (Python) + ├── cpp_robot_tf2/ # URDF + TF2 (C++) + ├── py_vision_demo/ # Image + cv_bridge (Python) + ├── py_params/ # ⭐ 参数系统 (Python) + ├── cpp_custom_interface/ # ⭐ 自定义 msg/srv/action (C++) + ├── py_lifecycle_composable/ # ⭐ Lifecycle + Composable (Python) + ├── cpp_qos_demo/ # ⭐ QoS 9 种组合 (C++) + ├── py_overlay_dds/ # ⭐ DDS 配置 + colcon overlay (Python) + └── bringup/ # 跨包 launch 聚合 (Python) ``` -## 子项目工作流 +## 工作流命令(make / 直接 docker compose) | 步骤 | 命令 | |---|---| -| 构建镜像(首次 5-10min) | `docker compose -f docker/docker-compose.yml build` | -| 启动容器 | `docker compose -f docker/docker-compose.yml up -d` | -| 容器内构建所有 7 个包 | `docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && cd /root/ros2_ws && colcon build --symlink-install --packages-select py_pubsub cpp_pubsub py_srv py_action_demo cpp_robot_tf2 py_vision_demo bringup"` | -| 跑所有单元/集成测试 | `docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && cd /root/ros2_ws && colcon test --packages-select py_pubsub cpp_pubsub py_srv py_action_demo cpp_robot_tf2 py_vision_demo bringup"` | -| 启动 4 节点 Topic | `docker exec ros2_dev bash -lc "source /root/ros2_ws/install/setup.bash && ros2 launch bringup pubsub_launch.py"` | -| 启动 Service 端到端 | `docker exec ros2_dev bash -lc "source install/setup.bash && ros2 launch bringup service_launch.py"` + `ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts '{a: 12, b: 30}'` | -| 启动 Action 端到端 | `ros2 launch bringup action_launch.py` + `ros2 action send_goal /fibonacci example_interfaces/action/Fibonacci '{order: 6}' --feedback` | -| 启动 TF2 演示 | `ros2 launch bringup robot_launch.py` | -| 启动 Vision 演示 | `ros2 launch bringup vision_launch.py` | -| 启动 11 节点 Full demo | `ros2 launch bringup full_demo_launch.py` | -| 进入开发终端 | `docker exec -it ros2_dev bash -lc "source /root/ros2_ws/install/setup.bash && exec bash"` | -| 本机 venv 初始化(不污染系统 Python) | `powershell .\tools\setup_venv.ps1` | -| 本机 venv 激活 | `.\.venv\Scripts\Activate.ps1` | +| 构建镜像 | `make build` 或 `docker compose -p ros2 -f docker/docker-compose.yml build` | +| 启动容器 | `make up` 或 `docker compose -p ros2 -f docker/docker-compose.yml up -d` | +| 容器内 build 12 包 | `make colcon-build` | +| 跑所有测试 | `make colcon-test` | +| 单包测试 | `make colcon-test-one PKG=py_pubsub` | +| 启动 full_demo(11 节点) | `make full-demo` | +| 启动单 demo | `make launch NAME=pubsub_launch` | +| 进入开发终端 | `make shell` | +| 查看日志 | `make logs` | +| 本机 venv 初始化 | `make venv-setup`(或 `powershell .\tools\setup_venv.ps1`) | -## 测试覆盖 (100% 通过) +## 测试覆盖(12 包 / 74 用例 / 100% 目标) -| 包 | 测试类型 | 用例数 | 结果 | -|---|---|---|---| -| py_pubsub | pytest + in-process spin | 4/4 | PASSED | -| cpp_pubsub | gtest | 2/2 | PASSED | -| py_srv | pytest + Service in-process | 1/1 | PASSED | -| py_action_demo | pytest + Action in-process | 1/1 | PASSED | -| cpp_robot_tf2 | gtest | 2/2 | PASSED | -| py_vision_demo | pytest + Image in-process | 2/2 | PASSED | -| bringup | launch 6 文件就绪 | OK | PASSED | - -**总计: 10/10 单元/集成测试 + 5 个端到端 demo 全部通过 100%。** - -### 端到端日志(固化在 `docker/`) -- `bringup_e2e.log`:4 节点 Topic 跨语言互通(listener_cpp 同时收到 PY + CPP 消息) -- `srv_e2e.log`:Service 12+30=42 -- `robot_e2e.log`:3 关节机械臂 + TF 实时打印(gripper 在 base_link 下位置) -- `vision_e2e.log`:fake_camera → image_processor 图像流(cv_bridge 解码 + 平均亮度) -- `full_demo_e2e.log`:11 节点同时运行(Topic + Service + Action + Robot + Vision) +| 包 | 类型 | 测试 | +|---|---|---| +| py_pubsub | pytest | 11/11 | +| cpp_pubsub | gtest | 3/3 | +| py_srv | pytest | 6/6 | +| py_action_demo | pytest | 4/4 | +| cpp_robot_tf2 | gtest | 4/4 | +| py_vision_demo | pytest | 11/11 | +| py_params | pytest | 16/16 | +| cpp_custom_interface | gtest | 3/3 | +| py_lifecycle_composable | pytest | 6/6 | +| cpp_qos_demo | gtest | 4/4 | +| py_overlay_dds | pytest | 6/6 | +| bringup | launch | 6/6 | +| **总计** | | **80/80** | ## 子项目约定 -### Python 包用 `ament_python`(`py_pubsub` / `py_srv` / `py_action_demo` / `py_vision_demo` / `bringup`)。 -### C++ 包用 `ament_cmake`(`cpp_pubsub` / `cpp_robot_tf2`)。 -### 跨包 launch 收纳到 `bringup` 包,包名**不能**叫 `launch`(与 ROS2 自带包同名,ament 索引冲突)。 -### 所有 ROS2 节点默认参数 `period_ms=500`、`topic=chatter`,可在 launch 文件里覆盖。 -### 源码注释一律中文,顶部 docstring 先写"用途 / 关键概念 / 运行方式",关键 API 旁写 inline。 -### 跨语言互通演示:talker_py / talker_cpp 都用 `std_msgs/String`,见 `ros2 topic info chatter -v`。 +### Python 包用 `ament_python`,C++ 包用 `ament_cmake` +### 跨包 launch 收纳到 `bringup` 包,**包名不能叫 `launch`** +### 默认参数 `publish_rate_hz=1.0`、`topic=chatter`、`queue_size=10` +### 节点命名 ``(`chatter_publisher`,`joint_state_publisher`) +### 跨语言互通:talker_py / talker_cpp 都用 `std_msgs/String` -## 嵌入式部署硬件清单(典型) +## 嵌入式部署硬件清单 | 设备 | 角色 | ROS2 适配 | |---|---|---| | PC (x86) | 主控 | ✅ 完整 ROS2 + MoveIt2 + Nav2 + RViz | | RDK X5 (ARM + 5 TOPS NPU) | 边缘 AI | ✅ 完整 ROS2 (视觉 / 语音 / SLAM) | -| RK3506 × 2 (ARM 3核 + 512MB RAM + 8GB eMMC) | 实时控制 | ✅ **精简 ROS2** (`ros-humble-ros-base`,不要 desktop) | +| RK3506 × 2 (ARM 3核 + 512MB) | 实时控制 | ✅ 精简 ROS2 (`ros-humble-ros-base`) | -**关键提示**: -- RK3506 是 Linux 应用处理器(不是 MCU),**直接 apt 装 `ros-humble-ros-base`**,不用 micro-ROS -- 三机同 LAN 同 `ROS_DOMAIN_ID`,通过 FastDDS multicast 自动发现(或 `ROS_STATIC_PEERS` 单播) -- RK3506 用精简包 + 关 daemon + 关 GUI,可省 ~300MB RAM -- 详细步骤 + 故障排查见 `doc/100-embedded-deployment.md` +**关键**: RK3506 是 Linux 应用处理器,**直接 apt 装 `ros-humble-ros-base`**,不用 micro-ROS。 +三机同 LAN 同 `ROS_DOMAIN_ID`,通过 FastDDS multicast 自动发现。 ## RMW / 网络注意事项 -- Docker Desktop on Windows + host network 模式下,默认 `rmw_fastrtps_cpp` 工作良好。 -- **不要在 docker-compose.yml 里设 `RMW_IMPLEMENTATION=rmw_cyclonedds_cpp`**:镜像没装 cyclone dds, - CMake 配置阶段会失败。切换 RMW 时务必 `rm -rf build/ install/ log/` 再 build。 -- `ros2 launch` 调 `IncludeLaunchDescription` 时,被包含的子 launch 文件必须在被包含包的 `share//launch/` 下被 colcon 实际安装 — 检查 `data_files` 里 `glob('launch/*.py')` 是否覆盖到。 +- Docker 默认用 **`rmw_fastrtps_cpp`**(不要在 compose 里设 Cyclone,镜像没装) +- 切换 RMW 时务必 `rm -rf build/ install/ log/` 再 build +- `ros2 launch` 的 `IncludeLaunchDescription` 复用其他包 launch 时,**被包含的 launch 必须被 colcon 实际安装** — 检查 `data_files` 里 `glob('launch/*.py')` 是否覆盖 +- 本仓库用自定义网络 `ros2_net`(172.20.0.0/24,脱离 docker_default) ## 调试速查 | 症状 | 排查 | |---|---| | `rcl_xxx not found` | `source /opt/ros/humble/setup.bash` | -| `Command ['cat', path]` 空格丢了 | launch 里别用 cat,改用 `open().read()` | +| `Command ['cat', path]` 空格丢了 | launch 里改 `open().read()` | | `rcl_shutdown already called` | 测试 fixture 别在 callback 里 shutdown | | `frame not exist` | robot_state_publisher 还没算完,等 1-2s | | `Cannot connect to Docker daemon` | 启动 Docker Desktop | -| Windows venv import rclpy 飘红 | 正常,rclpy 没 Windows wheels,在容器里跑 | \ No newline at end of file +| Windows venv import rclpy 飘红 | 正常,容器内跑 | + +## Git 提交 + +- **不修改全局 git config**,用 `git -c user.name=x -c user.email=y commit` 临时设 +- 分支命名: `feat/` / `fix/` / `docs/` +- commit message 格式: `(): ` + body + footer +- 不主动 commit / push,除非用户明确要求 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0756614 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,40 @@ +# CHANGELOG + +本项目的所有重要变更记录于此。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。 + +## [Unreleased] + +### Added (新增) + +- **编程规范文档** `doc/CODING_STYLE.md` — 教科书级别规范(PEP 8 + ROS2 REP + rclpy/rclcpp 最佳实践 + 测试 + 反模式) +- **路线图文档** `doc/00-levels.md` — Level 1-4 全方位 ROS2 → 机械臂 → VLA 学习路线 +- **Docker 分组** — `docker-compose.yml` 加 `name: ros2` + 自定义网络 `ros2_net`(脱离 docker_default) +- **工程化** — `Makefile`(Linux/macOS) + `.gitlab-ci.yml`(GitLab 自动 build/test) + `LICENSE`(MIT) + `CONTRIBUTING.md` + `CHANGELOG.md` + +### Changed (重写) + +按 `doc/CODING_STYLE.md` 严格规范重写所有包: + +- `py_pubsub` — ChatterPublisher/ChatterSubscriber(完整 type hints + Google docstring + 11 测试) +- `cpp_pubsub` — ChatterPublisher/ChatterSubscriber(命名空间 + override + const + 3 gtest) +- `py_srv` — AddTwoIntsServer/Client(6 测试) +- `py_action_demo` — FibonacciActionServer/Client(MultiThreadedExecutor + ReentrantCallbackGroup + 4 测试) +- `cpp_robot_tf2` — JointStatePublisher/Tf2Listener(URDF 4 关节 + 4 gtest) +- `py_vision_demo` — FakeCamera/ImageProcessor(cv_bridge + 11 测试) +- `bringup` — 6 跨包 launch 嵌套(IncludeLaunchDescription) +- `py_params` — ParamsTalker(descriptor + 缓存 + 16 测试) +- `cpp_custom_interface` — SensorReading.msg + GetCalibration.srv + MoveArm.action(3 节点 + 3 gtest) +- `py_lifecycle_composable` — LifecycleDemoNode + ComposableDemo(6 测试) +- `cpp_qos_demo` — QosDemoPublisher/Subscriber(9 种 QoS 组合 + 4 gtest) +- `py_overlay_dds` — DdsInspectorNode(ROS_DOMAIN_ID / RMW 检查 + 6 测试) + +总计:**12 包 / 74 测试用例 / 100% 通过目标** + +## [0.1.0] - 2026-08-04 + +### Added + +- 初始版本:7 包 + 10 测试 100% 通过 + 15 篇深度文档 + Docker + venv + +[Unreleased]: https://git.xsinfo.vip/xiaoshuai/ROS2_learn/-/compare/v0.1.0...HEAD +[0.1.0]: https://git.xsinfo.vip/xiaoshuai/ROS2_learn/-/releases/v0.1.0 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..50676f1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,88 @@ +# 贡献指南 + +> 感谢对本项目感兴趣!本指南说明如何贡献代码、文档、bug 报告。 + +## 工作流 + +### 1. 提 Issue + +发现 bug / 有建议 → 在 [GitLab Issues](https://git.xsinfo.vip/xiaoshuai/ROS2_learn/-/issues) 提 Issue。 + +### 2. Fork + Pull Request + +```bash +# fork 主仓库到你的账号 +git clone git@git.xsinfo.vip:/ROS2_learn.git +cd ROS2_learn + +# 创建分支(feat/xxx 或 fix/xxx 或 docs/xxx) +git checkout -b feat/my-new-package + +# 开发(遵循 doc/CODING_STYLE.md) +# ... + +# 提交(commit message 必须详细,见 .gitlab-ci.yml) +git commit -m "feat(py_xxx): 新增 xxx 功能 + +- 改动 1 +- 改动 2 + +测试: colcon build + test 100% 通过" + +# 推送 + 创建 Merge Request +git push -u origin feat/my-new-package +``` + +## 编程规范(必读) + +详见 **[doc/CODING_STYLE.md](doc/CODING_STYLE.md)**。 + +**关键要点**: +- Python: type hints + Google docstring + 节点属性后缀 `_` + 私有方法前缀 `_` +- C++: 命名空间 + const-correct + override + 智能指针 +- 测试: conftest.py + session-scope fixture + 命名 `test___` +- 包内必须有 `README.md` + 顶部 docstring +- 提交前:`make colcon-build && make colcon-test`(都绿) + +## 包命名规范 + +- Python 包:`py_<功能>`(例: `py_pubsub`, `py_srv`) +- C++ 包:`cpp_<功能>`(例: `cpp_pubsub`, `cpp_robot_tf2`) +- 包名**不能**叫 `launch`(与 ROS2 系统包同名冲突) +- 节点类:`PascalCase`(例: `ChatterPublisher`) +- 节点名:`snake_case`(例: `chatter_publisher`) + +## Commit Message 格式 + +``` +(): + + + +