跳到正文

Wiki

開發問題與排查

約 26 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

1.開發問題與排查

1.1.SSH 連線 ysu.local 時解析到 MID360 網口地址

1.1.1.原因

現象是:電腦與工控機連線同一個 Wi-Fi,直接 ping 工控機的 Wi-Fi 地址 192.168.2.183 正常,也能通過這個地址 SSH 登入;但使用 ssh ysu@ysu.local 時,主機名卻解析到 192.168.1.50,提示 Connection closed by 192.168.1.50 port 22。 此前拔掉 MID360 網線並重啟工控機後,主機名又能解析到 Wi-Fi 地址,但保留兩張網絡卡時問題再次出現。

工控機同時使用 Wi-Fi 和有線網口,兩張網絡卡的用途不同:

介面或裝置 本次地址 用途
工控機 Wi-Fi wlp4s0 192.168.2.183/24 SSH、區域網通訊和聯網,地址由 DHCP 分配
工控機雷達有線口 enp3s0 192.168.1.50/24 接收 MID360 點雲和 IMU 資料
MID360 192.168.1.107 向配置的上位機地址傳送資料

**192.168.1.50 是雷達資料的接收端地址,不是 MID360 的預設閘道器。 **該地址填寫在驅動配置的 host_net_info 中,雷達自身地址填寫在 lidar_configs 中。 Wi-Fi 和雷達使用不同的 /24 網段,本次故障不是這兩個網段重疊造成的。

2026-10-19 保留兩張網絡卡復現後,抓包和程序跟蹤確認:工控機上的 NoMachine 9.7.3 在 Wi-Fi 上錯誤釋出了 ysu.local → 192.168.1.50,與 Avahi 釋出的正確地址衝突。

釋出程序 在 Wi-Fi 上返回的 A 記錄 記錄有效期 TTL
avahi-daemon,PID 735 ysu.local → 192.168.2.183 120 秒
nxserver.bin,PID 3955,傳送執行緒 4400 ysu.local → 192.168.1.50 600 秒

在工控機 wlp4s0 上抓到的響應均來自 192.168.2.183:5353,但記錄內容分別為 .183 和 .50。進一步跟蹤系統呼叫,確認 NoMachine 的傳送執行緒將包含 .50 的記錄從 Wi-Fi socket 發出,而 Avahi 發出的是 .183。因此不是單憑“哪個程序監聽了 5353 埠”推測原因,已經定位到錯誤記錄的實際傳送程序。

兩個服務使用同一個主機名給出不同的地址,電腦收到並快取錯誤記錄後,就會把 SSH 發往雷達網段。 600 秒錶示記錄有效期,並不是地址優先順序;實際解析結果還受響應先後和快取更新影響,所以會出現有時正常、有時指向 .50 的現象。

本次電腦去往 .50 的實際路由還進入了 FlClash TUN,去往 .183 則直接經過 Wi-Fi。 錯誤地址來自工控機 NoMachine 的 mDNS 釋出;FlClash 接管去往錯誤地址的連線,是後續路徑問題。 不能僅憑 Connection closed 判斷工控機的 SSH 服務故障。

Avahi 當時已正確回答 Wi-Fi 地址,systemd-resolved 的 mDNS 未啟用,兩端檢查的 hosts 檔案中也沒有靜態配置 ysu.local → .50。先關閉 NoMachine 自帶的區域網發現後,主機名立即恢復正常,驗證了故障來源;單獨重啟 Avahi 或限制其介面,無法阻止 NoMachine 繼續傳送錯誤記錄。隨後進一步通過 NoMachine 自身的 NXDListenAddress 配置繫結 Wi-Fi 地址,恢復原生自動發現,並補上 DHCP 地址變化時的同步。下面先記錄臨時方案,再說明本機最終採用的完整修復。

1.1.2.臨時方案:關閉 NoMachine 自動發現

1. 臨時連線:使用工控機當前的 Wi-Fi 地址。(前提是你得知道它的 IP 地址)

以下命令在電腦端執行,本次地址為:

ssh ysu@192.168.2.183

Wi-Fi 地址可能隨 DHCP 租約變化,可以在工控機本地或路由器客戶端列表中查詢。工控機上也可以執行:

nmcli -f DEVICE,TYPE,STATE,CONNECTION device status
ip -br -4 addr

本次工控機 Wi-Fi 介面為 wlp4s0,雷達有線介面為 enp3s0。排查其他機器時,以實際輸出為準。

2. 工控機:確認 NoMachine 也在使用 mDNS。

以下操作在工控機上執行,不是在用於遠端連線的電腦上執行:

sudo ss -ulpn 'sport = :5353'
sudo /etc/NX/nxserver --version

修復前,5353 UDP 埠同時由 avahi-daemon 和 nxserver.bin 使用。本次 NoMachine 版本為 9.7.3。多個程序監聽同一埠本身不代表一定故障,應結合實際回答的地址判斷;本次已通過抓包和程序跟蹤確認 NoMachine 釋出錯誤記錄。

3. 工控機:關閉 NoMachine 的區域網自動發現。

先備份配置。下面的備份檔名用於本次排查;再次操作時換一個檔名,保留已有備份:

sudo cp -a /usr/NX/etc/server.cfg /usr/NX/etc/server.cfg.before-mdns-fix-20261019
sudoedit /usr/NX/etc/server.cfg

找到本次安裝版本中的配置項:

#EnableLocalNetworkBroadcast 1

去掉開頭的 # 並改為:

EnableLocalNetworkBroadcast 0

這會關閉 NoMachine 的區域網自動發現,其他電腦不再通過它的廣播自動發現該工控機;仍可通過 IP 或能夠正常解析的主機名手動連線 NoMachine。 無需解除安裝 NoMachine,也無需停掉 Avahi。NoMachine 官方埠與配置說明

不同版本的配置項可能不同,舊資料中還會出現 EnableNetworkBroadcast。應以安裝版本的 server.cfg 註釋和對應文件為準,不能把舊名稱直接新增到本次 9.7.3 的配置中。

4. 工控機:重啟 NoMachine,使配置生效。

先結束正在使用的 NoMachine 遠端桌面會話,再執行;重啟會暫時中斷該服務:

sudo /etc/NX/nxserver --restart
sudo /etc/NX/nxserver --status

隨後檢查:

sudo ss -ulpn 'sport = :5353'
sudo ss -ltnp 'sport = :4000'

臨時方案生效後,5353 埠只剩 Avahi,NoMachine 的 nxserver、nxnode 和 nxd 服務恢復執行,4000 TCP 埠繼續監聽。配置儲存在檔案中,系統重啟後仍會讀取;本次沒有修改 Avahi 配置,也沒有修改 Wi-Fi、雷達 IP 或路由。

5. 電腦端:驗證主機名和 SSH。

getent ahostsv4 ysu.local
avahi-resolve-host-name -4 ysu.local
ssh ysu@ysu.local

預期解析到工控機當前的 Wi-Fi 地址,本次為 192.168.2.183,並且可以正常登入。已經快取的錯誤記錄可能需要等待過期或被正確記錄更新;若仍返回 .50,應繼續抓包確認是否還有錯誤釋出源,不能僅憑服務狀態認定修復完成。

如需抓包,在工控機執行以下命令,將介面名換成實際 Wi-Fi 介面:

sudo tcpdump -ni wlp4s0 -vv 'udp port 5353'

觀察 ysu.local 的 A 記錄內容,而不只是資料包的源 IP。本次錯誤響應的資料包源 IP 也是 .183,真正錯誤的是其中的 A 記錄指向了 .50。

1.1.3.臨時方案驗證結果

2026-10-19 完成上述修改並重啟 NoMachine 後:

檢查專案 結果
電腦端三次名稱查詢 均得到 192.168.2.183
工控機 Wi-Fi 上的三次 mDNS 查詢響應 均為 Avahi 返回的 .183,該輪抓包未再出現 .50
ssh ysu@ysu.local 密碼登入成功,遠端 hostname 返回 ysu
NoMachine 狀態 服務正常,4000 TCP 埠繼續監聽
電腦斷開並重新連線原 Wi-Fi 後 主機名仍解析到 .183

臨時方案只將 /usr/NX/etc/server.cfg 中的 EnableLocalNetworkBroadcast 設為 0,原配置備份在同目錄的 server.cfg.before-mdns-fix-20261019。後續已恢復自動發現,最終狀態以下面的完整修復為準。

1.1.4.完整修復:讓 NoMachine 自動跟隨 Wi-Fi 當前 IP

這裡固定的是用於遠端連線的 Wi-Fi 介面,不是某一個 IP。NXDListenAddress 本身需要填寫具體地址,不能靠寫一行配置就自動追蹤 DHCP。動態效果來自下面的同步程式:每次讀取 Wi-Fi 當前地址,再把它寫入 NoMachine 配置。

例如當前地址為 .183,程式就寫入 .183;以後 DHCP 分配了其他地址,程式會用新地址替換它。不要手動把 NXDListenAddress 永久寫成 192.168.2.183,也不要把 ysu.local 填進這個欄位來解析自己;電腦端連線時才使用 ysu.local。NoMachine 監聽和廣播地址配置說明

本機已完成下面的部署,現有工控機無需重複安裝。下面給出完整步驟,方便在其他機器複用;首次部署會重啟 NoMachine,應先結束正在使用的遠端桌面會話。

1. 工控機:確認需要跟隨的 Wi-Fi 介面。

nmcli -f DEVICE,TYPE,STATE,CONNECTION device status
ip -br -4 addr

本機 Wi-Fi 為 wlp4s0,雷達網口為 enp3s0。後面的 Python 指令碼和 NetworkManager 事件指令碼中各有一處介面設定,其他機器應把兩處 wlp4s0 都換成實際 Wi-Fi 介面名。Wi-Fi 繼續使用 DHCP,無需固定 IP,也無需修改雷達網口。

2. 工控機:安裝讀取當前 IP 的同步程式。

建立檔案:

sudoedit /usr/local/sbin/nomachine-wifi-sync

填入下面的內容。指令碼中只指定介面名 INTERFACE,沒有寫死 Wi-Fi IP;PORT 對應本機 NoMachine 預設的 4000 埠。

#!/usr/bin/python3
"""Keep NoMachine bound to the robot's management Wi-Fi IPv4 address."""

import fcntl
import json
import os
from pathlib import Path
import re
import stat
import subprocess
import tempfile


INTERFACE = "wlp4s0"
CONFIG = Path("/usr/NX/etc/server.cfg")
PORT = 4000


def run(*args):
    return subprocess.check_output(args, text=True, timeout=10)


def replace_config(content):
    metadata = CONFIG.stat()
    temporary = None
    try:
        with tempfile.NamedTemporaryFile(mode="w", dir=CONFIG.parent, delete=False) as output:
            temporary = Path(output.name)
            output.write(content)
            output.flush()
            os.fchown(output.fileno(), metadata.st_uid, metadata.st_gid)
            os.fchmod(output.fileno(), stat.S_IMODE(metadata.st_mode))
            os.fsync(output.fileno())
        os.replace(temporary, CONFIG)
    finally:
        if temporary is not None and temporary.exists():
            temporary.unlink()


def main():
    with open("/run/nomachine-wifi-sync.lock", "w") as lock:
        fcntl.flock(lock, fcntl.LOCK_EX)
        interfaces = json.loads(run("/usr/sbin/ip", "-j", "-4", "address", "show", "dev", INTERFACE))
        addresses = [
            address["local"]
            for interface in interfaces
            for address in interface.get("addr_info", [])
            if address.get("scope") == "global" and address.get("valid_life_time") != 0
        ]
        if not addresses:
            print(f"{INTERFACE} has no IPv4 address; leaving configuration unchanged.", flush=True)
            return
        if len(addresses) != 1:
            raise RuntimeError(f"Expected one Wi-Fi IPv4 address; got {addresses!r}")
        address = addresses[0]
        old = CONFIG.read_text()
        updated, count = re.subn(
            r"(?m)^[ \t]*#?NXDListenAddress[^\S\n]+[^\n]*$",
            f'NXDListenAddress "{address}"',
            old,
        )
        if count != 1:
            raise RuntimeError(f"Expected one NXDListenAddress entry; got {count}")
        listeners = run("/usr/bin/ss", "-H", "-ltn", f"sport = :{PORT}")
        bound = any(line.split()[3] == f"{address}:{PORT}" for line in listeners.splitlines())
        if updated == old and bound:
            print(f"NoMachine already bound to {INTERFACE} {address}:{PORT}; no restart.", flush=True)
            return
        if updated != old:
            replace_config(updated)
        print(f"Synchronizing NoMachine to {INTERFACE} {address}:{PORT}.", flush=True)
        subprocess.run(["/etc/NX/nxserver", "--restart"], check=True, timeout=75)
        listeners = run("/usr/bin/ss", "-H", "-ltn", f"sport = :{PORT}")
        if not any(line.split()[3] == f"{address}:{PORT}" for line in listeners.splitlines()):
            raise RuntimeError(f"NoMachine is not listening on {address}:{PORT} after restart")
        print(f"Verified NoMachine listener {address}:{PORT}.", flush=True)


if __name__ == "__main__":
    main()

這個程式讀取介面當前的唯一全域性 IPv4,更新現有的 NXDListenAddress 配置項。當前地址和監聽都正確時直接退出;地址改變或監聽未恢復時,才重啟 NoMachine 並檢查結果。介面暫時沒有 IPv4 時不修改配置,存在多個 IPv4 時明確報錯,避免隨意選擇地址。

3. 工控機:設定開機同步和網路變化觸發。

建立一次性同步服務:

sudoedit /etc/systemd/system/nomachine-wifi-sync.service

填入:

[Unit]
Description=Bind NoMachine to the current management Wi-Fi IPv4 address
Wants=network-online.target
After=network-online.target nxserver.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/nomachine-wifi-sync
TimeoutStartSec=100

[Install]
WantedBy=multi-user.target

再建立 NetworkManager 事件指令碼:

sudoedit /etc/NetworkManager/dispatcher.d/90-nomachine-wifi

填入:

#!/bin/sh
# NetworkManager runs this as root after connection/address changes.
case "${1:-}:${2:-}" in
    wlp4s0:up|wlp4s0:dhcp4-change|wlp4s0:reapply)
        /usr/bin/systemctl --no-block start nomachine-wifi-sync.service
        ;;
esac

設定檔案所有者和執行許可權:

sudo chown root:root /usr/local/sbin/nomachine-wifi-sync /etc/systemd/system/nomachine-wifi-sync.service /etc/NetworkManager/dispatcher.d/90-nomachine-wifi
sudo chmod 755 /usr/local/sbin/nomachine-wifi-sync /etc/NetworkManager/dispatcher.d/90-nomachine-wifi
sudo chmod 644 /etc/systemd/system/nomachine-wifi-sync.service

這樣,Wi-Fi 連線、DHCPv4 租約變化或重新應用連線配置時,系統都會觸發同步。NetworkManager 官方事件說明

上述三個檔案是本次部署的輔助配置,不是 NoMachine 自帶功能;只修改 server.cfg、沒有安裝事件指令碼,地址就不會自動跟隨 DHCP。

4. 工控機:啟用同步,再恢復 NoMachine 自動發現。

先載入並啟用同步服務,讓程式根據當前 Wi-Fi 地址生成 NXDListenAddress:

sudo systemctl daemon-reload
sudo systemctl enable --now nomachine-wifi-sync.service

然後編輯 NoMachine 配置:

sudoedit /usr/NX/etc/server.cfg

將區域網自動發現配置項改為:

EnableLocalNetworkBroadcast 1

**NXDListenAddress 這一行交給同步程式維護,不手動填寫 .183。**首次開啟自動發現後,重啟 NoMachine,使該開關生效:

sudo /etc/NX/nxserver --restart

後續 IP 變化由同步服務自動處理,無需再手動修改配置或重啟;地址發生變化時的服務重啟會中斷原有 NoMachine 會話。

可以檢查程式生成的地址和日誌:

sudo grep '^NXDListenAddress' /usr/NX/etc/server.cfg
systemctl is-enabled nomachine-wifi-sync.service
sudo journalctl -u nomachine-wifi-sync.service -n 30 --no-pager

這裡看到的是程式最後一次同步的具體地址,應與 wlp4s0 當前 IPv4 一致,不代表給網絡卡設定了靜態 IP。服務是一次性任務,執行成功後顯示 inactive 屬於正常情況,應看日誌和 Result,不要求它一直處於 active。

需要手動重新同步時執行:

sudo systemctl start nomachine-wifi-sync.service

NoMachine 恢復自動發現後仍會使用 5353 UDP 埠。此時不能把“看到 nxserver.bin 監聽 5353”直接當作故障,應檢查它釋出的地址是否與當前 Wi-Fi IP 一致。

5. 電腦端:始終使用主機名連線。

avahi-browse -rt _nomachine._tcp
getent ahostsv4 ysu.local
ssh ysu@ysu.local

自動發現的 A 記錄和 TXT 中的 ip= 應等於工控機此時的 Wi-Fi IPv4,而不是永遠等於本次排查時的 .183。本次 NoMachine 的 SRV 記錄埠為 0,實際 NX 連線埠由 TXT 中的 service=nx:4000 給出,不能據此誤判服務沒有監聽。

手動建立 NoMachine 連線時使用主機名:

协议:NX
主机:ysu.local
端口:4000
用户名:ysu

電腦仍需與工控機處於能夠進行區域網發現和互訪的網路中。IP 變化時,由 Avahi 更新主機名記錄、同步程式更新 NoMachine 繫結地址,電腦端繼續使用 ysu.local。

1.1.5.完整修複驗證結果

檢查專案 結果
原生 NoMachine 自動發現 已恢復,能發現 ysu,A 記錄和 TXT 的 IP 均為 .183
Wi-Fi 抓包 Avahi 和 NoMachine 都回答 .183,該輪未再出現 .50
4000 TCP 埠 繫結 192.168.2.183:4000,電腦端實際 TCP 連線成功
SSH 主機名連線 ssh ysu@ysu.local hostname 密碼登入成功,返回 ysu
配置殘留舊 IP 的模擬 將儲存地址臨時改成 .254 後觸發 DHCP 變化處理,同步任務自動恢復 .183、重啟服務並驗證監聽成功
地址未變化的事件 同步任務直接退出,不重啟 NoMachine
開機同步 服務已啟用

完整修復前的配置備份為 /usr/NX/etc/server.cfg.before-wifi-binding-20261019。本次沒有改變工控機實際 Wi-Fi IP,也沒有做整機重啟或圖形桌面登入測試;模擬舊配置驗證了同步處理流程,真實換 IP 和完整圖形會話仍應在實際使用時確認。

1.2.ROS 2 遠端檢視 MID360 點雲導致降頻、延遲和 RViz 不顯示

1.2.1.原因

現象是:電腦通過 Wi-Fi 開啟 RViz,或者僅執行遠端 ros2 topic hz,工控機上的 MID360 點雲和 IMU 就明顯降頻,連工控機本地測頻也受到影響;關閉遠端訂閱後恢復。切換手機熱點和其他 Wi-Fi 都未直接解決問題。

本次排查發現了兩個獨立問題:

  1. 電腦端 DDS 公佈了不適合本次跨機通訊的地址。

    電腦端 DDS 同時公佈了 Wi-Fi 地址、Docker 網橋地址 172.17.0.1 和 Clash TUN 虛擬網絡卡地址 198.20.0.1。工控機根據這些地址傳送資料,其中發往 Clash TUN 地址的資料被路由到雷達網口的閘道器 192.168.1.1,鄰居解析未完成。實測部分 UDP 傳送呼叫阻塞約 250~330 毫秒,傳送阻塞拖慢釋出,點雲和 IMU 出現積壓。

    本次定位到的明顯阻塞與 Clash TUN 地址有關,沒有單獨驗證 Docker 地址是否造成同樣阻塞。不過,電腦 Docker 網橋地址也不適合作為本次跨機接收地址,應一併排除。選擇介面的原則是:只使用 ROS 對端實際可達、並且本次通訊確實需要的介面。

  2. 電腦端 UDP 接收緩衝不足以承受本次點雲突發流量。

    修正介面並重建電腦端 daemon 後,工控機已恢復點雲約 20 Hz、IMU 約 200 Hz,但電腦端點雲仍只有約 12.8 Hz,接收 socket 仍有緩衝丟包。此時預設接收緩衝約為 208 KiB;將請求值增大到 4 MiB 後,電腦端點雲恢復約 20 Hz,測試期間該 socket 的緩衝丟包計數為零。

    因此,緩衝丟包不能全部歸因於選錯網絡卡。大點雲的分片集中到達、接收執行緒短暫來不及處理時,正確介面也可能出現緩衝溢位。增大接收緩衝是按資料量選擇的最佳化;本次 MID360 接收環境有實測依據,建議保留 4 MiB。Fast DDS 大數據傳輸說明

ros2 topic hz 會訂閱完整訊息來統計頻率,同樣需要接收整幀點雲,因此僅開啟遠端測頻也可能觸發上述問題。ROS 2 Humble 測頻實現

點雲延遲到十幾秒甚至幾十秒後,點雲時間戳可能早於 RViz 儲存的 TF 歷史。即使 TF 樹連通、最新變換正常,RViz 仍無法完成點雲時間戳對應的座標變換,表現為不顯示點雲。本次將 Fixed Frame 臨時改為點雲自身的 front_mid360 後出現點雲,驗證了這個方向;解決傳輸積壓後,就可以使用正常的顯示座標系。

1.2.2.解決方法

1. 工控機:建議保留非同步釋出。

在實際執行 ROS 的使用者的 ~/.bashrc 中加入。

export RMW_FASTRTPS_PUBLICATION_MODE=ASYNCHRONOUS

修改後執行:

source ~/.bashrc

然後在載入了該環境的終端中重新啟動 MID360 驅動,使新程序繼承設定。

非同步釋出將實際傳送交給後臺執行緒,可以減輕網路傳送對驅動釋出執行緒的直接影響;單獨設定它未能解決本次問題,仍需配合下面的電腦端配置。Fast DDS 在 ROS 2 Humble 中的釋出模式說明

2. 電腦端:通過 source 載入工程內的 dds_env.bash。

先查詢自己網路網絡卡:

ip -br -4 addr

看ip就能看出來,只有10.xx.xx.xx和192.168.xx.xx這種才是NAT4下發的ip。 所以這個wlp0s20f3才是你和ROS用於區域網通訊的網絡卡。

也可以點選WIFI按鈕確認一下ip,和上方說的一模一樣:

當前指令碼位於工程根目錄:

/ysu2026_rm_sentry_nav/dds_env.bash

現在初始化當前終端後即可正常使用 rviz2 和 ros2 命令。

這份配置應首先載入到電腦端,因為接收地址由電腦 DDS 公佈,接收緩衝也屬於電腦上的接收程序。工控機使用電腦公佈的地址傳送點雲;增大工控機的接收緩衝不能替代電腦端設定。

指令碼讀取所選介面當前的 IPv4,生成 XML,並匯出 FASTRTPS_DEFAULT_PROFILES_FILE。UDP 使用所選地址和本機迴環地址 127.0.0.1,同時保留本機 SHM 通訊。XML 儲存在工程根目錄,檔名為 .dds-env-<用户ID>-<配置摘要>.xml,已加入 Git 忽略規則。Fast DDS 介面白名單

以下命令在電腦的 ROS 終端執行。兩端 ROS_DOMAIN_ID 必須一致,本次為 6;網絡卡名 wlp0s20f3 應按實際介面修改:

source /opt/ros/humble/setup.bash
export ROS_DOMAIN_ID=6

export DDS_INTERFACE=wlp0s20f3
export DDS_RECV_BUFFER=4MiB
source /你的存放工程的路径/ysu2026_rm_sentry_nav/dds_env.bash

比如:source /home/tungchiahui/UserFolder/MySource/ysu2026_rm_sentry/ysu2026_rm_sentry_nav/dds_env.bash

兩個引數可以分別配置:

引數 用法
DDS_INTERFACE 指定網絡卡名稱,支援逗號分隔的多個介面;未設定時自動選擇唯一處於 UP 狀態且有 IPv4 的物理網絡卡,多個候選時返回錯誤,要求明確選擇
DDS_RECV_BUFFER 不設定或 0 表示系統預設;純整數表示位元組;支援 B、KB、MB、GB、KiB、MiB、GiB 單位,數值須為非負整數

4MiB 等於 4194304 位元組,4MB 等於 4000000 位元組。緩衝引數是指令碼定義的選項,最終會轉換為位元組寫入 Fast DDS XML。本次接收 MID360 點雲建議使用已驗證有效的 4MiB。請求值還受系統 net.core.rmem_max 上限約束,超出上限時指令碼會提示,但不會自動修改系統引數。

指令碼成功載入時不列印,出錯或系統上限不足時給出提示。它僅配置 Fast DDS,不改變 ROS_DOMAIN_ID、訊息 QoS 或當前 shell 的選項。

我電腦容器使用 host 網路,可以訪問宿主 Wi-Fi 介面。其他 Docker 網路模式應選擇容器實際可見且對端可達的介面; 介面白名單不能替代 bridge 網路所需的 NAT 或發現配置。跨容器使用 SHM 還取決於 IPC 配置。(一勞永逸,直接host即可)

3. 首次啟用或切換網路後,重建電腦端 daemon。

先關閉舊的 RViz 和遠端測頻程序,在已載入上述 DDS 配置的終端執行:

ros2 daemon stop
ros2 daemon start

daemon 本身也會建立 DDS Participant,應繼承相同的介面配置,避免保留原先的虛擬網絡卡地址。本次排查中,重建 daemon 是恢復正常通訊的一步;不必每次測頻都重啟它。

4. 直接開啟 RViz 和測頻。

在已載入配置的電腦終端中開啟 RViz:

rviz2

另一個終端也先載入第 2 步相同的 ROS 和 DDS 環境,再執行:

ros2 topic hz /livox/lidar/pointcloud --window 40

--window 40 只是縮短統計視窗,方便觀察近期變化;真正起作用的是網絡卡白名單和接收緩衝設定。

5. 日常使用:可加入電腦 ROS 使用者的 .bashrc。

將以下三行放在已有 ROS 環境載入語句之後。當前電腦容器使用 root,對應 /root/.bashrc:

export DDS_INTERFACE=wlp0s20f3
export DDS_RECV_BUFFER=4MiB
source /你的存放工程的路径/ysu2026_rm_sentry_nav/dds_env.bash

比如:source /home/tungchiahui/UserFolder/MySource/ysu2026_rm_sentry/ysu2026_rm_sentry_nav/dds_env.bash

這樣,新開啟並讀取 .bashrc 的終端會自動初始化 DDS。通過服務或非互動方式啟動的 ROS 程式,需要在其啟動環境中主動載入配置。指令碼位於個人工程中,載入現有 /opt/ros/humble/setup.bash 不會修改 ROS 安裝檔案。

切換 Wi-Fi、改變所選介面或接收緩衝後,應重新 source 指令碼,重建 daemon,並重新啟動相關 ROS 程式;已經執行的程序不會自動讀取新的環境變數或 XML。指令碼本身不會重啟這些程序。

1.2.3.本次驗證結果

完成介面限制、daemon 重建和 4 MiB 接收緩衝配置後的現場遠端實測結果如下:

測量位置 點雲頻率 IMU 頻率
工控機本地 約 20 Hz 約 200 Hz
電腦端 約 20 Hz 約 197~199 Hz

放大緩衝後的測試節點接收緩衝丟包計數為零。開啟 RViz 後,工控機仍保持正常頻率,點雲持續顯示,延遲不再增長到幾十秒。這是本次測試期間的結果。

上表來自整理新指令碼之前的現場遠端測試。後續新增 dds_env.bash,在 ysu_ros_humble 中驗證了 XML 載入、接收緩衝生效、本機約 262 KB 點雲接收 20/20 幀,以及單位轉換、靜默載入和引數錯誤時保留原環境。新版指令碼的驗證範圍包含這些專案;上述遠端頻率仍引用此前現場實測。

配置整理在工程中新增了 DDS 環境指令碼和生成 XML 的 Git 忽略規則;本次排查未修改 MID360 驅動、SLAM 或控制節點的業務程式碼。

1.3.點雲和 TF 正常但二維地圖不更新

1.3.1.現象與排查入口

2026-10-20,工控機 ysu.local 在容器 ysu_rm_ros_humble 中進行實車建圖,啟動引數包含 slam:=True use_sim_time:=False。現場表現為點雲持續重新整理,odom 到底盤的 TF 和位姿輸出正常,但二維地圖沒有繼續生成。

當前二維建圖鏈路為:

Point-LIO → cloud_registered
    → loam_interface → registered_scan(odom系点云)
    → 地形分析 → terrain_map_ext
    → pointcloud_to_laserscan → obstacle_scan
    → slam_toolbox → /map

先檢查點雲有沒有變成二維掃描,再檢查掃描有沒有進入地圖。 點雲重新整理只說明三維處理鏈有輸出;ros2 node list 能看到轉換節點、ros2 topic list 能看到話題,也不代表該節點正在釋出有效資料。

以下檢查在工控機實際執行建圖的 ROS 容器內執行,先載入對應環境。工程路徑和 ROS_DOMAIN_ID 按現場修改,本次為:

source /opt/ros/humble/setup.bash
source /home/ysu/robomaster/ysu2026_rm_sentry/install/setup.bash
export ROS_DOMAIN_ID=6

# 分别观察二维转换的直接输入和输出,每项最多运行10秒。
timeout 10s ros2 topic hz /terrain_map_ext --window 40
timeout 10s ros2 topic hz /obstacle_scan --window 40

# 查看是否有发布端和订阅端;有连接仍需结合实际收帧判断。
ros2 topic info /obstacle_scan --verbose

上面的 timeout 到時結束屬於預期行為;在沒有訊息時,測頻命令可能一直沒有頻率輸出。本次已存在 slam_toolbox 訂閱,轉換節點仍然沒有輸出,不能解釋為“沒人訂閱,所以節點沒有接收點雲”。

1.3.2.現場證據

本次在工控機本地連續訂閱取樣 10 秒,結果如下:

話題 接收數量 說明
/cloud_registered 199 Point-LIO 點雲持續更新
/registered_scan 197 轉到 odom 系的點雲持續更新
/terrain_map 199 近場地形持續更新
/terrain_map_ext 195 二維轉換的直接輸入持續更新,約20 Hz
/odometry 196 位姿輸出持續更新
/obstacle_scan 0 二維轉換的輸出斷流
/map 20 仍在釋出舊地圖,訊息時間約落後405秒

/terrain_map_ext 最後一幀有 2700 個點,其中 1204 個點滿足配置的 intensity=[0.1,2.0] 門檻。這裡的 intensity 是地形節點計算的相對地面高差,單位為 m。該結果說明輸入並非全部被這項門檻篩掉,但其他幾何條件仍需單獨檢查;本次確定死鎖依靠下面的執行緒和鎖證據。

slam_toolbox 的引數服務正常返回,use_sim_time=false,scan_topic=obstacle_scan;轉換節點的引數請求在取樣期間沒有返回。這與轉換節點的主執行執行緒被阻塞相符。

單獨看到 /map 的時間戳較舊,不能直接斷言死鎖:機器人靜止或新掃描未達到入圖門檻時,地圖內容也可能不變。本次同時確認了輸入持續更新、輸出完全斷流、引數請求不返回,以及兩個執行緒互相等待鎖。

1.3.3.原因:轉換節點內部的 TF 等待死鎖

現場安裝版本為:

ros-humble-tf2             0.25.23-1jammy.20260907.213559
ros-humble-tf2-ros         0.25.23-1jammy.20260907.224752
ros-humble-tf2-sensor-msgs 0.25.23-1jammy.20260907.225802

轉換節點使用 tf2_ros::MessageFilter,等待把 odom 系地形點雲轉換到 base_footprint 所需的 TF。這個版本的非同步等待路徑存在鎖順序反轉:點雲處理執行緒先持有一把鎖、再等另一把鎖,TF 接收執行緒按相反順序持鎖,可能形成永久的相互等待。Humble 上游修復說明

本次轉換節點容器 PID 為 2902,抓取執行緒堆疊後立即脫離。兩條關鍵呼叫鏈為:

主线程2902:
pthread_mutex_lock(0x5dad8dbf3840)
  → tf2::BufferCore::addTransformableRequest
  → tf2_ros::Buffer::waitForTransform
  → tf2_ros::MessageFilter<PointCloud2>::add

TF接收线程3006:
pthread_mutex_lock(0x5dad8dbf38f0)
  → tf2_ros::Buffer的transform-ready回调
  → tf2::BufferCore::testTransformableRequests
  → tf2::BufferCore::setTransformImpl
  → tf2_ros::TransformListener::subscription_callback

讀取兩把鎖的所有者,得到:

鎖地址 持有該鎖的執行緒 等待該鎖的執行緒
0x5dad8dbf3840 3006 2902
0x5dad8dbf38f0 2902 3006

執行緒2902和3006各持有對方需要的鎖,已經形成死鎖。這裡的執行緒編號和地址只描述本次現場,重新啟動後會變化。

外部 TF 正常與這個死鎖可以同時存在。 每個轉換程序有自己的 TF Buffer 和處理執行緒;本次卡住的是二維轉換程序內部的等待路徑,其他節點仍能接收 TF、處理點雲和輸出位姿。轉換節點程序保持存在,卻停止處理新點雲,slam_toolbox 收不到新的 obstacle_scan,於是只能繼續釋出舊地圖。相同版本與呼叫鏈的上游報告 #992

1.3.4.修復版本與時間

Humble 已有對應修復:

日期 上游進展
2026-09-10 修復鎖順序反轉的補丁合入 Humble 分支,見PR #990
2026-09-15 tf2/tf2_ros 0.25.24 的版本記錄列出這項死鎖修復,見tf2版本記錄、tf2_ros版本記錄

2026-10-20 直接檢查工控機所配置的清華 ROS 軟體源線上索引,確認已有以下 amd64 包:

ros-humble-tf2     0.25.24-1jammy.20260915.203746
ros-humble-tf2-ros 0.25.24-1jammy.20260915.204410

但容器當時的 apt-cache policy 仍把 0.25.23 列為候選,本地 ROS 軟體源索引停在2026-10-02。因此需要先重新整理索引,再檢查候選版本;“上游有修復”“本地安裝了修復版”“執行中的程序已經載入修復版”是三個不同狀態。

1.3.5.處理方法

以下為修復操作步驟,本次診斷時尚未執行。

1. 在執行建圖的 ROS 容器內重新整理索引,核對候選版本。

以下以容器中的 root 使用者為例;普通使用者執行軟體包管理命令時需使用 sudo:

apt update
apt-cache policy ros-humble-tf2 ros-humble-tf2-ros

確認兩個包的 Candidate 均為 0.25.24 或包含該修復的後續 Humble 版本。如果仍為 0.25.23,先檢查索引重新整理是否成功及映象是否同步,再進行升級。

2. 結束本輪建圖,升級相關包並核對安裝結果。

apt install --only-upgrade ros-humble-tf2 ros-humble-tf2-ros
dpkg-query -W -f='${Package} ${Version}\n' ros-humble-tf2 ros-humble-tf2-ros

由包管理器處理依賴,檢查命令是否成功以及實際安裝版本。單獨重啟轉換節點有機會暫時恢復,但繼續使用 0.25.23 仍可能再次觸發死鎖。

3. 重新啟動建圖,檢查掃描與地圖恢復。

使用原來的實車建圖啟動方式重新執行,隨後檢查:

  • /terrain_map_ext 持續輸入,/obstacle_scan 恢復連續輸出,且掃描中存在有效距離值。
  • 轉換節點的引數請求能夠正常返回。
  • 在滿足掃描入圖條件的實際移動後,/map 內容繼續更新。不要只憑靜止時地圖不變判斷修復失敗。

升級磁碟上的庫不會解除舊程序已經形成的死鎖,必須重啟使用舊庫的建圖程序。修改高度、強度、TF容差或RViz顯示引數,也無法解除這次已經形成的鎖等待環。

1.3.6.本次驗證狀態

已完成: 現場話題取樣、引數服務檢查、執行緒堆疊和鎖所有者核對;確認二維轉換節點死鎖;核實上游修復版本及當前配置映象中修復包可用。

待完成: 容器軟體包升級、建圖重啟,以及升級後二維掃描與地圖恢復的實車驗證。截至2026-10-20本次記錄,尚未執行這些修復操作,不能將上游修復結論寫成已經完成本機恢復驗證。