This commit is contained in:
2026-08-05 18:17:25 +08:00
parent 61796dfa9f
commit b52cee9e39
34 changed files with 866 additions and 309 deletions
+3 -2
View File
@@ -106,10 +106,11 @@ py_lifecycle_composable 6 pytest ⭐(新增)
cpp_qos_demo 4 gtest ⭐(新增)
py_overlay_dds 6 pytest ⭐(新增)
总计: **78 用例**(pytest 64 + gtest 14),目标 100% 通过
总计: **82 用例**(pytest 64 + gtest 14 + launch_test 4),目标 100% 通过
> 注:78 用例是当前仓库实测数(`colcon test` 结果),与 README/AGENTS 一致。
> 注:82 用例是当前仓库实测数(`colcon test` 结果),与 README/AGENTS 一致。
> 上述表格列是"实测用例数"(非"测试文件数")。
> 拆分:pytest=11+7+5+13+16+6+6=64,gtest=3+4+3+4=14,launch_test=1+1+1+1=4。
```
---
+25 -17
View File
@@ -62,7 +62,7 @@
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 11 个 ROS2 节点(运行时) │ │
│ │ talker/listener × 2 (py+cpp) │ │
│ │ chatter_publisher/subscriber × 2 (py+cpp) │ │
│ │ service server / action server │ │
│ │ joint_state_publisher + robot_state_publisher │ │
│ │ + tf2_listener + fake_camera + image_processor │ │
@@ -117,14 +117,13 @@ ROS2 所有节点通过 **DDS**(默认 fastdds)做发布订阅,**不直接 impor
└────────┬────────┬──────────┬──────────┬────────┬────────┘
│ │ │ │ │
┌─────▼───┐ ┌──▼──────┐ ┌▼────────┐ ┌▼─────┐ ┌▼─────────┐
talker_py│ │fake_cam │ │joint_pub│ │srv │ │fibonacci │
listener │ │image_ │ │robot_ │ │server│ │_server
_py │ │processor│ │state_pub│ │ │ │
├─────────┤ ├─────────┤ ├─────────┤ └──────┘ └──────────┘
talker_
cpp │ ← topic 互通(跨语言)
listener
│ _cpp │
chatter_ │ │fake_cam │ │joint_ │ │add_ │ │fibonacci │
publish_ │ │image_ │ │state_ │ │two_ │ │_action_
er_py │ │processor│ │publisher│ │ints_ │ │server
├─────────┤ ├─────────┤ ├─────────┤ │server│ └──────────┘
chatter_ │
publish_ │ ← topic 互通(跨语言)
er_cpp
└─────────┘
```
@@ -151,14 +150,23 @@ D:\xs\ros2\
├── build.sh / start.sh / start.ps1
├── src/
│ ├── py_pubsub/ ← Topic (Python)
│ ├── cpp_pubsub/ ← Topic (C++)
│ ├── py_srv/ ← Service (Python)
│ ├── py_action_demo/ ← Action (Python)
│ ├── cpp_robot_tf2/ ← TF2 + URDF (C++)
│ ├── py_vision_demo/ ← Image (Python)
── bringup/ launch 聚合 (Python)
├── src/ ← 12 个 ROS2 包 + 4 个 L2 占位
│ ├── py_pubsub/ ← Topic (Python)
│ ├── cpp_pubsub/ ← Topic (C++)
│ ├── py_srv/ ← Service (Python)
│ ├── py_action_demo/ ← Action (Python)
│ ├── cpp_robot_tf2/ ← TF2 + URDF (C++)
│ ├── py_vision_demo/ ← Image (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)
│ ├── gazebo_sim/ ← ⚪ L2 占位(待 Gazebo)
│ ├── moveit2_demo/ ← ⚪ L2 占位(待 MoveIt2)
│ ├── nav2_demo/ ← ⚪ L2 占位(待 Nav2)
│ └── ros2_control_demo/ ← ⚪ L2 占位(待 ros2_control)
└── doc/ ← 15 篇深度文档
├── 00-overview.md ← 本篇
+40 -19
View File
@@ -248,22 +248,23 @@ bash scripts/launch.sh pubsub_launch 30
# 第 2 个参数是运行时长(秒);空着 = 一直跑
```
**预期输出**:
**预期输出**(节点名是 `bringup/launch/pubsub_launch.py``name=` 字段决定的):
```
[INFO] [launch]: All log files can be found below /root/.ros/log/2026-08-03-...
[INFO] [launch]: All log files can be found below /root/.ros/log/...
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [talker-1]: process started with pid [56]
[INFO] [listener-2]: process started with pid [58]
[INFO] [talker-3]: process started with pid [60]
[INFO] [listener-4]: process started with pid [62]
[talker-1] [INFO] [...] talker_py started -> topic=chatter, period=500ms
[listener-2] [INFO] [...] listener_py subscribed <- chatter
[talker-3] [INFO] [...] talker_cpp started -> topic=chatter, period=500ms
[listener-4] [INFO] [...] listener_cpp subscribed <- chatter
[listener-2] [INFO] [...] recv: "Hello from PY, seq=0"
[listener-4] [INFO] [...] recv: "Hello from C++, seq=0"
[listener-4] [INFO] [...] recv: "Hello from PY, seq=0" ← **跨语言互通!**
[listener-2] [INFO] [...] recv: "Hello from C++, seq=0" ← **跨语言互通!**
[INFO] [chatter_publisher-1]: process started with pid [2656]
[INFO] [chatter_publisher_cpp-2]: process started with pid [2658]
[INFO] [chatter_subscriber-3]: process started with pid [2660]
[INFO] [chatter_subscriber_cpp-4]: process started with pid [2662]
[chatter_publisher_cpp-2] [INFO] [...] ChatterPublisher started: rate=2.00 Hz, topic="chatter"
[chatter_publisher-1] [INFO] [...] ChatterPublisher started: rate=2.00 Hz, topic="chatter"
[chatter_subscriber-3] [INFO] [...] ChatterSubscriber subscribed: topic="chatter"
[chatter_subscriber_cpp-4] [INFO] [...] ChatterSubscriber subscribed: topic="chatter"
[chatter_subscriber-3] [INFO] [...] recv #0: "Hello from PY, seq=0"
[chatter_subscriber_cpp-4] [INFO] [...] recv #0: "Hello from C++, seq=0"
[chatter_subscriber-3] [INFO] [...] recv #1: "Hello from C++, seq=0" ← 跨语言互通
[chatter_subscriber_cpp-4] [INFO] [...] recv #1: "Hello from PY, seq=1" ← 跨语言互通
...
```
**Ctrl+C** 退出。
@@ -278,15 +279,13 @@ docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && source /root
**预期输出**:
```
/chatter
/joint_states
/parameter_events
/rosout
/tf
```
**看 chatter 频率**:
```bash
docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && source /root/ros2_ws/install/setup.bash && ros2 topic hz /chatter --no-daemon"
docker exec ros2_dev bash -lc "source /opt/ros/humble/setup.bash && source /root/ros2_ws/install/setup.bash && ros2 topic hz /chatter"
```
**预期**:
@@ -295,7 +294,7 @@ average rate: 4.000
min: 0.250s max: 0.260s std dev: 0.00302s window: 10
```
(4Hz = 2 talker × 2Hz)
(4Hz = 2 publisher × 2Hz)
### 5.3 看通信拓扑(可视化)
@@ -380,7 +379,7 @@ source .venv/bin/activate
- [ ] `docker ps` 看到 `ros2_dev` 容器 `Up`
- [ ] `docker exec ros2_dev echo hello` 输出 `hello`
- [ ] `bash scripts/build.sh` 12 packages 全 build 成功
- [ ] `bash scripts/test.sh` 全过(12 packages / 78 tests, 0 failed)
- [ ] `bash scripts/test.sh` 全过(12 packages / 82 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
@@ -399,6 +398,28 @@ source .venv/bin/activate
- Win/macOS:启动 Docker Desktop,等右下角图标稳定
- Linux:`sudo systemctl start docker`
### Q1.4: `ros2 topic info` 显示 Publisher count: 4(应该是 2)
**原因**:`bash scripts/launch.sh pubsub_launch forever` 或多次调试后,launch fork 出的 `chatter_publisher_*` 子进程 reparent 到容器 PID 1,下次启动 launch 又起 2 个,累计 4 个。
**解决**:
```bash
docker exec ros2_dev bash /root/ros2_ws/scripts/clean_ros.sh
```
### Q1.5: `docker compose up` 报 `Pool overlaps with other one on this address space`
```
Network ros2_ros2_net Error Error response from daemon:
invalid pool request: Pool overlaps with other one on this address space
failed to create network ros2_ros2_net
```
**原因**:compose project 名 `ros2` 让网络名前缀成 `ros2_ros2_net`,但仓库里手动(或上次启动)已经创建了 `ros2_net`,子网重叠 daemon 拒绝。
**解决 1(推荐)**:绕过 compose,直接走 docker run(参考 Step 3)。
**解决 2**:
```bash
docker rm -f ros2_dev
docker network rm ros2_net
docker compose -p ros2 -f docker/docker-compose.yml up -d
```
### Q2: 构建镜像很慢,卡在 `pulling image`
**原因**: 网络慢 / 在国内
**解决**:
+25 -24
View File
@@ -161,8 +161,8 @@ rclpy.shutdown() ── 清理
```
### 2.6 本仓库对照
- Python:[`src/py_pubsub/py_pubsub/publisher_member_function.py`](../src/py_pubsub/py_pubsub/publisher_member_function.py)
- C++:[`src/cpp_pubsub/src/publisher_member_function.cpp`](../src/cpp_pubsub/src/publisher_member_function.cpp)
- Python:[`src/py_pubsub/py_pubsub/publisher_node.py`](../src/py_pubsub/py_pubsub/publisher_node.py)(类名 `ChatterPublisher`)
- C++:[`src/cpp_pubsub/src/chatter_publisher.cpp`](../src/cpp_pubsub/src/chatter_publisher.cpp)(类名 `ChatterPublisher`)
---
@@ -486,21 +486,21 @@ executor.spin()
### 6.2 声明 / 读取 / 写
```python
class Talker(Node):
def __init__(self):
super().__init__('talker_py')
# 声明参数 + 默认值
self.declare_parameter('period_ms', 500)
self.declare_parameter('topic', 'chatter')
class ChatterPublisher(Node):
def __init__(self, *, node_name='chatter_publisher'):
super().__init__(node_name)
# 声明参数 + 默认值(实际类名 ChatterPublisher / 参数 publish_rate_hz)
self.declare_parameter('publish_rate_hz', 2.0)
self.declare_parameter('topic_name', 'chatter')
# 读取
period = self.get_parameter('period_ms').value
topic = self.get_parameter('topic').value
rate = self.get_parameter('publish_rate_hz').value
topic = self.get_parameter('topic_name').value
def change_param(self, new_period):
def change_param(self, new_rate):
# 运行时改
param = rclpy.parameter.Parameter(
'period_ms', rclpy.Parameter.Type.INTEGER, new_period
'publish_rate_hz', rclpy.Parameter.Type.DOUBLE, new_rate
)
self.set_parameters([param])
```
@@ -508,12 +508,12 @@ class Talker(Node):
### 6.3 CLI 改参数
```bash
ros2 param list # 节点的所有参数
ros2 param get /talker_py period_ms
ros2 param set /talker_py period_ms 200
ros2 param describe /talker_py period_ms
ros2 param dump /talker_py > params.yaml # 导出
ros2 param load /talker_py params.yaml # 加载
ros2 param list # 节点的所有参数
ros2 param get /chatter_publisher_py publish_rate_hz
ros2 param set /chatter_publisher_py publish_rate_hz 5.0
ros2 param describe /chatter_publisher_py publish_rate_hz
ros2 param dump /chatter_publisher_py > params.yaml # 导出
ros2 param load /chatter_publisher_py params.yaml # 加载
```
### 6.4 launch 中覆盖
@@ -521,24 +521,25 @@ ros2 param load /talker_py params.yaml # 加载
```python
Node(
package='py_pubsub',
executable='talker',
parameters=[{'period_ms': 200, 'topic': 'chatter'}] # 覆盖
executable='chatter_publisher',
name='chatter_publisher_py',
parameters=[{'publish_rate_hz': 5.0, 'topic_name': 'chatter'}], # 覆盖
)
```
CLI 启动:
```bash
ros2 run py_pubsub talker --ros-args -p period_ms:=200 -p topic:=hello
ros2 run py_pubsub chatter_publisher --ros-args -p publish_rate_hz:=5.0 -p topic_name:=hello
```
### 6.5 YAML 文件
```yaml
# config/params.yaml
talker_py:
chatter_publisher_py:
ros__parameters:
period_ms: 200
topic: chatter
publish_rate_hz: 5.0
topic_name: chatter
```
```python
+16 -16
View File
@@ -361,7 +361,7 @@ sudo apt install -y \
ros-humble-sensor-msgs \
ros-humble-geometry-msgs \
ros-humble-rmw-fastrtps-cpp \
ros-humble-ros2control \
ros-humble-ros2-control \
python3-colcon-common-extensions
# 检查内存
@@ -376,13 +376,10 @@ ROS2 默认启动 `ros2 daemon` 加速 `ros2 ...` 命令调用,但每节点起
```bash
# 停 + 禁启 daemon
pkill -9 -f ros2_daemon 2>/dev/null
echo 'unset ROS_DAEMON_PYTHON_OR_EXECUTABLE' >> ~/.bashrc
# alias,直接绕过 daemon
cat >> ~/.bashrc <<'EOF'
alias ros2='ros2 --no-daemon'
EOF
source ~/.bashrc
# 注:`alias ros2='ros2 --no-daemon'` 写到 ~/.bashrc **不会传给子脚本**(脚本里 `ros2` 不展开 alias)。
# Humble 里 `ros2 topic ... --no-daemon` 这个 flag 已被 deprecated,改用下面的方案:
# 设 CYCLONE_DDS_URI / FASTRTPS_DEFAULT_PROFILES_FILE 走静态发现,从根本上不用 daemon
# 验证: 不再起 daemon
ps aux | grep -v grep | grep -i daemon | head -3
@@ -422,6 +419,8 @@ cat > ~/.ros/fastdds.xml <<'EOF'
<discovery_config>
<discoveryProtocol>SIMPLE</discoveryProtocol>
<!-- discoveryStrategy 决定 SIMPLE 的语义:STATIC(只连 initialPeersList)/ NONE(全不主动连)/ MULTICAST(默认)
跟 <initialPeersList> 配合才能实现"单播静态发现" -->
<discoveryStrategy>STATIC</discoveryStrategy>
<leaseDuration>30</leaseDuration>
</discovery_config>
@@ -484,8 +483,8 @@ source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
export ROS_DOMAIN_ID=0
# 跑 cpp_robot_tf2 的关节发布者
ros2 run cpp_robot_tf2 joint_state_publisher
# 跑 cpp_robot_tf2 的关节发布者(executable 名是 joint_state_publisher_cpp,不是 joint_state_publisher)
ros2 run cpp_robot_tf2 joint_state_publisher_cpp
# 预期
[joint_state_publisher_cpp]: joint_state_publisher_cpp started, joints: 3
@@ -572,14 +571,14 @@ ros2 run py_vision_demo fake_camera # 或你写的 YOLO 节点
ssh user@192.168.1.31
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 run cpp_robot_tf2 joint_state_publisher
ros2 run cpp_robot_tf2 joint_state_publisher_cpp
# (后续:接真实电机时改读编码器)
# === 在 PC 上监控全部 ===
docker exec ros2_dev bash -lc "ros2 node list --no-daemon"
# 看到 /fake_camera_py (RDK X5), /joint_state_publisher_cpp (RK3506)
docker exec ros2_dev bash -lc "ros2 node list"
# 看到 /fake_camera (RDK X5), /joint_state_publisher (RK3506,launch 重命名去掉了 _cpp 后缀)
docker exec ros2_dev bash -lc "ros2 topic hz /image_raw /joint_states --no-daemon"
docker exec ros2_dev bash -lc "ros2 topic hz /image_raw /joint_states"
# /image_raw: ~10Hz
# /joint_states: ~20Hz
```
@@ -597,11 +596,11 @@ ros2 run py_vision_demo fake_camera # 1 节点
# === RK3506 #1 ===
ssh user@192.168.1.31
ros2 run cpp_robot_tf2 joint_state_publisher # 1 节点
ros2 run cpp_robot_tf2 joint_state_publisher_cpp # 1 节点
# === RK3506 #2 ===
ssh user@192.168.1.32
ros2 run cpp_robot_tf2 tf2_listener # 1 节点
ros2 run cpp_robot_tf2 tf2_listener_cpp # 1 节点
# === 在任一设备看 ===
ros2 node list --no-daemon
@@ -793,7 +792,8 @@ echo $ROS_DOMAIN_ID # 三机都要一样
切单播 (4.5 节):
```bash
export ROS_STATIC_PEERS="192.168.1.10:7400;192.168.1.20:7400"
# 分隔符是英文逗号 `,`,不是分号 `;`(FastDDS 默认)
export ROS_STATIC_PEERS="192.168.1.10:7400,192.168.1.20:7400"
```
### 9.3 RK3506 内存不足
+47 -32
View File
@@ -316,51 +316,62 @@ PushRosNamespace('my_ns')
完整示例见 `src/py_params/`,这里讲关键设计:
### 5.1 主节点: `param_node.py`
### 5.1 主节点: `py_params/param_node.py`
```python
class ParamNode(Node):
def __init__(self):
super().__init__('param_node')
class ParamsTalker(Node):
DEFAULT_RATE_HZ = 1.0
DEFAULT_TOPIC = 'params_chatter'
DEFAULT_PREFIX = 'Params:'
def __init__(self, *, node_name='params_talker'):
super().__init__(node_name)
# 1. 声明三个参数(类型自动推断)
self.declare_parameter('publish_rate', 1.0)
self.declare_parameter('topic_name', 'params_chatter')
self.declare_parameter('message_prefix', 'Params:')
self.declare_parameter('publish_rate_hz', self.DEFAULT_RATE_HZ)
self.declare_parameter('topic_name', self.DEFAULT_TOPIC)
self.declare_parameter('message_prefix', self.DEFAULT_PREFIX)
# 2. 读取参数
# 2. 读取参数 + 构造组件
publish_rate_hz = self.get_parameter('publish_rate_hz').value
topic_name = self.get_parameter('topic_name').value
# 3. 用参数构造发布者
self._pub = self.create_publisher(String, topic_name, 10)
self.publisher_ = self.create_publisher(String, topic_name, 10)
# 4. 用参数构造定时器
rate = self.get_parameter('publish_rate').value
self._timer = self.create_timer(1.0 / rate, self._cb)
period = 1.0 / publish_rate_hz if publish_rate_hz > 0 else 1.0
self.timer_ = self.create_timer(period, self._on_timer)
# 5. 注册回调
self.add_on_set_parameters_callback(self._on_change)
# 3. 缓存可变参数 + 注册回调
self._prefix = self.get_parameter('message_prefix').value
self.add_on_set_parameters_callback(self._validate_parameter_change)
```
### 5.2 回调: 拒绝非法值
```python
def _on_change(self, params):
for p in params:
if p.name == 'publish_rate' and p.value <= 0.0:
return SetParametersResult(
successful=False,
reason='publish_rate 必须 > 0'
)
def _validate_parameter_change(self, params):
for param in params:
if param.name == 'publish_rate_hz':
if not isinstance(param.value, (int, float)):
return SetParametersResult(successful=False,
reason=f'publish_rate_hz 必须是数字')
if param.value <= 0.0:
return SetParametersResult(successful=False,
reason=f'publish_rate_hz 必须 > 0')
elif param.name == 'message_prefix':
if not isinstance(param.value, str):
return SetParametersResult(successful=False,
reason='message_prefix 必须是字符串')
self._prefix = param.value
return SetParametersResult(successful=True)
```
### 5.3 YAML 配置: `config/params.yaml`
```yaml
param_node:
params_talker:
ros__parameters:
publish_rate: 2.0
publish_rate_hz: 2.0
topic_name: "params_chatter"
message_prefix: "Configured:"
```
@@ -382,13 +393,15 @@ def generate_launch_description():
return LaunchDescription([
Node(
package='py_params',
executable='param_node',
executable='params_talker',
parameters=[cfg],
output='screen',
),
])
```
> 注:本仓库 launch 文件名是 `params_launch.py`,命令:`ros2 launch py_params params_launch.py`。
---
## 6. 测试策略 (Test)
@@ -398,15 +411,17 @@ def generate_launch_description():
```python
def test_param_declaration():
rclpy.init()
node = ParamNode()
assert node.get_parameter('publish_rate').value == 1.0
node = ParamsTalker()
assert node.get_parameter('publish_rate_hz').value == 1.0
assert node.get_parameter('topic_name').value == 'params_chatter'
assert node.get_parameter('message_prefix').value == 'Params:'
```
### 6.2 单元测试: 合法 set
```python
new_param = Parameter(
name='publish_rate',
name='publish_rate_hz',
value=ParameterValue(type=ParameterType.PARAMETER_DOUBLE, double_value=5.0),
)
result = node.set_parameters([new_param])
@@ -417,12 +432,12 @@ assert result[0].successful is True
```python
bad = Parameter(
name='publish_rate',
name='publish_rate_hz',
value=ParameterValue(type=ParameterType.PARAMETER_DOUBLE, double_value=-1.0),
)
result = node.set_parameters([bad])
assert result[0].successful is False
assert '必须 > 0' in result[0].reason
assert 'publish_rate_hz 必须 > 0' in result[0].reason
```
### 6.4 YAML 集成测试
@@ -431,7 +446,7 @@ assert '必须 > 0' in result[0].reason
def test_yaml_loadable():
with open('config/params.yaml') as f:
cfg = yaml.safe_load(f)
assert cfg['param_node']['ros__parameters']['publish_rate'] == 2.0
assert cfg['params_talker']['ros__parameters']['publish_rate_hz'] == 2.0
```
---
@@ -542,7 +557,7 @@ ros2 pkg prefix py_params
# 查看 install/py_params/share/py_params/config/params.yaml
# 启动时打印实际加载的参数
ros2 param list /param_node # 看实际值
ros2 param list /params_talker # 看实际值
```
### 8.4 浮点精度
+5 -4
View File
@@ -128,6 +128,7 @@ class MyLifecycleNode(LifecycleNode):
ros2 service call /lifecycle_demo_node/get_state lifecycle_msgs/srv/GetState
# 触发 configure (transition id = 1)
# (Humble+ 推荐用 `ros2 lifecycle set`,见 §8)
ros2 service call /lifecycle_demo_node/change_state \
lifecycle_msgs/srv/ChangeState "{transition: {id: 1}}"
```
@@ -187,15 +188,15 @@ public:
## 8. CLI 控制
```bash
# 1. 启 Lifecycle Node
ros2 launch my_pkg lifecycle_demo.py
# 1. 启 Lifecycle Node(本仓库 launch 文件叫 lifecycle_launch.py,节点默认名 lifecycle_demo_node)
ros2 launch py_lifecycle_composable lifecycle_launch.py
# 2. 触发 configure
ros2 lifecycle set /lifecycle_node configure
ros2 lifecycle set /lifecycle_demo_node configure
# (Humble 后 ros2 lifecycle set 直接用,而不是 service call)
# 3. 触发 activate
ros2 lifecycle set /lifecycle_node activate
ros2 lifecycle set /lifecycle_demo_node activate
# 4. 看状态
ros2 lifecycle get /lifecycle_node
+4 -2
View File
@@ -99,10 +99,10 @@ install(TARGETS my_component
```bash
# 1. 单线程 container(调试用)
ros2 component standalone --container-type standalone
ros2 run rclcpp_components component_container
# 2. 多线程 container(生产用)
ros2 component standalone --container-type multithreaded
ros2 run rclcpp_components component_container_mt
# 3. 在已有 container 里加载组件
ros2 component load <container_name> <package_name> <component_name>
@@ -111,6 +111,8 @@ ros2 component load <container_name> <package_name> <component_name>
ros2 component load /ComponentManager my_pkg my_component
```
> ❗ Humble 里 `ros2 component standalone --container-type ...` 已 deprecated,改用上面的 `ros2 run rclcpp_components component_container[_mt]`。
### Launch 文件
```python
+1 -1
View File
@@ -105,7 +105,7 @@ Topic information: MessageType Count
## 5. 转换格式
### 导出 CSV
### 导出元数据 YAML
```bash
ros2 bag info my_bag --yaml > my_bag_info.yaml
+18 -12
View File
@@ -97,6 +97,10 @@ msg.data = bgr_array.tobytes() # numpy → bytes
## 3. Publisher API(Python / C++)
> ⚠️ **本节用教学简化命名**(Talker/Listener/talker_py/listener_py),只是为了讲解 API。
> 仓库真实节点是 `chatter_publisher` / `chatter_subscriber`,launch 重命名为 `chatter_publisher_py` / `chatter_publisher_cpp` 等。
> 看真实代码:`src/py_pubsub/py_pubsub/publisher_node.py` 的 `ChatterPublisher`。
### 3.1 Python
```python
@@ -201,6 +205,8 @@ int main(int argc, char * argv[]) {
## 4. Subscriber API(Python / C++)
> 同 §3,本节用教学简化命名。真实代码:`src/py_pubsub/py_pubsub/subscriber_node.py` 的 `ChatterSubscriber`。
### 4.1 Python
```python
@@ -337,21 +343,21 @@ ros2 launch bringup pubsub_launch.py
ros2 topic info /chatter -v
```
**预期**:
**预期**(实际节点名是 `bringup/launch/pubsub_launch.py``name=` 字段定的):
```
Publication count: 2
Subscription count: 2
Node name: listener_py Node namespace: /
Node name: chatter_publisher_py Node namespace: /
Publisher count: 0
Node name: talker_py Node namespace: /
Node name: chatter_subscriber_py Node namespace: /
Publisher count: 1
Node name: listener_cpp Node namespace: /
Node name: chatter_publisher_cpp Node namespace: /
Publisher count: 0
Node name: talker_cpp Node namespace: /
Node name: chatter_subscriber_cpp Node namespace: /
Publisher count: 1
```
看到 **talker_py + talker_cpp** 两个 publisher, **listener_py + listener_cpp** 两个 subscriber。
看到 **chatter_publisher_py + chatter_publisher_cpp** 两个 publisher,**chatter_subscriber_py + chatter_subscriber_cpp** 两个 subscriber。
### 6.4 看跨语言消息流
@@ -475,8 +481,8 @@ ros2 bag play my_bag
# 看当前所有节点
ros2 node list
# 看节点发布的 topic
ros2 node info /talker_py
# 看节点发布的 topic(实际节点名 chatter_publisher_py / chatter_publisher_cpp)
ros2 node info /chatter_publisher_py
```
---
@@ -563,10 +569,10 @@ docker exec ros2_dev bash -lc "cd /root/ros2_ws && source install/setup.bash &&
```
### 10.2 源码位置
- Python pub: [`src/py_pubsub/py_pubsub/publisher_member_function.py`](../src/py_pubsub/py_pubsub/publisher_member_function.py)
- Python sub: [`src/py_pubsub/py_pubsub/subscriber_member_function.py`](../src/py_pubsub/py_pubsub/subscriber_member_function.py)
- C++ pub: [`src/cpp_pubsub/src/publisher_member_function.cpp`](../src/cpp_pubsub/src/publisher_member_function.cpp)
- C++ sub: [`src/cpp_pubsub/src/subscriber_member_function.cpp`](../src/cpp_pubsub/src/subscriber_member_function.cpp)
- Python pub: [`src/py_pubsub/py_pubsub/publisher_node.py`](../src/py_pubsub/py_pubsub/publisher_node.py)
- Python sub: [`src/py_pubsub/py_pubsub/subscriber_node.py`](../src/py_pubsub/py_pubsub/subscriber_node.py)
- C++ pub: [`src/cpp_pubsub/src/chatter_publisher.cpp`](../src/cpp_pubsub/src/chatter_publisher.cpp)
- C++ sub: [`src/cpp_pubsub/src/chatter_subscriber.cpp`](../src/cpp_pubsub/src/chatter_subscriber.cpp)
- launch: [`src/bringup/launch/pubsub_launch.py`](../src/bringup/launch/pubsub_launch.py)
### 10.3 端到端日志
+4 -4
View File
@@ -34,7 +34,7 @@ DDS 提供:
| RMW | 包 | 适用 |
|---|---|---|
| `rmw_fastrtps_cpp` | `ros-humble-rmw-fastrtts-cpp` | 通用,默认 |
| `rmw_fastrtps_cpp` | `ros-humble-rmw-fastrtps-cpp` | 通用,默认 |
| `rmw_cyclonedds_cpp` | `ros-humble-rmw-cyclonedds-cpp` | 跨网段 / Xenomai 实时 |
切换:
@@ -85,8 +85,8 @@ UDP multicast **不跨路由器**,跨网段(如 192.168.1.x ↔ 192.168.2.x)默
### 方案 1: 单播发现 (`ROS_STATIC_PEERS`)
```bash
# PC 端(知道 RK3506 IP)
ROS_STATIC_PEERS="192.168.2.10;192.168.2.11" \
# PC 端(知道 RK3506 IP,分隔符是英文逗号 `,`,不是分号)
ROS_STATIC_PEERS="192.168.2.10,192.168.2.11" \
ros2 launch my_pkg demo.py
```
@@ -128,7 +128,7 @@ export CYCLONE_DDS_URI=file:///etc/cyclonedds.xml
`/etc/cyclonedds.xml`:
```xml
<?xml version="1.0" version="1.0"?>
<?xml version="1.0" encoding="UTF-8" ?>
<CycloneDDS xmlns="https://cdds.io/config">
<Domain id="any">
<General>
+3 -2
View File
@@ -109,7 +109,8 @@ from example_interfaces.srv import AddTwoInts
class AddTwoIntsServer(Node):
def __init__(self):
super().__init__('add_two_ints_server_py')
# 节点名(实际本仓库无 _py 后缀,launch 也不重命名)
super().__init__('add_two_ints_server')
# create_service(srv_type, srv_name, callback)
# callback 签名: callback(request, response) -> response
self.srv = self.create_service(
@@ -167,7 +168,7 @@ private:
```python
class Client(Node):
def __init__(self):
super().__init__('add_two_ints_client_py')
super().__init__('add_two_ints_client')
self.client = self.create_client(AddTwoInts, 'add_two_ints')
# 阻塞等服务端上线(1s 超时,循环等)
+2 -1
View File
@@ -130,7 +130,8 @@ from example_interfaces.action import Fibonacci
class FibonacciActionServer(Node):
def __init__(self):
super().__init__('fibonacci_action_server_py')
# 节点名(实际本仓库无 _py 后缀)
super().__init__('fibonacci_action_server')
self._action_server = ActionServer(
self,
Fibonacci, # ActionType
+11 -11
View File
@@ -290,8 +290,8 @@ sudo apt install ros-humble-robot-state-publisher
ros2 run robot_state_publisher robot_state_publisher \
--ros-args -p robot_description:="$(xacro arm.urdf)"
# 发布 JointState(本仓库 cpp_robot_tf2 就是这个)
ros2 run cpp_robot_tf2 joint_state_publisher
# 发布 JointState(本仓库 cpp_robot_tf2 就是这个,executable 名 joint_state_publisher_cpp)
ros2 run cpp_robot_tf2 joint_state_publisher_cpp
```
### 7.3 看 TF 树
@@ -380,16 +380,16 @@ def transform_grasp_to_base(camera_pose, buffer):
docker exec ros2_dev bash -lc "source /root/ros2_ws/install/setup.bash && ros2 launch bringup robot_launch.py"
```
**预期日志**:
**预期日志**(launch 用 `name=` 重命名去掉了 `_cpp` 后缀,所以节点名是 joint_state_publisher / tf2_listener,而 `[INFO]` 前缀里显示的是 launch 起的 process 名,带 `_cpp`):
```
[joint_state_publisher_cpp]: joint_state_publisher_cpp started, joints: 3
[tf2_listener_cpp]: tf2_listener_cpp started
[robot_state_publisher]: got segment base_link
[robot_state_publisher]: got segment link1
[robot_state_publisher]: got segment link2
[robot_state_publisher]: got segment gripper
[tf2_listener_cpp]: gripper in base_link: x=0.013 y=0.004 z=0.299
[tf2_listener_cpp]: gripper in base_link: x=0.019 y=0.009 z=0.298
[joint_state_publisher_cpp-1] [INFO] [...] JointStatePublisher started, joints: 3
[tf2_listener_cpp-2] [INFO] [...] Tf2Listener started: from=base_link to=gripper
[robot_state_publisher-3] [INFO] [...] got segment base_link
[robot_state_publisher-3] [INFO] [...] got segment link1
[robot_state_publisher-3] [INFO] [...] got segment link2
[robot_state_publisher-3] [INFO] [...] got segment gripper
[tf2_listener_cpp-2] [INFO] [...] gripper in base_link: x=0.013 y=0.004 z=0.299
[tf2_listener_cpp-2] [INFO] [...] gripper in base_link: x=0.019 y=0.009 z=0.298
```
### 10.2 看 TF 树(PDF)
+3 -3
View File
@@ -591,10 +591,10 @@ GUI 里:
docker exec ros2_dev bash -lc "source /root/ros2_ws/install/setup.bash && ros2 launch bringup robot_launch.py"
```
节点:
- `joint_state_publisher_cpp` (本包)
节点(launch 重命名后是 `joint_state_publisher` / `tf2_listener`,executable 名带 `_cpp`):
- `joint_state_publisher_cpp` (本包,executable 名)
- `robot_state_publisher` (系统包)
- `tf2_listener_cpp` (本包)
- `tf2_listener_cpp` (本包,executable 名)
### 11.2 校验本仓库 URDF
+7 -1
View File
@@ -391,8 +391,14 @@ src/
├── py_action_demo/launch/action_launch.py (1 action server)
├── cpp_robot_tf2/launch/robot_tf2_launch.py (3 节点,URDF + TF)
├── py_vision_demo/launch/vision_launch.py (2 节点,cv_bridge)
├── py_params/launch/params_launch.py (1 节点 + YAML)
├── cpp_custom_interface/(无 launch,直接 ros2 run)
├── py_lifecycle_composable/launch/lifecycle_launch.py
├── cpp_qos_demo/(无 launch,直接 ros2 run)
├── py_overlay_dds/(无 launch,直接 ros2 run)
└── bringup/launch/
├── pubsub_launch.py (4 节点,Topic 跨包)
├── all_launch.py (4 demo 合一)
├── pubsub_launch.py (4 节点,Topic 跨包跨语言)
├── service_launch.py
├── action_launch.py
├── robot_launch.py (嵌套 cpp_robot_tf2)
+5 -2
View File
@@ -344,8 +344,11 @@ colcon build ...
```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
# ❗ --executor sequential 必需:cpp_custom_interface 的 rosidl export cmake 在并行构建时偶发失败
colcon build --symlink-install --executor sequential \
--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
```
### 11.2 增量 build(只编改的)