fix
This commit is contained in:
+3
-2
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -105,7 +105,7 @@ Topic information: MessageType Count
|
||||
|
||||
## 5. 转换格式
|
||||
|
||||
### 导出 CSV
|
||||
### 导出元数据 YAML
|
||||
|
||||
```bash
|
||||
ros2 bag info my_bag --yaml > my_bag_info.yaml
|
||||
|
||||
+18
-12
@@ -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 端到端日志
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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(只编改的)
|
||||
|
||||
Reference in New Issue
Block a user