跳到正文

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本次記錄,尚未執行這些修復操作,不能將上游修復結論寫成已經完成本機恢復驗證。