You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非交互式SSH执行ROS2 Launch文件失败问题求助

问题:Golang SSH远程执行ROS2 Launch命令无节点启动

我用Golang实现了SSH连接机器人的程序,ros2 topic list、ros2 topic info等命令都能正常执行并返回结果,但执行ROS2 Launch文件时没有输出,机器人本地用ros2 node list也检测不到启动的节点。

我的Golang SSH执行代码如下:

func (serverMsg *BaseWSServerMsg) executeArbitrarySSH(payload interface{}, c *websocket.Conn) {
    type arbSSH struct {
        Data     string `json:"data,omitempty"`
        Port     int    `json:"port,omitempty"`
        Username string `json:"username,omitempty"`
        Password string `json:"password,omitempty"`
        Hostname string `json:"hostname,omitempty"`
        Type     string `json:"type,omitempty"`
    }
    marshalled, err := json.Marshal(payload)
    if err != nil {
        serverMsg.CreateAsError(http.StatusBadRequest, err.Error())
        return
    }

    var load arbSSH
    err = json.Unmarshal(marshalled, &load)
    if err != nil {
        serverMsg.CreateAsError(http.StatusBadRequest, fmt.Sprintf(err.Error()))
        return
    }

    config := &ssh.ClientConfig{
        User: load.Username,
        Auth: []ssh.AuthMethod{
            ssh.Password(load.Password),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }
    client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", load.Hostname, load.Port), config)
    if err != nil {
        serverMsg.CreateAsError(http.StatusInternalServerError, err.Error())
        return
    }
    session, err := client.NewSession()
    if err != nil {
        serverMsg.CreateAsError(http.StatusInternalServerError, err.Error())
        return
    }
    if load.Type == "output" {
        fmt.Println("Recieved Output command")
        defer client.Close()
        defer session.Close()
        stdout, err := session.Output(load.Data)
        if err != nil {
            serverMsg.CreateAsError(http.StatusInternalServerError, err.Error())
            return
        }
        serverMsg.Payload = struct {
            Data string `json:"data,omitempty"`
        }{string(stdout)}
    }
}

由于每个命令都会创建新终端,我会在执行Launch命令前先source环境变量,示例命令:

export ROS_DOMAIN_ID=30 > /dev/null && source /opt/ros/humble/setup.bash > /dev/null && source ~/example_ws/install/local_setup.bash > /dev/null && ros2 launch example example.launch.py

这个命令在交互式SSH终端中完全正常,能启动节点并输出内容,但通过Golang执行就没效果。试过加&后台运行、用nohup都没用。

另外测试发现,直接用非交互式SSH执行上述命令,终端能显示ROS_INFO输出,但机器人本地ros2 node list和ros2 topic list还是检测不到节点,ROS_DOMAIN_ID是一致的。


原因分析

  1. 非交互式shell环境变量缺失:非交互式SSH会话不会自动加载~/.bashrc或~/.profile,即使手动source了setup.bash,ROS2节点发现所需的隐性环境变量(如CYCLONEDDS_URI、ROS_LOCALHOST_ONLY)可能未正确设置。
  2. SSH会话结束导致进程终止:Launch命令是长期运行的进程,SSH会话关闭时会给子进程发SIGHUP信号,即使加了&,进程仍会被终止。
  3. Golang session.Output()的阻塞特性:Output()会等待命令执行完成才返回,而Launch进程不会主动退出,导致会话一直阻塞,最终进程被强制终止。

解决方法

1. 完整加载环境变量

用bash -i启动交互式shell,自动加载~/.bashrc中的配置,确保所有ROS2环境变量生效:

bash -i -c 'export ROS_DOMAIN_ID=30 && source /opt/ros/humble/setup.bash && source ~/example_ws/install/local_setup.bash && ros2 launch example example.launch.py'

2. 让进程脱离SSH会话控制

用nohup+disown让进程后台运行并脱离会话组,避免被SIGHUP信号终止:

bash -i -c 'export ROS_DOMAIN_ID=30 && source /opt/ros/humble/setup.bash && source ~/example_ws/install/local_setup.bash && nohup ros2 launch example example.launch.py > ~/launch_log.out 2>&1 & disown'

也可以用screen托管进程(需机器人已安装screen):

bash -i -c 'screen -dmS ros_launch bash -c "export ROS_DOMAIN_ID=30 && source /opt/ros/humble/setup.bash && source ~/example_ws/install/local_setup.bash && ros2 launch example example.launch.py"'

3. 修改Golang代码适配长期运行命令

原代码用session.Output()会阻塞到进程结束,改用session.Run()执行后台命令,无需等待进程退出:

if load.Type == "output" {
    fmt.Println("Recieved Output command")
    defer client.Close()
    defer session.Close()

    // 替换为带交互式shell和后台启动的命令
    cmd := fmt.Sprintf("bash -i -c 'export ROS_DOMAIN_ID=30 && source /opt/ros/humble/setup.bash && source ~/example_ws/install/local_setup.bash && nohup ros2 launch example example.launch.py > ~/launch_log.out 2>&1 & disown'")
    
    err := session.Run(cmd)
    if err != nil {
        serverMsg.CreateAsError(http.StatusInternalServerError, err.Error())
        return
    }
    serverMsg.Payload = struct {
        Data string `json:"data,omitempty"`
    }{"Launch命令已执行,可查看~/launch_log.out获取日志"}
}

验证步骤

  1. 在机器人上执行ps aux | grep ros2,确认launch进程是否在运行
  2. 查看~/launch_log.out文件,检查ROS2 Launch的输出是否正常
  3. 再次执行ros2 node list,确认节点已被发现

内容的提问来源于stack exchange,提问作者user25009967

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 07:36:07