Ubuntu + ROS2 + Meta Quest VR + 机器人 完整通信
整体四层架构(数据流总览)
- Meta Quest(Android):手柄位姿、手部追踪、按键、IMU → 向外发送控制指令
- Ubuntu 中间层(ROS2):接收 VR 数据、转换 ROS 话题、运动解算、下发机器人指令;同时回传机器人图像 / 状态给 VR 显示
- 机器人端(PC / 开发板,跑 ROS2):底盘 / 机械臂驱动、传感器、运动控制器
- 底层通信链路分两大类:
- VR ↔ Ubuntu PC:TCP/UDP/ROS-TCP 桥(主流),Quest 原生不支持 DDS
- Ubuntu PC ↔ 机器人:ROS2 原生 DDS 分布式通信(局域网 / 有线)
一、硬件网络前置条件(必须先配置)
1. 同一局域网(核心前提)
- Ubuntu 电脑、Meta Quest、机器人(Jetson / 工控机)全部连同一个路由器 5G Wi‑Fi,或交换机有线
- 查看 Ubuntu 本机 IP:
ip a,记下192.168.x.x - Quest 设置 → WLAN,查看 VR 内网 IP,PC ping VR IP 验证连通
ping 192.168.1.105 # 替换为QuestIP
2. Ubuntu 防火墙放行端口
# ROS-TCP默认10000,UDP常用8888
sudo ufw allow 10000/tcp
sudo ufw allow 8888/udp
sudo ufw reload
3. Meta Quest 开启开发者模式
Meta 手机 App → 设备 → 开发者模式开启;USB 连接电脑允许调试,SideQuest 可安装自研 Unity VR APP
二、模块 1:Ubuntu ↔ 机器人(ROS2 原生 DDS 通信,最简单)
机器人本身运行 ROS2(Jetson Nano/Orin、工控机 Ubuntu),和上位机 Ubuntu 跨机 DDS 互通,不需要额外桥接。
配置步骤
- 所有设备(Ubuntu 主机、机器人)终端统一环境变量
# 任意数字0~255,全部设备保持一致
export ROS_DOMAIN_ID=10
# 禁止仅本地回环
export ROS_LOCALHOST_ONLY=0
# 写入~/.bashrc永久生效
echo "export ROS_DOMAIN_ID=10" >> ~/.bashrc
source ~/.bashrc
- 验证通信
机器人端发布话题:
ros2 topic pub /robot/cmd_vel geometry_msgs/msg/Twist "{linear: {x:0.1}}"
Ubuntu 主机查看:
ros2 topic list、ros2 topic echo /robot/cmd_vel
能收到即成功
原理
ROS2 底层 DDS(FastDDS/CycloneDDS)基于 UDP 组播,同 Domain ID 自动发现所有节点; 机器人底盘、机械臂、激光雷达全部通过标准 Topic/Service/Action 交互,是整套系统的数据核心枢纽。
三、模块 2:Meta Quest ↔ Ubuntu ROS2(3 套主流通信方案,按需求选)
Quest 是 Android 系统,不原生支持 DDS/ROS2,必须通过桥接层中转 VR 数据到 ROS 话题。
方案 1:ROS-TCP-Endpoint(工业标准,推荐新手)
架构
Quest Unity APP(ROS#/ROS-TCP-Connector)→ TCP 长连接 → Ubuntu ros_tcp_endpoint ROS2 节点 → 生成标准 ROS 话题(手柄位姿、手部追踪、按键)
完整部署步骤
- Ubuntu 工作空间拉取桥接包
cd ~/colcon_ws/src
git clone -b main-ros2 https://github.com/ros2/ros_tcp_endpoint.git
cd .. && colcon build --packages-select ros_tcp_endpoint
source install/setup.bash
- 启动 TCP 服务端(Ubuntu)
# 默认端口10000,监听Quest连接
ros2 run ros_tcp_endpoint default_server_endpoint --ros-args -p server_ip:=0.0.0.0
- Unity 侧开发 Quest APP
- Unity 导入
ROS-TCP-Connector+ Oculus XR Interaction SDK - VR 脚本填写 Ubuntu 电脑局域网 IP、端口 10000
- 发布手柄 Pose、手部追踪、按键到 ROS 话题:
/vr/left_controller/vr/right_controller/vr/hands - 可双向通信:机器人图像 / TF 坐标通过 TCP 回传到 VR 渲染机器人模型、相机画面
- 现成免开发 APP:Quest2ROS 商店 /sidequest 直接安装,填入 Ubuntu IP 即可自动发布 VR 数据,无需写 Unity 代码
优缺点
双向通信、完美兼容所有 ROS 消息、支持 TF / 图像 / 点云、生态成熟
TCP 有轻微延迟(20~60ms),高速手部精细抓取场景延迟敏感
方案 2:原生 UDP Socket(低延迟,机械臂遥操首选)
架构
Quest Unity UDP 客户端(高频发包手柄 / 手部坐标)→ Ubuntu Python UDP 服务节点 → 封装成 ROS2 Topic 下发机器人
- Quest 侧:Unity C# 脚本每 10ms 发送二进制位姿包(端口 8888)
- Ubuntu 接收节点(Python)示例逻辑:
import socket, rclpy
from geometry_msgs.msg import PoseStamped
# UDP监听
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", 8888))
# 解析VR手柄坐标,发布/vr/right_pose话题给机械臂MoveIt2
优缺点
延迟极低(<15ms),适合高精度手控机械臂抓取
需要手动序列化 / 反序列化数据,无原生 ROS 消息支持,双向图像传输麻烦
方案 3:Websocket / Vuer(无需 Unity,浏览器 VR,快速原型)
架构
Ubuntu 启动robotis_vuer ROS 节点 → 本地 Web 服务 → Quest 浏览器打开网页 VR,手部追踪通过 Websocket 传 ROS 话题
适用场景
快速验证遥操作逻辑、无 Unity 开发环境;缺点画质、追踪精度低于原生 Unity APP
四、完整三层数据流示例(VR 遥控机械臂)
正向控制流(VR → 机器人)
- Meta Quest 3:XR 手部追踪读取左右手 6DOF 位姿、夹爪按键
- Unity APP → TCP 发送至 Ubuntu
ros_tcp_endpoint - Ubuntu 生成 ROS 话题
/vr/hand_target_pose(PoseStamped) - ROS2 MoveIt2 订阅该话题,计算机械臂运动轨迹
- MoveIt2 下发关节速度话题
/arm/joint_cmd - 机械臂 Jetson 开发板(同 DDS Domain)订阅关节指令,驱动电机运动
反向反馈流(机器人 → VR 视觉)
- 机器人相机节点发布
/camera/image_raw - Ubuntu TCP 桥转发图像流至 Quest Unity
- VR 眼镜内实时渲染机器人第一视角画面,实现沉浸式遥操
- 机器人 TF 坐标树通过 TCP 同步,VR 渲染真实尺寸机器人 3D 模型
五、三种通信方案对比选型表
| 方案 | 延迟 | 开发成本 | 双向图像 / TF | 适用场景 |
|---|---|---|---|---|
| ROS-TCP 桥 | 20~60ms | 中(Unity 简单集成) | 完整支持 | 移动机器人、通用遥操、SLAM 可视化 |
| UDP 原生 Socket | 8~20ms | 高(手动编解码) | 不适合大图 | 高精度机械臂、精细抓取 |
| Vuer 网页 VR | 50~120ms | 极低(无需 Unity) | 低清图像 | 快速 Demo、教学原型 |
六、常见排错关键点
- Quest 收不到 Ubuntu 数据
- 检查二者同 5G 局域网,关闭电脑防火墙,放行 10000/8888 端口
- Unity 内 IP 填Ubuntu 内网 IP,不要 127.0.0.1
- Ubuntu 看不到 VR ROS 话题
- 确认
ros_tcp_endpoint正常运行,Quest APP 已成功连接服务端 - QoS 匹配:VR 端消息可靠性设为 RELIABLE
- 确认
- Ubuntu 和机器人无 ROS 通信
- 全部设备
ROS_DOMAIN_ID完全一致,关闭本地仅回环 - 机器人 Wi‑Fi 切换 5G,避免 2.4G 组播丢包
- 全部设备
- VR 操控机械臂延迟高
- 切换 UDP 方案、路由器换 Wi‑Fi6、关闭 Quest 后台程序
- DDS 开启 CycloneDDS 优化单播传输,关闭组播冗余
七、两套完整代码:Unity ROS-TCP 发送 VR 手柄 + Ubuntu Python 接收转发 ROS2
- 通信链路:Meta Quest (Unity) ↔ Ubuntu (ros_tcp_endpoint) ↔ ROS2 机器人
- 环境准备
- Ubuntu 22.04 Humble,装好
ros_tcp_endpoint - Unity 导入包:
ROS-TCP-Connector+ Meta XR SDK - 所有设备同局域网,Ubuntu IP 举例:
192.168.1.100
- Ubuntu 22.04 Humble,装好
八、Unity C# 脚本(Quest 手柄位姿发布 ROS 话题)
挂载在 XR Origin / Right Controller 物体上
using UnityEngine;
using RosMessageTypes.Geometry;
using RosMessageTypes.Std;
using Unity.Robotics.ROSTCPConnector;
using UnityEngine.XR;
public class VRControllerPublisher : MonoBehaviour
{
// ROS TCP配置
private ROSConnection ros;
// 话题名
private const string controllerTopic = "/vr/right_controller_pose";
// 手柄输入设备
private InputDevice rightController;
void Start()
{
// 填入你的Ubuntu内网IP
ros = ROSConnection.GetOrCreateInstance();
ros.RosIPAddress = "192.168.1.100";
ros.RosPort = 10000;
ros.Connect();
// 获取右手柄设备
var devices = InputTracking.GetDevices();
foreach (var dev in devices)
{
if (dev.characteristics.HasFlag(InputDeviceCharacteristics.Controller)
&& dev.characteristics.HasFlag(InputDeviceCharacteristics.Right))
{
rightController = dev;
break;
}
}
}
void Update()
{
if (!rightController.isValid) return;
// 读取手柄6DOF位姿
rightController.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 pos);
rightController.TryGetFeatureValue(CommonUsages.deviceRotation, out Quaternion rot);
// 转换Unity坐标系 → ROS坐标系(Unity Y上,ROS Z上)
MposeStampedMsg poseMsg = new MposeStampedMsg();
poseMsg.header.frame_id = "vr_base";
poseMsg.header.stamp = ros.GetTime();
// 坐标转换修正
poseMsg.pose.position.x = pos.x;
poseMsg.pose.position.y = pos.z;
poseMsg.pose.position.z = pos.y;
poseMsg.pose.orientation.x = rot.x;
poseMsg.pose.orientation.y = rot.z;
poseMsg.pose.orientation.z = rot.y;
poseMsg.pose.orientation.w = rot.w;
// 发布位姿话题
ros.Publish(controllerTopic, poseMsg);
}
}
Unity 配套设置
- Package Manager 安装:
ROS-TCP-Connector、Meta XR All-in-One SDK - 场景添加空物体
ROSConnection - 脚本挂载到右手柄,修改代码内 IP 为你电脑 IP
- Build APK 侧载到 Meta Quest
九、Ubuntu Python ROS2 接收脚本(订阅 VR 手柄话题)
功能:接收手柄 Pose,可直接转换为机械臂目标位姿 / 小车速度指令 新建 vr_listener.py,放入你的 python 功能包
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import PoseStamped, Twist
class VRControllerListener(Node):
def __init__(self):
super().__init__("vr_controller_listener")
# 订阅VR手柄位姿
self.sub = self.create_subscription(
PoseStamped,
"/vr/right_controller_pose",
self.vr_callback,
10
)
# 发布小车速度指令示例
self.cmd_pub = self.create_publisher(Twist, "/cmd_vel", 10)
self.get_logger().info("VR手柄监听节点已启动")
def vr_callback(self, msg: PoseStamped):
"""收到VR手柄数据回调"""
x = msg.pose.position.x
y = msg.pose.position.y
z = msg.pose.position.z
self.get_logger().info(f"VR手柄坐标 x:{x:.2f}, y:{y:.2f}, z:{z:.2f}")
# 简单映射手柄前后左右 → 小车线速度角速度
twist = Twist()
twist.linear.x = x * 0.3
twist.angular.z = -y * 0.8
self.cmd_pub.publish(twist)
def main(args=None):
rclpy.init(args=args)
node = VRControllerListener()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
配置运行步骤
- 功能包 setup.py 添加入口
entry_points={
'console_scripts': [
'vr_listener = my_py_pkg.vr_listener:main',
],
},
- 编译运行
cd ~/colcon_ws
colcon build --packages-select my_py_pkg
source install/setup.bash
ros2 run my_py_pkg vr_listener
- 验证数据 新开终端查看 VR 话题:
ros2 topic echo /vr/right_controller_pose
十、低延迟 UDP 方案全套代码(机械臂高精度遥操)
1. Ubuntu UDP 服务端(接收 VR 二进制坐标,发布 ROS)
import socket
import struct
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import PoseStamped
UDP_PORT = 8888
UDP_BIND_IP = "0.0.0.0"
class UdpVrReceiver(Node):
def __init__(self):
super().__init__("udp_vr_receiver")
self.pub = self.create_publisher(PoseStamped, "/vr/udp_hand_pose", 10)
# 创建UDP套接字
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((UDP_BIND_IP, UDP_PORT))
self.sock.setblocking(False)
self.timer = self.create_timer(0.01, self.recv_loop)
self.get_logger().info(f"UDP监听 {UDP_BIND_IP}:{UDP_PORT}")
def recv_loop(self):
try:
data, addr = self.sock.recvfrom(1024)
# 解析6个float: px,py,pz,qx,qy,qz,qw
px, py, pz, qx, qy, qz, qw = struct.unpack("7f", data)
msg = PoseStamped()
msg.header.frame_id = "vr_base"
msg.pose.position.x = px
msg.pose.position.y = py
msg.pose.position.z = pz
msg.pose.orientation.x = qx
msg.pose.orientation.y = qy
msg.pose.orientation.z = qz
msg.pose.orientation.w = qw
self.pub.publish(msg)
except BlockingIOError:
return
def main(args=None):
rclpy.init(args=args)
node = UdpVrReceiver()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
2. Unity UDP 发送 C# 脚本
using UnityEngine;
using System.Net.Sockets;
using System.Net;
using UnityEngine.XR;
public class VrUdpSender : MonoBehaviour
{
private Socket udpSocket;
private IPEndPoint serverEp;
private InputDevice rightController;
byte[] sendBuf = new byte[28]; // 7个float *4字节
void Start()
{
// Ubuntu IP端口
string serverIp = "192.168.1.100";
int port = 8888;
udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
serverEp = new IPEndPoint(IPAddress.Parse(serverIp), port);
// 获取右手柄
var devs = InputTracking.GetDevices();
foreach (var d in devs)
{
if (d.characteristics.HasFlag(InputDeviceCharacteristics.Right | InputDeviceCharacteristics.Controller))
{
rightController = d;
break;
}
}
}
void Update()
{
if (!rightController.isValid) return;
rightController.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 pos);
rightController.TryGetFeatureValue(CommonUsages.deviceRotation, out Quaternion rot);
// 打包7个浮点数
System.BitConverter.GetBytes(pos.x).CopyTo(sendBuf, 0);
System.BitConverter.GetBytes(pos.z).CopyTo(sendBuf, 4);
System.BitConverter.GetBytes(pos.y).CopyTo(sendBuf, 8);
System.BitConverter.GetBytes(rot.x).CopyTo(sendBuf, 12);
System.BitConverter.GetBytes(rot.z).CopyTo(sendBuf, 16);
System.BitConverter.GetBytes(rot.y).CopyTo(sendBuf, 20);
System.BitConverter.GetBytes(rot.w).CopyTo(sendBuf, 24);
udpSocket.SendTo(sendBuf, serverEp);
}
}
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)