# Fast DDS `.164` 专用链路设计与回滚规格

- 日期：2026-07-23
- 适用环境：RDKx5（ROS 2 Humble）与 WSL（ROS 2 Humble）
- ROS Domain：`22`
- 状态：设计已确认，DDS 配置尚未部署

## 1. 目标

将 WSL 与 RDKx5 之间的 ROS 2/DDS 数据固定在以下链路：

```text
WSL 192.168.10.151 <-> RDKx5 192.168.10.164 (wlx200db0c3fca5)
```

RDKx5 的 `192.168.10.210`（`wlan0`）继续用于 SSH 和 VS Code，不承担 WSL 与 RDKx5 之间的 DDS 数据。

本次不开发 `obstacle_nav_web`，只优化 DDS 网络选择。

## 2. 已确认的现状

### RDKx5

- 主机：`sunrise@192.168.10.210`
- ROS 2：Humble
- 默认 RMW：`rmw_fastrtps_cpp`
- `~/.bashrc` 已设置 `ROS_DOMAIN_ID=22`
- DDS 专用网卡：`wlx200db0c3fca5`，地址 `192.168.10.164/24`
- 管理网卡：`wlan0`，地址 `192.168.10.210/24`
- 两张网卡处于同一子网，普通路由优先级为 `.210` 的 metric 600、`.164` 的 metric 601。因此不能依赖普通路由保证 DDS 使用 `.164`。

### WSL

- 用户：`hikos`
- 默认 RMW：`rmw_fastrtps_cpp`
- ROS 2 接口：`eth4`，地址 `192.168.10.151/24`
- 到 `.164` 和 `.210` 均经 `eth4` 直连
- WSL 端配置必须放在 `/home/hikos/Yiliao2026/` 中

## 3. 采用方案

继续使用 Fast DDS，不修改系统路由、网卡 metric 或防火墙。双方通过 Fast DDS 默认 participant profile 显式选择接口，并设置对端 discovery peer。

### 跨主机 UDP

- RDKx5 的自定义 UDPv4 transport 只允许本地地址 `192.168.10.164`。
- WSL 的自定义 UDPv4 transport 只允许本地地址 `192.168.10.151`。
- RDKx5 discovery peer 指向 `192.168.10.151`。
- WSL discovery peer 指向 `192.168.10.164`。
- 不对单个 DDS 端口做端口转发；DDS 发现和数据端口由 Fast DDS 按 Domain 和 participant 管理。

### 同主机通信

配置保留 SHM transport。RDKx5 内部 ROS 节点之间及 WSL 内部 ROS 节点之间优先使用共享内存，避免将同主机的大流量消息无意义地送入无线链路。

### 生效范围

只有从指定环境脚本启动的 ROS 2 participant 才受该配置约束。为保证实际 Nav2 数据都走 `.164`，RDKx5 的底盘、雷达、`obstacle_scanner` 和 Nav2 必须从同一个已加载 RDK 环境脚本的终端或其子进程启动；WSL 现有软件必须从已加载 WSL 环境脚本的终端或其子进程启动。

## 4. 计划新增文件

### RDKx5

```text
/home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml
/home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
```

### WSL

```text
/home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml
/home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
```

### 变更与回滚记录

```text
/home/sunrise/yiliao_ws/docs/crucial_change/2026-07-23-fastdds-164-network-change.md
```

当前设计规格即保存在上述变更文档中。实施时必须在同一文档中追加实际文件内容、执行命令、验证结果、文件校验值和回滚结果，不另建无法互相对应的零散说明。

## 5. 环境脚本行为

两个脚本均设计为由 Bash `source` 加载，不作为独立子进程执行。

脚本必须：

1. 检查当前主机是否存在预期的本地 IP；不存在时输出明确错误并返回非零。
2. 检查 XML 文件存在且可读。
3. 设置 `ROS_DOMAIN_ID=22`。
4. 设置 `RMW_IMPLEMENTATION=rmw_fastrtps_cpp`。
5. 将 Humble 使用的 `FASTRTPS_DEFAULT_PROFILES_FILE` 指向对应 XML；如当前 Fast DDS 同时支持新变量，则将 `FASTDDS_DEFAULT_PROFILES_FILE` 指向同一文件。
6. 输出本次选择的 Domain、RMW、配置文件和本地 DDS 地址，便于启动前人工核对。
7. 不修改 `~/.bashrc`、`~/.profile` 或系统服务环境。

## 6. 使用流程

### RDKx5

```bash
source /opt/ros/humble/setup.bash
source /home/sunrise/yiliao_ws/install/setup.bash
source /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
ros2 launch obstacle_nav2 obstacle_nav2.launch.py
```

### WSL

```bash
source /opt/ros/humble/setup.bash
source /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
# 在同一终端启动现有软件
```

## 7. 精简验证

实施后只执行以下必要验证：

1. 双端加载环境脚本，确认本地 IP、XML、`ROS_DOMAIN_ID=22` 和 `rmw_fastrtps_cpp` 正确。
2. 使用唯一命名的临时 ROS 2 话题验证 WSL 与 RDKx5 能互相发现并收发消息。
3. 从该环境启动实际 `ros2 launch`，确认 WSL 能看到关键话题；观察 DDS 数据经过 `.164`，`.210` 上的 SSH 保持正常，并且没有 WSL 相关 DDS 数据使用 `.210`。

验证记录应包含执行命令、成功或失败、关键输出摘要。无需进行长时间带宽统计、完整 Nav2 功能回归或 costmap 性能调优。

## 8. 失败保护和回滚

### 快速回滚

1. 停止从专用 DDS 环境启动的 ROS 2 节点。
2. 关闭已加载环境脚本的终端，或在当前终端取消 DDS 配置变量。
3. 新开终端，仅加载原有 ROS 2 和工作空间环境，然后按原命令重新启动。
4. 验证默认 ROS graph 恢复。

### 文件级回滚

配置验证失败且不再保留时，只删除本规格第 4 节列出的四个新增配置文件；如果目录为空，再删除新增的 `config/dds` 目录。不得删除工作空间、现有 package、路由表或其他用户文件。

实施文档必须记录精确的删除命令。在执行删除前，应再次解析并核对绝对路径分别位于：

- `/home/sunrise/yiliao_ws/config/dds/`
- `/home/hikos/Yiliao2026/config/dds/`

### 不需要回滚的项目

因为本方案不修改以下项目，所以回滚不得操作它们：

- RDKx5 与 WSL 的路由表
- 防火墙规则
- 网卡 metric
- `~/.bashrc` 与 `~/.profile`
- `obstacle_nav2`、`obstacle_scanner` 和其他 ROS package
- SSH 与 VS Code 配置

## 9. 变更记录要求

实施时，本文件必须追加：

- 每个新增文件的绝对路径与完整内容
- 修改前的网络和 ROS 环境摘要
- 实际执行命令及执行顺序
- 每项精简验证的结果
- 新增文件的 SHA-256
- 完整回滚命令
- 若发生失败，失败现象、停止位置以及是否已回滚

如果实施内容与本设计不一致，必须先在本文件记录差异及原因，再执行该项变更。

## 10. 验收标准

- WSL 与 RDKx5 在 Domain 22 中能发现并收发 ROS 2 消息。
- 由专用环境启动的跨主机 DDS 通信只使用 WSL `.151` 与 RDKx5 `.164`。
- `.210` 上的 SSH 和 VS Code 管理链路保持可用。
- 未修改路由表、系统启动文件、现有 ROS package 或 Nav2 参数。
- 所有实际修改、验证和回滚方式均记录在本文件中。

# Fast DDS `.164` 专用链路实施计划

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 让从专用环境启动的 WSL 与 RDKx5 ROS 2 participant 只通过 `.151 <-> .164` 交换跨主机 DDS 数据，同时保持 `.210` 的管理链路不变。

**Architecture:** 双端保持 `rmw_fastrtps_cpp` 和 Domain 22，分别加载一个 Fast DDS 默认 participant profile。profile 保留 SHM 供同主机通信，并用带接口白名单的自定义 UDPv4 transport 约束跨主机通信；sourceable Bash 脚本负责前置检查和环境变量设置。

**Tech Stack:** ROS 2 Humble、Fast DDS 2.6.x、Bash、Fast DDS XML profiles、`tcpdump`、Git。

---

## 文件职责

- `/home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml`：RDKx5 participant transport，只允许 UDPv4 使用 `.164`，peer 为 WSL `.151`。
- `/home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh`：验证 RDKx5 网卡和 XML 后，导出 Domain、RMW 和 profile 环境变量。
- `/home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml`：WSL participant transport，只允许 UDPv4 使用 `.151`，peer 为 RDKx5 `.164`。
- `/home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh`：验证 WSL 网卡和 XML 后，导出 Domain、RMW 和 profile 环境变量。
- `/home/sunrise/yiliao_ws/docs/crucial_change/2026-07-23-fastdds-164-network-change.md`：设计、计划、实际变更、证据、提交号和回滚命令的唯一记录。

### Task 1：实施前保护与基线

**Files:**

- Modify: `/home/sunrise/yiliao_ws/docs/crucial_change/2026-07-23-fastdds-164-network-change.md`

- [ ] **Step 1：确认目标文件不会被覆盖**

在 RDKx5 执行：

```bash
for path in \
  /home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml \
  /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh; do
  if [ -e "$path" ]; then
    echo "STOP: existing file: $path" >&2
    exit 1
  fi
done
```

在 WSL 执行：

```bash
for path in \
  /home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml \
  /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh; do
  if [ -e "$path" ]; then
    echo "STOP: existing file: $path" >&2
    exit 1
  fi
done
```

Expected：两段命令均无输出且返回 `0`。任何文件已存在都停止实施，先将其路径、SHA-256 和内容写入本文件，不能覆盖。

- [ ] **Step 2：采集最小基线**

RDKx5：

```bash
ip -br -4 addr
ip -4 route
source /opt/ros/humble/setup.bash
python3 -c 'from rclpy.utilities import get_rmw_implementation_identifier; print(get_rmw_implementation_identifier())'
grep -n 'ROS_DOMAIN_ID' /home/sunrise/.bashrc
git -C /home/sunrise/yiliao_ws status --short
```

WSL：

```bash
ip -br -4 addr
ip -4 route get 192.168.10.164
source /opt/ros/humble/setup.bash
python3 -c 'from rclpy.utilities import get_rmw_implementation_identifier; print(get_rmw_implementation_identifier())'
git -C /home/hikos/Yiliao2026 status --short
```

Expected：RDKx5 存在 `.164` 和 `.210`，WSL 存在 `.151`，双方 RMW 均为 `rmw_fastrtps_cpp`。记录现有 dirty files，后续提交不得包含它们。

- [ ] **Step 3：把基线摘要追加到唯一变更文档**

用 `apply_patch` 在本文件末尾增加 `## 实施记录` 和 `### 实施前基线`，写入上述命令的关键输出、实施开始时间以及“目标文件原先不存在”。不要粘贴无关的大段 package 列表。

- [ ] **Step 4：提交基线记录**

```bash
cd /home/sunrise/yiliao_ws
git add -- docs/crucial_change/2026-07-23-fastdds-164-network-change.md
git diff --cached --check
git diff --cached --name-only
git commit -m "docs: record Fast DDS network baseline" -- \
  docs/crucial_change/2026-07-23-fastdds-164-network-change.md
```

Expected：待提交文件列表只有该 Markdown 文档。

### Task 2：RDKx5 Fast DDS 配置

**Files:**

- Create: `/home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml`
- Create: `/home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh`
- Modify: `/home/sunrise/yiliao_ws/docs/crucial_change/2026-07-23-fastdds-164-network-change.md`

- [ ] **Step 1：运行存在性测试并确认失败**

```bash
test -f /home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml && \
test -f /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
```

Expected：返回非零，因为两个文件尚未创建。

- [ ] **Step 2：创建 RDKx5 XML**

创建目录 `/home/sunrise/yiliao_ws/config/dds`：

```bash
mkdir -p /home/sunrise/yiliao_ws/config/dds
```

然后用 `apply_patch` 创建 `fastdds_rdk_164.xml`，完整内容为：

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
  <transport_descriptors>
    <transport_descriptor>
      <transport_id>rdk_udp_164</transport_id>
      <type>UDPv4</type>
      <interfaceWhiteList>
        <address>192.168.10.164</address>
      </interfaceWhiteList>
    </transport_descriptor>
    <transport_descriptor>
      <transport_id>rdk_shm</transport_id>
      <type>SHM</type>
    </transport_descriptor>
  </transport_descriptors>

  <participant profile_name="rdk_dds_164" is_default_profile="true">
    <rtps>
      <builtin>
        <initialPeersList>
          <locator>
            <udpv4>
              <address>192.168.10.151</address>
            </udpv4>
          </locator>
        </initialPeersList>
      </builtin>
      <userTransports>
        <transport_id>rdk_udp_164</transport_id>
        <transport_id>rdk_shm</transport_id>
      </userTransports>
      <useBuiltinTransports>false</useBuiltinTransports>
    </rtps>
  </participant>
</profiles>
```

- [ ] **Step 3：创建 RDKx5 环境脚本**

用 `apply_patch` 创建 `use_rdk_dds_164.sh`，完整内容为：

```bash
#!/usr/bin/env bash

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  echo "ERROR: source this file instead of executing it" >&2
  exit 64
fi

_rdk_dds_profile="/home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml"

if ! ip -4 addr show dev wlx200db0c3fca5 2>/dev/null | grep -q 'inet 192\.168\.10\.164/'; then
  echo "ERROR: wlx200db0c3fca5 does not own 192.168.10.164" >&2
  unset _rdk_dds_profile
  return 1
fi

if [[ ! -r "${_rdk_dds_profile}" ]]; then
  echo "ERROR: unreadable Fast DDS profile: ${_rdk_dds_profile}" >&2
  unset _rdk_dds_profile
  return 1
fi

export ROS_DOMAIN_ID=22
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export ROS_LOCALHOST_ONLY=0
export FASTRTPS_DEFAULT_PROFILES_FILE="${_rdk_dds_profile}"
export FASTDDS_DEFAULT_PROFILES_FILE="${_rdk_dds_profile}"

printf 'DDS link enabled: domain=%s rmw=%s local=192.168.10.164 profile=%s\n' \
  "${ROS_DOMAIN_ID}" "${RMW_IMPLEMENTATION}" "${_rdk_dds_profile}"
unset _rdk_dds_profile
```

设置权限：

```bash
chmod 0644 /home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml
chmod 0755 /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
```

- [ ] **Step 4：验证语法和 Fast DDS 实际加载**

```bash
bash -n /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
python3 -c "import xml.etree.ElementTree as ET; ET.parse('/home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml'); print('XML well-formed')"
source /opt/ros/humble/setup.bash
source /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
python3 -c "import rclpy; rclpy.init(); n=rclpy.create_node('rdk_dds_profile_check'); print('RMW participant created'); n.destroy_node(); rclpy.shutdown()"
```

Expected：依次输出 `XML well-formed`、环境摘要和 `RMW participant created`，stderr 中没有 XML profile 或 transport 错误。

- [ ] **Step 5：记录 RDKx5 文件内容与校验值**

```bash
sha256sum \
  /home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml \
  /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
```

用 `apply_patch` 在本文件的实施记录中加入两个文件的完整内容、权限、SHA-256 和 Step 4 的结果。

- [ ] **Step 6：只提交 RDKx5 配置和文档**

```bash
cd /home/sunrise/yiliao_ws
git add -- \
  config/dds/fastdds_rdk_164.xml \
  config/dds/use_rdk_dds_164.sh \
  docs/crucial_change/2026-07-23-fastdds-164-network-change.md
git diff --cached --check
git diff --cached --name-only
git commit -m "feat: add dedicated Fast DDS 164 profile" -- \
  config/dds/fastdds_rdk_164.xml \
  config/dds/use_rdk_dds_164.sh \
  docs/crucial_change/2026-07-23-fastdds-164-network-change.md
```

Expected：提交只包含上述三个路径。

### Task 3：WSL Fast DDS 配置

**Files:**

- Create: `/home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml`
- Create: `/home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh`
- Modify: `/home/sunrise/yiliao_ws/docs/crucial_change/2026-07-23-fastdds-164-network-change.md`

- [ ] **Step 1：运行存在性测试并确认失败**

```bash
test -f /home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml && \
test -f /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
```

Expected：返回非零，因为两个文件尚未创建。

- [ ] **Step 2：创建 WSL XML**

创建目录 `/home/hikos/Yiliao2026/config/dds`：

```bash
mkdir -p /home/hikos/Yiliao2026/config/dds
```

然后用 `apply_patch` 创建 `fastdds_wsl_151.xml`，完整内容为：

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
  <transport_descriptors>
    <transport_descriptor>
      <transport_id>wsl_udp_151</transport_id>
      <type>UDPv4</type>
      <interfaceWhiteList>
        <address>192.168.10.151</address>
      </interfaceWhiteList>
    </transport_descriptor>
    <transport_descriptor>
      <transport_id>wsl_shm</transport_id>
      <type>SHM</type>
    </transport_descriptor>
  </transport_descriptors>

  <participant profile_name="wsl_dds_151" is_default_profile="true">
    <rtps>
      <builtin>
        <initialPeersList>
          <locator>
            <udpv4>
              <address>192.168.10.164</address>
            </udpv4>
          </locator>
        </initialPeersList>
      </builtin>
      <userTransports>
        <transport_id>wsl_udp_151</transport_id>
        <transport_id>wsl_shm</transport_id>
      </userTransports>
      <useBuiltinTransports>false</useBuiltinTransports>
    </rtps>
  </participant>
</profiles>
```

- [ ] **Step 3：创建 WSL 环境脚本**

用 `apply_patch` 创建 `use_wsl_dds_164_link.sh`，完整内容为：

```bash
#!/usr/bin/env bash

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  echo "ERROR: source this file instead of executing it" >&2
  exit 64
fi

_wsl_dds_profile="/home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml"

if ! ip -4 addr show dev eth4 2>/dev/null | grep -q 'inet 192\.168\.10\.151/'; then
  echo "ERROR: eth4 does not own 192.168.10.151" >&2
  unset _wsl_dds_profile
  return 1
fi

if [[ ! -r "${_wsl_dds_profile}" ]]; then
  echo "ERROR: unreadable Fast DDS profile: ${_wsl_dds_profile}" >&2
  unset _wsl_dds_profile
  return 1
fi

export ROS_DOMAIN_ID=22
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export ROS_LOCALHOST_ONLY=0
export FASTRTPS_DEFAULT_PROFILES_FILE="${_wsl_dds_profile}"
export FASTDDS_DEFAULT_PROFILES_FILE="${_wsl_dds_profile}"

printf 'DDS link enabled: domain=%s rmw=%s local=192.168.10.151 peer=192.168.10.164 profile=%s\n' \
  "${ROS_DOMAIN_ID}" "${RMW_IMPLEMENTATION}" "${_wsl_dds_profile}"
unset _wsl_dds_profile
```

设置权限：

```bash
chmod 0644 /home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml
chmod 0755 /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
```

- [ ] **Step 4：验证语法和 Fast DDS 实际加载**

```bash
bash -n /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
python3 -c "import xml.etree.ElementTree as ET; ET.parse('/home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml'); print('XML well-formed')"
source /opt/ros/humble/setup.bash
source /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
python3 -c "import rclpy; rclpy.init(); n=rclpy.create_node('wsl_dds_profile_check'); print('RMW participant created'); n.destroy_node(); rclpy.shutdown()"
```

Expected：依次输出 `XML well-formed`、环境摘要和 `RMW participant created`，stderr 中没有 XML profile 或 transport 错误。

- [ ] **Step 5：记录 WSL 文件并提交**

```bash
sha256sum \
  /home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml \
  /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
cd /home/hikos/Yiliao2026
git add -- \
  config/dds/fastdds_wsl_151.xml \
  config/dds/use_wsl_dds_164_link.sh
git diff --cached --check
git diff --cached --name-only
git commit -m "feat: bind WSL Fast DDS to 192.168.10.151" -- \
  config/dds/fastdds_wsl_151.xml \
  config/dds/use_wsl_dds_164_link.sh
git rev-parse HEAD
```

Expected：提交只包含两个 WSL 配置文件。用 `apply_patch` 把文件完整内容、权限、SHA-256、加载验证结果和 WSL commit 写入 RDKx5 的唯一变更文档，然后单独提交该文档：

```bash
cd /home/sunrise/yiliao_ws
git add -- docs/crucial_change/2026-07-23-fastdds-164-network-change.md
git diff --cached --check
git diff --cached --name-only
git commit -m "docs: record WSL Fast DDS deployment" -- \
  docs/crucial_change/2026-07-23-fastdds-164-network-change.md
```

Expected：RDKx5 本次提交只包含该 Markdown 文档。

### Task 4：双向通信与网卡检查

**Files:**

- Modify: `/home/sunrise/yiliao_ws/docs/crucial_change/2026-07-23-fastdds-164-network-change.md`

- [ ] **Step 1：清理 ROS 2 daemon 并加载双方环境**

RDKx5：

```bash
source /opt/ros/humble/setup.bash
source /home/sunrise/yiliao_ws/install/setup.bash
source /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
ros2 daemon stop || true
```

WSL：

```bash
source /opt/ros/humble/setup.bash
source /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
ros2 daemon stop || true
```

Expected：两个终端显示 Domain 22、Fast DDS 和各自本地专用地址。

- [ ] **Step 2：验证 WSL 到 RDKx5**

先在 RDKx5 运行：

```bash
timeout 20 ros2 topic echo /dds_164_smoke_20260723 std_msgs/msg/String --once
```

随后在 WSL 运行：

```bash
ros2 topic pub --once /dds_164_smoke_20260723 std_msgs/msg/String "{data: wsl_to_rdk}"
```

Expected：RDKx5 输出 `data: wsl_to_rdk`。

- [ ] **Step 3：验证 RDKx5 到 WSL**

先在 WSL 运行：

```bash
timeout 20 ros2 topic echo /dds_164_smoke_return_20260723 std_msgs/msg/String --once
```

随后在 RDKx5 运行：

```bash
ros2 topic pub --once /dds_164_smoke_return_20260723 std_msgs/msg/String "{data: rdk_to_wsl}"
```

Expected：WSL 输出 `data: rdk_to_wsl`。

- [ ] **Step 4：确认 DDS 使用 `.164` 而非 `.210`**

在 RDKx5 两个终端同时开始短抓包：

```bash
sudo timeout 15 tcpdump -ni wlx200db0c3fca5 -c 5 'udp and host 192.168.10.151'
```

```bash
sudo timeout 15 tcpdump -ni wlan0 -c 1 'udp and host 192.168.10.151'
```

抓包期间重复 Step 2 的一次发布。Expected：`wlx200db0c3fca5` 捕获到 UDP；`wlan0` 超时且捕获 `0 packets`。当前 SSH 命令仍能返回：

```bash
ssh sunrise@192.168.10.210 'echo management-link-ok'
```

- [ ] **Step 5：记录并提交精简验证结果**

用 `apply_patch` 写入双向消息结果、两张网卡的抓包摘要和 `management-link-ok`。如果任一步失败，停止到 Task 6 的快速回滚，不启动 Nav2。

```bash
cd /home/sunrise/yiliao_ws
git add -- docs/crucial_change/2026-07-23-fastdds-164-network-change.md
git diff --cached --check
git commit -m "docs: record dedicated DDS link verification" -- \
  docs/crucial_change/2026-07-23-fastdds-164-network-change.md
```

### Task 5：实际 `obstacle_nav2` 最小验证

**Files:**

- Modify: `/home/sunrise/yiliao_ws/docs/crucial_change/2026-07-23-fastdds-164-network-change.md`

- [ ] **Step 1：安全启动 Nav2**

在已加载 RDKx5 专用 DDS 环境的终端运行：

```bash
ros2 launch obstacle_nav2 obstacle_nav2.launch.py enable_motion:=false
```

Expected：launch 启动，真实底盘速度输出仍映射到 `/cmd_vel_disabled`。

- [ ] **Step 2：WSL 检查关键话题**

在已加载 WSL 专用 DDS 环境的终端运行：

```bash
ros2 topic list | grep -E '^/obstacles$|^/local_costmap/costmap$|^/global_costmap/costmap$'
```

Expected：至少出现 `/obstacles`，并出现 local/global costmap 话题；如果当前 Nav2 使用 `costmap_raw` 命名，则记录实际名称，不修改 Nav2 参数。

- [ ] **Step 3：结束测试并记录结果**

使用 `Ctrl+C` 正常停止本次 launch。用 `apply_patch` 记录启动命令、关键话题、停止方式和最终结论；不执行运动目标、不修改 costmap 参数。

- [ ] **Step 4：提交最终记录**

```bash
cd /home/sunrise/yiliao_ws
git add -- docs/crucial_change/2026-07-23-fastdds-164-network-change.md
git diff --cached --check
git commit -m "docs: finalize Fast DDS 164 deployment record" -- \
  docs/crucial_change/2026-07-23-fastdds-164-network-change.md
```

### Task 6：回滚演练说明与最终核对

**Files:**

- Modify: `/home/sunrise/yiliao_ws/docs/crucial_change/2026-07-23-fastdds-164-network-change.md`

- [ ] **Step 1：写明快速回滚命令**

停止由专用环境启动的 ROS 节点后，在当前终端执行：

```bash
unset FASTRTPS_DEFAULT_PROFILES_FILE
unset FASTDDS_DEFAULT_PROFILES_FILE
unset RMW_IMPLEMENTATION
unset ROS_LOCALHOST_ONLY
export ROS_DOMAIN_ID=22
```

推荐的实际快速回滚方式是关闭该终端并新开终端，避免残留环境变量。将上述命令原样保存在实施记录中。

- [ ] **Step 2：写明文件级回滚命令，但成功时不执行**

只有用户要求撤销或部署失败时才执行。执行前分别确认四个绝对路径仍位于批准目录，然后删除具体文件，不递归删除工作空间：

```bash
rm -- \
  /home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml \
  /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
rmdir --ignore-fail-on-non-empty /home/sunrise/yiliao_ws/config/dds
```

```bash
rm -- \
  /home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml \
  /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
rmdir --ignore-fail-on-non-empty /home/hikos/Yiliao2026/config/dds
```

- [ ] **Step 3：最终范围核对**

```bash
git -C /home/sunrise/yiliao_ws status --short
git -C /home/sunrise/yiliao_ws log -5 --oneline
git -C /home/hikos/Yiliao2026 status --short
git -C /home/hikos/Yiliao2026 log -3 --oneline
```

Expected：新提交只涉及四个 DDS 配置文件和本 Markdown；原有 dirty files 仍保持原状态，未被暂存、修改或清理。

## 实施记录

### 实施前基线

- 实施日期：2026-07-23（Asia/Shanghai）
- 用户已明确允许直接在 RDKx5 `master` 和 WSL `zbw` 当前分支实施，并要求仅按明确路径提交。
- 四个目标配置文件在实施前均不存在，未覆盖任何已有文件。
- RDKx5：
  - `wlx200db0c3fca5` 为 `UP`，地址 `192.168.10.164/24`。
  - `wlan0` 为 `UP`，地址 `192.168.10.210/24`。
  - 默认路由优先使用 `wlan0`（metric 600），`.164` 网卡 metric 为 601。
  - `~/.bashrc:141` 为 `export ROS_DOMAIN_ID=22`。
  - 当前 RMW 为 `rmw_fastrtps_cpp`。
  - 实施开始时仓库已有用户内容：`?? src/origincar_birdseye/`。
- WSL：
  - `eth4` 为 `UP`，地址 `192.168.10.151/24`。
  - `ip route get 192.168.10.164` 的结果为 `dev eth4 src 192.168.10.151`。
  - 当前 RMW 为 `rmw_fastrtps_cpp`。
  - 实施开始时仓库已有用户内容：`.vscode/browse.vc.db*` 修改，以及 `.codex`、`resource/`、`src/navigation/`、`src/obstacle_scanner/`、`src/origincar_birdseye/`、`src/third_party/`、`tf_bag/` 等未跟踪路径。
- 本任务不会暂存、修改或清理上述已有用户内容。

### RDKx5 配置实施结果

- 新增目录：`/home/sunrise/yiliao_ws/config/dds/`
- `/home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml`
  - 权限：`0644`
  - SHA-256：`f6714ed471930500bc42d5100113782fb585ce50d48326118a0d0a3c8903d1be`
- `/home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh`
  - 权限：`0755`
  - SHA-256：`5b40b2d3c61bd9d7eedfdb11ef1bb43dd60b3cdbb67ddcb1e2da01ab79996059`

XML 完整内容：

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
  <transport_descriptors>
    <transport_descriptor>
      <transport_id>rdk_udp_164</transport_id>
      <type>UDPv4</type>
      <interfaceWhiteList>
        <address>192.168.10.164</address>
      </interfaceWhiteList>
    </transport_descriptor>
    <transport_descriptor>
      <transport_id>rdk_shm</transport_id>
      <type>SHM</type>
    </transport_descriptor>
  </transport_descriptors>

  <participant profile_name="rdk_dds_164" is_default_profile="true">
    <rtps>
      <builtin>
        <initialPeersList>
          <locator>
            <udpv4>
              <address>192.168.10.151</address>
            </udpv4>
          </locator>
        </initialPeersList>
      </builtin>
      <userTransports>
        <transport_id>rdk_udp_164</transport_id>
        <transport_id>rdk_shm</transport_id>
      </userTransports>
      <useBuiltinTransports>false</useBuiltinTransports>
    </rtps>
  </participant>
</profiles>
```

环境脚本完整内容：

```bash
#!/usr/bin/env bash

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  echo "ERROR: source this file instead of executing it" >&2
  exit 64
fi

_rdk_dds_profile="/home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml"

if ! ip -4 addr show dev wlx200db0c3fca5 2>/dev/null | grep -q 'inet 192\.168\.10\.164/'; then
  echo "ERROR: wlx200db0c3fca5 does not own 192.168.10.164" >&2
  unset _rdk_dds_profile
  return 1
fi

if [[ ! -r "${_rdk_dds_profile}" ]]; then
  echo "ERROR: unreadable Fast DDS profile: ${_rdk_dds_profile}" >&2
  unset _rdk_dds_profile
  return 1
fi

export ROS_DOMAIN_ID=22
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export ROS_LOCALHOST_ONLY=0
export FASTRTPS_DEFAULT_PROFILES_FILE="${_rdk_dds_profile}"
export FASTDDS_DEFAULT_PROFILES_FILE="${_rdk_dds_profile}"

printf 'DDS link enabled: domain=%s rmw=%s local=192.168.10.164 profile=%s\n' \
  "${ROS_DOMAIN_ID}" "${RMW_IMPLEMENTATION}" "${_rdk_dds_profile}"
unset _rdk_dds_profile
```

验证结果：

- `bash -n`：通过。
- XML well-formed：通过。
- Fast DDS/RMW participant 实际创建：通过，输出 `RMW participant created`。
- 环境摘要：Domain `22`、RMW `rmw_fastrtps_cpp`、本地 DDS 地址 `.164`。
- 首次从 PowerShell 直接拼接远端 `python3 -c` 时，引号在 SSH 边界被破坏，Python 在读取 XML 前即报 `SyntaxError`。该失败与 XML 内容无关，未修改配置；改为通过 WSL Bash 和 stdin 传入验证程序后，相同验证完整通过。

### WSL 配置实施结果

- 新增目录：`/home/hikos/Yiliao2026/config/dds/`
- `/home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml`
  - 权限：`0644`
  - SHA-256：`bcf9c6e8e8b724cda1c6d83f02491ae0ad9ad7faec5be5dc01b8d60ea826d7ba`
- `/home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh`
  - 权限：`0755`
  - SHA-256：`7fe539eab3ddaa5b79a32145cd1d10834468ae47a73de239944b34c61be61f52`
- WSL Git commit：`b8840ec94bca8668f7d56d80b75c9db0298d18a3`，该提交只包含上述两个文件。

XML 完整内容：

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
  <transport_descriptors>
    <transport_descriptor>
      <transport_id>wsl_udp_151</transport_id>
      <type>UDPv4</type>
      <interfaceWhiteList>
        <address>192.168.10.151</address>
      </interfaceWhiteList>
    </transport_descriptor>
    <transport_descriptor>
      <transport_id>wsl_shm</transport_id>
      <type>SHM</type>
    </transport_descriptor>
  </transport_descriptors>

  <participant profile_name="wsl_dds_151" is_default_profile="true">
    <rtps>
      <builtin>
        <initialPeersList>
          <locator>
            <udpv4>
              <address>192.168.10.164</address>
            </udpv4>
          </locator>
        </initialPeersList>
      </builtin>
      <userTransports>
        <transport_id>wsl_udp_151</transport_id>
        <transport_id>wsl_shm</transport_id>
      </userTransports>
      <useBuiltinTransports>false</useBuiltinTransports>
    </rtps>
  </participant>
</profiles>
```

环境脚本完整内容：

```bash
#!/usr/bin/env bash

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  echo "ERROR: source this file instead of executing it" >&2
  exit 64
fi

_wsl_dds_profile="/home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml"

if ! ip -4 addr show dev eth4 2>/dev/null | grep -q 'inet 192\.168\.10\.151/'; then
  echo "ERROR: eth4 does not own 192.168.10.151" >&2
  unset _wsl_dds_profile
  return 1
fi

if [[ ! -r "${_wsl_dds_profile}" ]]; then
  echo "ERROR: unreadable Fast DDS profile: ${_wsl_dds_profile}" >&2
  unset _wsl_dds_profile
  return 1
fi

export ROS_DOMAIN_ID=22
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export ROS_LOCALHOST_ONLY=0
export FASTRTPS_DEFAULT_PROFILES_FILE="${_wsl_dds_profile}"
export FASTDDS_DEFAULT_PROFILES_FILE="${_wsl_dds_profile}"

printf 'DDS link enabled: domain=%s rmw=%s local=192.168.10.151 peer=192.168.10.164 profile=%s\n' \
  "${ROS_DOMAIN_ID}" "${RMW_IMPLEMENTATION}" "${_wsl_dds_profile}"
unset _wsl_dds_profile
```

验证结果：

- `bash -n`：通过。
- XML well-formed：通过。
- Fast DDS/RMW participant 实际创建：通过，输出 `RMW participant created`。
- 环境摘要：Domain `22`、RMW `rmw_fastrtps_cpp`、本地 DDS 地址 `.151`、peer `.164`。
- WSL 提交命令在工具等待窗口内没有返回，但后台提交随后正常完成；复核 commit `b8840ec9` 只包含两个批准的配置文件，原有 `.vscode` 暂存内容未进入该提交。

### 实施偏差：WSL 接口名不稳定

- 发现时间：2026-07-23 双向通信复核期间。
- 复现：WSL 重启前 `.151` 位于 `eth4`；重启后仍为 `192.168.10.151/24`，但接口名变为 `eth3`。原脚本执行时返回 `ERROR: eth4 does not own 192.168.10.151`。
- 根因：WSL 镜像网络接口编号会在实例重启后变化，接口名不是稳定标识。
- 修正：WSL 环境脚本不再要求接口名为 `eth4`，改为在所有本地 IPv4 接口中查找 `192.168.10.151/`。Fast DDS XML 仍按稳定 IP `.151` 建立 interface whitelist，网络约束不变。
- 范围：只修改 `/home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh` 的前置检查和错误文本；不修改 XML、路由表、RDKx5 配置或 ROS package。
- 失败保护：如果 `.151` 在任何接口上都不存在，脚本仍返回非零并拒绝启用 DDS 环境。

### 实施偏差：关闭 Domain 22 内建发现多播

- 发现时间：2026-07-23 受控双向通信与双网卡抓包期间。
- 受控条件：测试前 RDKx5 无其他 Domain 22 socket；RDK 测试 participant 的 UDP socket 只绑定 `.164`，WSL 测试 participant 只绑定 `.151`。
- 消息结果：WSL 到 RDKx5 发布/订阅成功。
- 抓包结果：`.164` 捕获 189 个 Domain 22 包；`.210` 捕获 3 个包，三者均为 WSL `.151` 发往 `239.255.0.1:12900` 的 SPDP 多播，没有发往 `.210` 的 Domain 22 单播。
- 根因：配置 initial peer 不会自动关闭 Fast DDS 内建发现多播；同一局域网中的两张 RDKx5 网卡都会收到该多播帧。
- 修正：在 RDKx5 和 WSL profile 的 `<builtin>` 中加入 `<avoid_builtin_multicast>true</avoid_builtin_multicast>`，保留双端 initial peer 进行单播发现。
- 范围：只修改两个已批准的 Fast DDS XML；不修改系统网卡的多播设置、路由、防火墙或 ROS package。
- 修正后的必要验收：双向消息仍成功，`.164` Domain 22 抓包大于零，`.210` Domain 22 抓包为零。

### WSL 接口名修正结果

- 修正提交：`9349f53cbc78851d9d053799af26f8055f58f9d3`
- 修正后脚本 SHA-256：`4dd272d34bbc480e7f5d112348fad7bb59532f155cdabbdc7e7b98dda7ff6155`
- 当前 WSL 接口为 `eth3`、地址仍为 `.151` 时，脚本加载通过。
- 实际 participant socket 为 `.151:12900`、`.151:12910`、`.151:12911` 及 `.151` 上的自定义 UDP socket，没有绑定 WSL 的其他地址。

修正后脚本完整内容：

```bash
#!/usr/bin/env bash

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  echo "ERROR: source this file instead of executing it" >&2
  exit 64
fi

_wsl_dds_profile="/home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml"

if ! ip -o -4 addr show 2>/dev/null | grep -q 'inet 192\.168\.10\.151/'; then
  echo "ERROR: no local IPv4 interface owns 192.168.10.151" >&2
  unset _wsl_dds_profile
  return 1
fi

if [[ ! -r "${_wsl_dds_profile}" ]]; then
  echo "ERROR: unreadable Fast DDS profile: ${_wsl_dds_profile}" >&2
  unset _wsl_dds_profile
  return 1
fi

export ROS_DOMAIN_ID=22
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export ROS_LOCALHOST_ONLY=0
export FASTRTPS_DEFAULT_PROFILES_FILE="${_wsl_dds_profile}"
export FASTDDS_DEFAULT_PROFILES_FILE="${_wsl_dds_profile}"

printf 'DDS link enabled: domain=%s rmw=%s local=192.168.10.151 peer=192.168.10.164 profile=%s\n' \
  "${ROS_DOMAIN_ID}" "${RMW_IMPLEMENTATION}" "${_wsl_dds_profile}"
unset _wsl_dds_profile
```

### 关闭发现多播的文件结果

- RDKx5 XML：
  - 新 SHA-256：`9c7acb168af9eacd93b41e03e02f8554570c94853ab5cd485782ba9483b72be8`
  - commit：`e7c3fe9`
- WSL XML：
  - 新 SHA-256：`990904e92ff54c76e04b70a7aa27fc9331484b2292635f1c22dddaeb0b16d52d`
  - commit：`4b11f35`
- 两个 XML 均只在 `<builtin>` 下新增：

```xml
<avoid_builtin_multicast>true</avoid_builtin_multicast>
```

- 两端 XML well-formed 检查通过，Fast DDS/RMW participant 实际创建通过，没有 profile 或 transport 解析错误。

### 双向与网卡验证过程

1. 首次使用 `ros2 topic` CLI 测试时，既有 Domain 0 daemon 干扰等待逻辑，改用不依赖 CLI daemon 的临时 `rclpy` 发布/订阅节点。
2. 关闭发现多播前的受控测试：双向消息成功，`.164` 捕获 189 个 Domain 22 包，`.210` 只捕获 3 个 SPDP 多播包，没有 `.210` 单播。
3. 关闭发现多播后的首次重测开始前，RDKx5 上出现一套由其他终端启动的未配置 `obstacle_nav2`，其 Domain 22 socket 绑定 `0.0.0.0`，破坏了“无其他 participant”的受控前提。该次消息超时不作为 XML 结论。
4. 当前运行命令确认为 `ros2 launch obstacle_nav2 obstacle_nav2.launch.py enable_motion:=true`，来源终端 `pts/3`；进程环境只有 `ROS_DOMAIN_ID=22`、`ROS_LOCALHOST_ONLY=0`，没有 Fast DDS profile 变量。
5. 因该 launch 已启用真实运动，实施者未擅自终止。最终双向和双网卡验证等待该 launch 停止，或获得明确授权后用专用 DDS 环境重新启动。

### 双向与网卡最终验证结果

- 用户在收到“需要停止或明确授权”的说明后指示继续；实施者对 PID `12541` 的 `enable_motion:=true` launch 发送一次 `SIGINT`。
- launch 在正常清理后退出，复核子进程数为 `0`；未终止其父 shell，未修改启动文件。
- 在 RDKx5 无其他 Domain 22 participant 的受控条件下：
  - RDK 测试 participant socket 只绑定 `.164`。
  - WSL 测试 participant socket 只绑定 `.151`。
  - WSL -> RDKx5：收到 `wsl_to_rdk`。
  - RDKx5 -> WSL：收到 `rdk_to_wsl`。
  - 抓包探针消息 `capture_probe` 收到。
  - `wlx200db0c3fca5` 的 Domain 22 抓包：`163 packets captured`。
  - `wlan0` 的 Domain 22 抓包：`0 packets captured`。
  - `.210` SSH 返回 `management-link-ok`。
- 结论：对从专用环境启动的 Domain 22 participant，跨主机 DDS 数据只使用 WSL `.151` 与 RDKx5 `.164`，`.210` 保持为管理链路。

### 实施偏差：为同主机多进程增加本机 bootstrap peer

- 发现时间：2026-07-23 `obstacle_nav2` 最小实际验证。
- 安全条件：使用专用 RDK 环境启动 `enable_motion:=false`，底盘速度被重映射到 `/cmd_vel_hardware_disabled`，未发布导航目标。
- 现象：底盘、雷达、`obstacle_scanner` 和 Nav2 进程均启动，但 lifecycle manager 持续输出 `Waiting for service controller_server/get_state...`；WSL 只能看到 `/scan` 等基础话题，未看到 `/obstacles` 和 costmap。
- 根因：关闭 SPDP 多播后，RDK profile 的 initial peer 只有远端 WSL `.151`。同一 RDK 上的多个 participant 没有本机单播 bootstrap peer，SHM transport 本身不替代 participant discovery。
- 修正：
  - RDK initial peer 同时包含本机 `.164` 和对端 `.151`。
  - WSL initial peer 同时包含本机 `.151` 和对端 `.164`，保证 WSL 现有软件由多个 participant 组成时也能本机发现。
- 范围：只在两个 Fast DDS XML 的 `<initialPeersList>` 中各增加一个本机 locator；继续保持 `avoid_builtin_multicast=true` 和接口白名单。
- 必要复测：Nav2 lifecycle 完成配置/激活，WSL 可见 `/obstacles` 与 local/global costmap，`.210` Domain 22 抓包仍为零。

### 实施偏差：扩大单播 initial peer 扫描范围

- 发现时间：2026-07-23 增加本机 peer 后的 Nav2 复测。
- 现象：两个 RDK 测试 participant 能通过本机 `.164` peer 互相发现，但包含约 16 个 participant 的 Nav2 launch 仍无法让 lifecycle manager 找到 `controller_server/get_state`。
- 根因：initial peer locator 未显式指定端口时，Fast DDS 只扫描有限数量的 participant ID；默认范围足够两节点测试，不足以覆盖 Nav2 多进程。
- 版本证据：WSL Fast DDS `2.6.11` 与 RDKx5 Fast DDS `2.6.8` 的库均包含 `maxInitialPeersRange` 配置项。
- 修正：在双端自定义 UDPv4 transport descriptor 中加入 `<maxInitialPeersRange>64</maxInitialPeersRange>`。
- 范围依据：64 覆盖当前约 16 个 RDK participant 和 WSL 现有软件，并保留余量；不固定具体 UDP 端口，不引入 discovery server。
- 必要复测：本机多 participant 发现、Nav2 lifecycle 激活、WSL 关键话题可见、`.210` Domain 22 仍为零。

### peer 扫描范围修正与 Nav2 最终结果

- RDKx5 XML：
  - 最终 SHA-256：`ecf275c084d4b8a1d2445a2613e079ab78182ac91dabd7ba53b48c10dde9c6d8`
  - `maxInitialPeersRange` commit：`e365bce`
- WSL XML：
  - 最终 SHA-256：`d5de39799e4daa030025629fe5ee5eac1800f1c6def2c44971108ffe6ee6d54d`
  - `maxInitialPeersRange` commit：`441bf80`
- 两个 RDK participant 的本机单播测试通过：`SUB_RECEIVED ... local_ok` 与 `PUB_SENT ... local_ok`。
- 实际启动命令：

```bash
source /opt/ros/humble/setup.bash
source /home/sunrise/yiliao_ws/install/setup.bash
source /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
ros2 launch obstacle_nav2 obstacle_nav2.launch.py enable_motion:=false
```

- Nav2 lifecycle 完成配置与激活，日志包含 `Managed nodes are active`。
- WSL 专用环境可见：
  - `/obstacles`
  - `/local_costmap/costmap`
  - `/local_costmap/costmap_raw`
  - `/global_costmap/costmap`
  - `/global_costmap/costmap_raw`
  - `/cmd_vel_hardware_disabled`
- 实际 Nav2 participant socket 抽查均绑定 `192.168.10.164`。
- 验证脚本输出 `NAV2_VERIFY_OK`；验证结束后通过 `SIGINT` 停止 launch，未发布运动目标。

RDKx5 最终 XML 完整内容：

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
  <transport_descriptors>
    <transport_descriptor>
      <transport_id>rdk_udp_164</transport_id>
      <type>UDPv4</type>
      <maxInitialPeersRange>64</maxInitialPeersRange>
      <interfaceWhiteList>
        <address>192.168.10.164</address>
      </interfaceWhiteList>
    </transport_descriptor>
    <transport_descriptor>
      <transport_id>rdk_shm</transport_id>
      <type>SHM</type>
    </transport_descriptor>
  </transport_descriptors>

  <participant profile_name="rdk_dds_164" is_default_profile="true">
    <rtps>
      <builtin>
        <avoid_builtin_multicast>true</avoid_builtin_multicast>
        <initialPeersList>
          <locator>
            <udpv4>
              <address>192.168.10.164</address>
            </udpv4>
          </locator>
          <locator>
            <udpv4>
              <address>192.168.10.151</address>
            </udpv4>
          </locator>
        </initialPeersList>
      </builtin>
      <userTransports>
        <transport_id>rdk_udp_164</transport_id>
        <transport_id>rdk_shm</transport_id>
      </userTransports>
      <useBuiltinTransports>false</useBuiltinTransports>
    </rtps>
  </participant>
</profiles>
```

### 最终交付、校验与回滚记录

最终文件状态（2026-07-23 复核）：

- RDKx5 `/home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml`
  - 权限/属主：`0644 sunrise:sunrise`
  - SHA-256：`ecf275c084d4b8a1d2445a2613e079ab78182ac91dabd7ba53b48c10dde9c6d8`
- RDKx5 `/home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh`
  - 权限/属主：`0755 sunrise:sunrise`
  - SHA-256：`5b40b2d3c61bd9d7eedfdb11ef1bb43dd60b3cdbb67ddcb1e2da01ab79996059`
- WSL `/home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml`
  - 权限/属主：`0644 hikos:hikos`
  - SHA-256：`d5de39799e4daa030025629fe5ee5eac1800f1c6def2c44971108ffe6ee6d54d`
- WSL `/home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh`
  - 权限/属主：`0755 hikos:hikos`
  - SHA-256：`4dd272d34bbc480e7f5d112348fad7bb59532f155cdabbdc7e7b98dda7ff6155`

最终精简验证结果：

- 双向 `rclpy` 通信通过；RDKx5 与 WSL 均能接收对端消息。
- RDKx5 抓包结果：`.164` 网卡捕获 `163 packets`，`.210` 网卡捕获 `0 packets`；`.210` SSH 管理链路正常。
- `ros2 launch obstacle_nav2 obstacle_nav2.launch.py enable_motion:=false` 通过：lifecycle 日志包含 `Managed nodes are active`，WSL 可见 `/obstacles`、local/global costmap 与 `/cmd_vel_hardware_disabled`，验证脚本输出 `NAV2_VERIFY_OK`。
- 验证时未发布导航目标，速度输出被重映射到 `/cmd_vel_hardware_disabled`。
- 验证 launch 已通过 `SIGINT` 正常退出；最终进程复核时，RDKx5 与 WSL 均无本任务 Nav2、DDS 验证脚本或 `tcpdump` 残留进程。

快速回滚（不删除文件）：先停止从专用环境启动的 ROS 2 节点。最稳妥方式是关闭该终端并新开终端，只加载原 ROS 2 与工作空间环境。若必须复用当前终端，执行：

```bash
unset FASTRTPS_DEFAULT_PROFILES_FILE
unset FASTDDS_DEFAULT_PROFILES_FILE
unset RMW_IMPLEMENTATION
unset ROS_LOCALHOST_ONLY
export ROS_DOMAIN_ID=22
```

文件级逆向还原（本次验证成功，以下命令仅留档，未执行）：

```bash
# RDKx5：先解析并核对两个目标均位于批准目录，再删除明确文件。
realpath /home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml
realpath /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
rm -- \
  /home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml \
  /home/sunrise/yiliao_ws/config/dds/use_rdk_dds_164.sh
rmdir --ignore-fail-on-non-empty /home/sunrise/yiliao_ws/config/dds
```

```bash
# WSL：先解析并核对两个目标均位于批准目录，再删除明确文件。
realpath /home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml
realpath /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
rm -- \
  /home/hikos/Yiliao2026/config/dds/fastdds_wsl_151.xml \
  /home/hikos/Yiliao2026/config/dds/use_wsl_dds_164_link.sh
rmdir --ignore-fail-on-non-empty /home/hikos/Yiliao2026/config/dds
```

如需连同 Git 记录一起逆向还原，应在确认工作区无其他人的重叠修改后，按提交逆序使用 `git revert <commit>`；不得使用 `git reset --hard`，不得清理本节所列之外的 dirty files。相关提交：

- RDKx5：`edbe450`、`e7c3fe9`、`32a253a`、`e365bce`，以及对应实施记录提交；最近一次成功验证记录为 `985550a`。
- WSL：`b8840ec`、`9349f53`、`4b11f35`、`3195933`、`441bf80`。

最终 Git 范围核对：

- RDKx5 当前分支为 `master`。除本 Markdown 的最终记录外，保留了任务开始前/用户已有的 `obstacle_nav2` 两个修改文件、三个 `nav2_params.yaml.bak.*` 和 `src/origincar_birdseye/`，未暂存、未清理、未纳入本任务提交。
- WSL 当前分支为 `zbw`。保留了 `.vscode/browse.vc.db*`、`.codex`、`resource/`、`src/navigation/`、`src/obstacle_scanner/`、`src/origincar_birdseye/`、`src/third_party/`、`tf_bag/` 等已有 dirty files，未暂存、未清理、未纳入本任务提交。
- 未修改 `.bashrc`、路由表、防火墙、网卡 metric、Nav2 参数、ROS package、SSH 或 VS Code 配置；配置仅在终端中显式 `source` 后生效。

### 2026-07-23 18:51 后续卡顿诊断与恢复计划

#### 只读诊断证据

- 重启前 `ip route get 192.168.10.151 from 192.168.10.164` 返回 `dev wlx200db0c3fca5 table 164`；活动 Nav2 participant 的专用 UDP socket 绑定 `192.168.10.164`。因此已加载专用 profile 的 DDS 当时确实走 `.164`。
- 同一启动周期累计计数显示：`.210` 的 `wlan0` 发送约 `11.58 GB / 3611 万包`，`.164` 网卡发送约 `64.5 MB / 22 万包`。该数量差异表明 `.210` 另有非专用 DDS 环境的流量。
- Windows 对 `.210` 的 20 次 ping 为平均 `4 ms`、最大 `55 ms`；`.164` 为平均 `2 ms`、最大 `5 ms`。
- 18:52:55 启动的两个只读 `tcpdump` 均由 `timeout 8` 控制，并在 18:53:03 正常关闭 sudo session；没有遗留 tcpdump。随后 RDK 网络失联并在 18:57:03 重新启动，上一启动日志末尾没有正常 shutdown 记录，因此只能确认发生了非正常重启，不能从现有日志证明触发源。
- 重启后 `wlx200db0c3fca5` 为 `NO-CARRIER`，没有 `.164` 地址、table 164 或 source rule；`openwrt_5G` 当前扫描不可见。
- `.210` 管理网卡改连 `kskbl_2.4G`，协商 TX 约 `52 Mbit/s`。一个未设置 `FASTRTPS_DEFAULT_PROFILES_FILE` 的 ROS 2 daemon 绑定 `192.168.10.210`，说明未显式 source 专用脚本的 ROS CLI 会绕过 `.164` 配置。

#### 方案与实施顺序

采用回退连接方案：保留 `/etc/NetworkManager/dispatcher.d/90-wlx164-policy-route`、`.210` 管理连接和两个 Fast DDS XML 不变；克隆现有 `kskbl_2.4G` 凭据为只绑定 `wlx200db0c3fca5` 的回退 profile。原 `openwrt_5G 1` 保持为 `.164` 的 5G profile，并将其自动连接优先级设为 `100`；回退 profile 优先级为 `50`。

执行步骤：

1. RED 基线：确认 `.164` 不存在、`use_rdk_dds_164.sh` 返回失败、错误 ROS daemon 的环境中没有 Fast DDS profile 且 socket 位于 `.210`。
2. 创建 `kskbl_2.4G-rdk164`，绑定 `wlx200db0c3fca5` 并试连接。只有 DHCP 返回 `192.168.10.164` 才继续；否则立即断开并删除该 profile。
3. 确认现有 dispatcher 自动恢复 `from 192.168.10.164/32 lookup 164`，且 `.164 -> .151` 走 USB 网卡；不直接编辑路由表、dispatcher、网卡 metric 或防火墙。
4. 停止错误环境创建的 ROS 2 daemon，从专用 DDS 环境启动新的 daemon，检查其环境变量与 UDP socket。
5. 使用双向一次性 ROS 2 消息、短时只计数抓包和 ping 验证 `.164` 承载 DDS、`.210` 没有同一批 DDS 包且 SSH 保持可用。
6. 将实际 profile UUID、命令、结果、Git 状态和残留进程检查追加到本节，只提交本 Markdown。

精确回滚：

```bash
nmcli connection down 'kskbl_2.4G-rdk164' 2>/dev/null || true
nmcli connection delete 'kskbl_2.4G-rdk164'
nmcli connection modify 'openwrt_5G 1' connection.autoconnect-priority 0
```

上述回滚只删除本次新增的回退 profile，并把既有 `openwrt_5G 1` 优先级恢复为实施前的 `0`；不得删除 `kskbl_2.4G`、`openwrt_5G 1`、策略路由脚本或 DDS 文件。

#### 后续恢复实施结果

- RDK 非正常重启后，`openwrt_5G` 扫描不可见，既有 `.164` profile 无法连接；`wlx200db0c3fca5` 没有地址，专用 DDS 脚本按设计返回失败。
- 新增 NetworkManager 回退连接：
  - 名称：`kskbl_2.4G-rdk164`
  - UUID：`98eb5d04-3bf5-49a2-96e7-4ec44171154f`
  - 绑定接口：`wlx200db0c3fca5`
  - 自动连接优先级：`50`
  - DHCP 实际地址：`192.168.10.164/24`
- 既有 `openwrt_5G 1` UUID `9e4f4ea7-f087-4369-8faf-fa3d0faa9839` 继续绑定 USB 网卡，自动连接优先级由 `0` 改为 `100`，保持 5G 为下次启动时的首选。
- 既有 `/etc/NetworkManager/dispatcher.d/90-wlx164-policy-route` 未修改；回退连接成功后自动恢复：

```text
1000: from 192.168.10.164 lookup 164
192.168.10.173 from 192.168.10.164 dev wlx200db0c3fca5 table 164
```

- WSL/Windows 有线 LAN 的 DHCP 地址在网络重启后变为 `192.168.10.173`。仅在 WSL 内临时添加 `.151` 时，WSL 能发出但 RDK 无法反向 ARP；该临时地址随后已删除。
- 尝试用 Windows `New-NetIPAddress` 添加持久 `.151` 辅助地址时系统返回 `Access is denied`，没有产生 Windows 地址或路由修改。
- 因 `.173` 是当前真实可双向访问的 LAN 地址，最终将 WSL Fast DDS 本地地址和 RDK initial peer 从 `.151` 调整为 `.173`：
  - RDK `/home/sunrise/yiliao_ws/config/dds/fastdds_rdk_164.xml`：仅把远端 peer `192.168.10.151` 改为 `192.168.10.173`。
  - WSL XML 从 `fastdds_wsl_151.xml` 重命名为 `fastdds_wsl_173.xml`；transport/profile ID 改为 `wsl_udp_173` / `wsl_dds_173`，本地白名单与本机 peer 改为 `.173`，对端仍为 `.164`。
  - WSL `use_wsl_dds_164_link.sh` 改为检查 `.173` 并加载 `fastdds_wsl_173.xml`。
- 新文件校验：
  - RDK XML：`0644 sunrise:sunrise`，SHA-256 `41c2fcac3c23218c40c3d4cf4273504f8233296e5db6ac44c3bc120c5d989dd1`
  - RDK 脚本未变：SHA-256 `5b40b2d3c61bd9d7eedfdb11ef1bb43dd60b3cdbb67ddcb1e2da01ab79996059`
  - WSL XML：`0644 hikos:hikos`，SHA-256 `fa95118ced19bb0888789c8a0a59cbee1db2a01b00d3592a31b4bd9214ef7b4a`
  - WSL 脚本：`0755 hikos:hikos`，SHA-256 `85a1390cbda47fed4f42c01210067a575775b79c267def83d24abdde0f232543`
- Git 提交：RDK XML `87f562d`；WSL XML/脚本 `c383338`。两个提交均只包含上述明确路径。

#### daemon 与运行中节点恢复

- 重启后发现 Domain 22 的 ROS 2 daemon 没有 Fast DDS profile，UDP socket 位于 `.210`。普通 `ros2 daemon stop` 和 `SIGTERM` 均未使该孤儿进程退出；在严格核对命令行后仅对该 daemon 使用 `SIGKILL`。
- 新 RDK Domain 22 daemon 从专用环境启动，环境包含两个 Fast DDS profile 变量，UDP socket 全部位于 `.164`。
- WSL 旧 daemon 同样绑定已失效 `.151`，按相同方式清理后从 `.173` 环境重启，UDP socket 全部位于 `.173`。
- WSL 原有 `origincar_birdseye offline_validation.launch.py` 的父进程没有 DDS profile，两个子节点仍绑定 `.151`。对明确 PID/命令行发送 `SIGINT` 正常停止后，用原命令在 `.173` 专用环境重启；`synthetic_camera_node` 与 `birdseye_node` 的 socket 均位于 `.173`。
- `.210` 仍有一个 `ROS_DOMAIN_ID=23` 的独立 daemon；它不属于本任务的 Domain 22，未终止、未修改。

#### 最终数据路径与延迟证据

- 双向一次性 ROS 2 消息通过：WSL 收到 `rdk_to_wsl_via_164`，RDK 收到 `wsl_to_rdk_via_164`。
- 在 RDK 从专用环境订阅 WSL `/image` 8 秒，第一次接口增量：
  - `.164` RX `88,225,838` bytes，TX `345,016` bytes
  - `.210` RX `2,042` bytes，TX `1,092` bytes
- 第二次同样负载：
  - `.164` RX `90,334,970` bytes，TX `346,092` bytes
  - `.210` RX `1,194` bytes，TX `1,092` bytes
- 结论：这两批约 84–86 MiB 的图像 DDS 数据经过 `.164`，没有经过 `.210`。
- 无负载时 `.210` 的 20 次 ping 为 `0%` 丢包、平均 `2 ms`、最大 `8 ms`。约 90 MB/8 秒图像负载期间，15 次 ping 仍为 `0%` 丢包、平均 `5 ms`、最大 `52 ms`。
- 剩余延迟尖峰不是 IP/DDS 泄漏：当前 `.164` 回退网卡与 `.210` 管理网卡都连接同一个 `kskbl_2.4G` AP，共享 2.4 GHz 空口。只有 `openwrt_5G` 恢复可见并由优先级 `100` 的 profile 在启动时选中，或使用独立有线/5G 链路，才能进一步消除射频竞争。

#### 后续配置精确回滚

```bash
# RDK：撤销 peer 地址提交，并删除本次新增回退连接。
cd /home/sunrise/yiliao_ws
git revert 87f562d
nmcli connection down 'kskbl_2.4G-rdk164' 2>/dev/null || true
nmcli connection delete 'kskbl_2.4G-rdk164'
nmcli connection modify 'openwrt_5G 1' connection.autoconnect-priority 0
```

```bash
# WSL：恢复 .151 文件名、XML 与脚本。
cd /home/hikos/Yiliao2026
git revert c383338
```

回滚后应停止并重新启动 Domain 22 daemon/ROS 节点，使 participant 重新读取旧 XML。不要删除 `.210` 管理连接、现有策略路由脚本、Domain 23 daemon 或用户 dirty files。

WSL 最终 XML 完整内容（本节以下为最初 `.151` 版本的历史快照；当前生效版本和精确差异以上述后续实施结果为准）：

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
  <transport_descriptors>
    <transport_descriptor>
      <transport_id>wsl_udp_151</transport_id>
      <type>UDPv4</type>
      <maxInitialPeersRange>64</maxInitialPeersRange>
      <interfaceWhiteList>
        <address>192.168.10.151</address>
      </interfaceWhiteList>
    </transport_descriptor>
    <transport_descriptor>
      <transport_id>wsl_shm</transport_id>
      <type>SHM</type>
    </transport_descriptor>
  </transport_descriptors>

  <participant profile_name="wsl_dds_151" is_default_profile="true">
    <rtps>
      <builtin>
        <avoid_builtin_multicast>true</avoid_builtin_multicast>
        <initialPeersList>
          <locator>
            <udpv4>
              <address>192.168.10.151</address>
            </udpv4>
          </locator>
          <locator>
            <udpv4>
              <address>192.168.10.164</address>
            </udpv4>
          </locator>
        </initialPeersList>
      </builtin>
      <userTransports>
        <transport_id>wsl_udp_151</transport_id>
        <transport_id>wsl_shm</transport_id>
      </userTransports>
      <useBuiltinTransports>false</useBuiltinTransports>
    </rtps>
  </participant>
</profiles>
```
