1.安装虚拟机和ubuntu
到官网下载VMware虚拟机,注意mac电脑是用VMware Fusion Pro
官网链接:VMware Fusion Pro: Now Available Free for Personal Use - VMware Fusion Blog
这是博主年糕~milo的提供的VMware Fusion Pro 13.6链接:13.6版本 -- 提取码: 8ete
下载ubuntu镜像,推荐ubuntu 22.04.1 版本,注意M系列的Mac电脑要下载arm架构的镜像
清华源仓库链接:https://mirrors.tuna.tsinghua.edu.cn/
https://mirrors.ustc.edu.cn/ubuntu-old-releases中科大旧链接(Mac电脑首选ubuntu-22.04-live-server-arm64.iso):https://mirrors.ustc.edu.cn/ubuntu-old-releases
虚拟机安装ubuntu步骤可参考教程:https://blog.csdn.net/davidson1471/article/details/146910376
16G内存可设置ubuntu的内存为8192Mb,磁盘建议设置至少40G
2.安装ros和gazebo
在ubuntu安装ROS2,推荐用鱼香ros大佬的脚本,一步到位~
参考教程:https://blog.csdn.net/weixin_55944949/article/details/140373710
本人在home创建Project文件夹,准备放不同的项目。
打开终端,在Project文件夹运行git clone https://github.com/osrf/gazebo和
git clone https://github.com/ros-simulation/gazebo_ros_pkgs
注意gazebo不直接支持 ARM64 架构,如mac,需要手动编译安装。
参考教程:https://blog.csdn.net/codecyborgs/article/details/142934546
本人将教程source ~/.gazebo_ros/install/setup.bash改为source ~/Project/gazebo_ros_pkgs/install/setup.bash


终端依次输入gazebo和ros2 launch gazebo_ros gazebo.launch.py验证是否成功安装gazebo和 gazebo_ros_pkgs并写入环境。(提示:ctrl+c可以中断进程)


3.启动VLA占位API服务
这里在宿主机(也可以暂时在虚拟机,但以后可能要放模型推理,虚拟机里不方便硬件加速)采用uv(用conda或python原生也可以)配置Python环境
uv虚拟环境采用uv pip install fastapi uvicorn opencv-python numpy安装对应库
conda虚拟环境或python原生用pip install fastapi uvicorn opencv-python numpy
代码实现如下:
import base64
from typing import Any, Dict
import cv2
import numpy as np
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn
app = FastAPI(title="VLA Server (stub)") # 初始化 FastAPI 应用,title可以随便起名
class ActRequest(BaseModel):
instruction: str
image_b64: str
timestamp: float | None = None
def decode_image(image_b64: str) -> np.ndarray:
# 1. 将Base64字符串解码为原始二进制图片数据
# 先把字符串转utf-8字节串,再用base64解码得到图片的二进制数据(如jpg/png的原始字节)
raw = base64.b64decode(image_b64.encode("utf-8"))
# 2. 将二进制数据转换成numpy的uint8数组(OpenCV处理图像的基础格式)
# uint8是因为图片像素值范围是0-255,刚好匹配无符号8位整数
buf = np.frombuffer(raw, dtype=np.uint8)
# 3. 用OpenCV解码numpy数组,得到彩色图像
# cv2.IMREAD_COLOR 表示读取彩色图像(会自动忽略透明通道)
img = cv2.imdecode(buf, cv2.IMREAD_COLOR)
# 4. 异常校验:如果解码失败(比如Base64字符串无效/损坏),抛出明确的错误
if img is None:
raise ValueError("cv2.imdecode failed")
# 5. 返回OpenCV可直接处理的图像数组(形状为[高度, 宽度, 3],3对应BGR三个通道)
return img
def rule_vla(instruction: str, image_bgr: np.ndarray) -> Dict[str, Any]:
"""
这是“占位 VLA”:保证 CPU 可跑、接口稳定。
以后把这个函数替换成真正的 VLA(OpenVLA/Octo)推理即可。
"""
t = (instruction or "").lower()
# 最简单:用指令关键字决定动作(能立刻看到机器人动)
if "stop" in t or "halt" in t:
return {"linear_x": 0.0, "angular_z": 0.0}
elif "left" in t:
return {"linear_x": 0.0, "angular_z": +0.8}
elif "right" in t:
return {"linear_x": 0.0, "angular_z": -0.8}
elif "back" in t:
return {"linear_x": -0.15, "angular_z": 0.0}
# 一个超简陋“看图避障”示例:画面越暗(靠近墙/阴影)就转一下
gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)# 灰度转换,BGR彩色图转为单通道灰度图,既简化计算,又能聚焦于 “亮度” 这一核心特征(灰度值直接对应像素的亮度)
center = gray[gray.shape[0] // 3 : 2 * gray.shape[0] // 3, gray.shape[1] // 3 : 2 * gray.shape[1] // 3]# 截取中心九宫格区域,避开图像边缘的干扰(比如边框、阴影、噪点),只分析核心区域的亮度,让结果更能代表图像的真实明暗
mean_intensity = float(np.mean(center))# 计算平均亮度
# 可以把这里替换成:目标检测 / 深度估计 / 真 VLA action
if mean_intensity < 60:
return {"linear_x": 0.0, "angular_z": 0.9}
return {"linear_x": 0.20, "angular_z": 0.0}
@app.post("/act")
def act(req: ActRequest):
img = decode_image(req.image_b64) # 解码图片
action = rule_vla(req.instruction, img) # 根据指令处理图像后,返回具体的 “动作”结果
return {"action": action, "model": "rule_vla_stub"} # 返回接口响应
if __name__ == "__main__":
# 启动 Uvicorn 服务器
# app: 要运行的 FastAPI 应用实例
# host="0.0.0.0": 监听所有网络接口,允许外部访问
# port=8000: 监听 8000 端口,若被占用可改端口号,但改后注意之后的代码对应的端口号也要改
uvicorn.run(app, host="0.0.0.0", port=8000)
在对应目录运行uv run vla_server.py(conda虚拟环境或python原生用python vla_server.py)启动
4.搭建 TurtleBot3 机器人仿真环境
在Project创建turtlebot3_ws,并在其中创建src
通过cd ~/Project/turtlebot3_ws/src进入src目录
执行git clone -b humble https://github.com/ROBOTIS-GIT/turtlebot3_simulations.git安装仿真包
执行git clone -b humble https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git克隆 turtlebot3_msgs(核心消息包)
执行git clone -b humble https://github.com/ROBOTIS-GIT/turtlebot3.git克隆 turtlebot3(包含 turtlebot3_bringup、robot_state_publisher 等)
回到工作空间turtlebot3_ws目录进行编译cd ~/Project/turtlebot3_ws && colcon build --symlink-install,将克隆的源码变成 ROS 2 能识别的功能包
colcon build(编译)的核心作用
-
必须在工作空间根目录执行:colcon 是 ROS 2 的编译工具,它会默认扫描当前目录下的
src文件夹,识别里面的功能包(比如turtlebot3_gazebo),然后把源码编译成可执行文件、库文件,同时生成install目录(相当于软件的 “安装目录”)。-
如果在
src目录下执行colcon build,编译工具会找不到 ROS 2 工作空间的规范结构,直接报错。
-
-
--symlink-install是实用优化:它会给编译后的文件创建 “软链接”,后续你修改源码(比如改 launch 文件)时,不用重新编译,只需要重新 source 就能生效,节省开发时间。
执行source install/setup.bash注册路径,告知终端刚编译的仿真包的位置,把当前工作空间的功能包、launch 文件、可执行文件路径,添加到 ROS 2 的搜索环境变量中
执行export TURTLEBOT3_MODEL=waffle_pi告知 ROS 2 要使用的机器人型号是 waffle_pi(有相机)
执行ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py启动仿真环境(包含 Gazebo、机器人模型、驱动节点等)
gazebo中黑色圆点是 TurtleBot3 Waffle Pi 机器人,白色圆点是障碍物,绿色小六边形和黑边连接的六边形区域是 turtlebot3_world 场景的物理边界,限制机器人的移动范围,蓝色线是激光雷达的扫描射线

放大后可以看到圆柱障碍物之间的机器人的相机采集结果,即机器人视角下看到的场景内容

再放大,就可以看清机器人的外观,圆形的上方是相机

确认模型加载成功后,另开一个终端,需要确认话题。
执行ros2 topic list | grep camera ros2 topic,得到下面信息,可以验证相机话题通信正常
li@li:~$ ros2 topic list | grep camera
/camera/camera_info
/camera/image_raw
执行ros2 topic echo /camera/image_raw --once,得到下面信息,能够验证相机话题是否在发布数据

5.搭建 ROS 2 ↔ VLA 控制桥接包
确认话题后,准备搭建 ROS 2 ↔ VLA 控制桥接包,打通链路:ROS2 相机/指令(Topic) →(桥接:转换/打包)→ VLA(HTTP /act) →(桥接:解析/翻译)→ ROS2 /cmd_vel(Topic)
桥接(bridge)是“翻译 + 转发”的中间人,把两个本来不直接兼容的系统接起来:
-
ROS2 这边说的是 Topic 消息:
相机/camera/image_raw(Image)、指令/vla/instruction(String) -
VLA 服务这边说的是 HTTP 接口:
你要把图像和指令打包成 HTTP 请求发给它,它回你一个动作结果 -
机器人底盘需要的是 速度指令 Topic:
/cmd_vel(Twist)
换句话说,这里就是把 ROS2 的数据转成 VLA 能懂的请求,再把 VLA 的输出翻译回 ROS2 能用的控制指令。
执行mkdir -p ~/Project/vla_ws/src创建VLA工作空间及其src源码文件夹
执行cd ~/Project/vla_ws/src进入src文件夹
执行ros2 pkg create vla_bridge --build-type ament_python --dependencies rclpy sensor_msgs geometry_msgs std_msgs,
生成一个标准 ROS2 Python 包骨架,包括:
-
package.xml:包的元信息与依赖声明 -
setup.py / setup.cfg:Python 打包/安装入口 -
resource/、vla_bridge/等结构 -
并声明它依赖:
-
rclpy(写 ROS2 节点用) -
sensor_msgs(相机 Image 消息类型) -
geometry_msgs(Twist,即/cmd_vel的常用类型) -
std_msgs(String,用来发文本指令)
-
执行sudo apt-get install -y python3-opencv python3-requests ros-humble-cv-bridge安装系统依赖
把 ~/Project/vla_ws/src/vla_bridge/setup.py 改成(保留原来的元信息也行,关键是 entry_points),注册可执行入口,告诉 ROS2/ament 这个包里有哪些可运行的程序
from setuptools import setup
package_name = "vla_bridge"
setup(
name=package_name,
version="0.0.1",
packages=[package_name],
data_files=[
("share/ament_index/resource_index/packages", ["resource/" + package_name]),
("share/" + package_name, ["package.xml"]),
],
install_requires=["setuptools", "requests"],
zip_safe=True,
maintainer="you",
maintainer_email="you@example.com",
description="ROS2 bridge: camera+instruction -> VLA /act -> cmd_vel",
license="MIT",
tests_require=["pytest"],
entry_points={
"console_scripts": [
"vla_agent = vla_bridge.vla_agent_node:main",
"send_instruction = vla_bridge.instruction_publisher:main",
],
},
)
作用是编译安装后能得到两个命令(本质是 Python 入口):
-
启动桥接节点:订阅相机/指令 → 调 HTTP → 发布 cmd_velros2 run vla_bridge vla_agent -
发送一条文本指令到某个 ROS topic(默认ros2 run vla_bridge send_instruction "xxx"/vla/instruction)
编写 ROS 节点 vla_agent_node.py作为桥接的主体,保存到 ~/Project/vla_ws/src/vla_bridge/vla_bridge目录中
import base64
import time
from typing import Optional
import cv2
import requests
# 导入ROS 2 Python核心库:创建节点、处理ROS通信
import rclpy
from rclpy.node import Node
# 导入ROS 2消息类型:传感器图像、字符串、运动指令
from sensor_msgs.msg import Image # 相机图像消息
from std_msgs.msg import String # 文本指令消息
from geometry_msgs.msg import Twist # 机器人运动速度指令
# 导入ROS-OpenCV桥接库:实现ROS Image和OpenCV Mat格式互转
from cv_bridge import CvBridge
def cv_to_jpeg_b64(cv_img) -> str:
"""
将OpenCV格式的图像编码为JPEG格式,并转换为Base64字符串(HTTP传输友好)
Args:
cv_img: OpenCV格式的图像(numpy.ndarray),BGR通道
Returns:
str: 编码后的Base64字符串(UTF-8编码)
Raises:
RuntimeError: 图像编码失败时抛出异常
"""
# cv2.imencode:将OpenCV图像编码为JPEG格式的二进制缓冲区
# [int(cv2.IMWRITE_JPEG_QUALITY), 80]:设置JPEG质量为80(平衡体积和画质)
ok, buf = cv2.imencode(".jpg", cv_img, [int(cv2.IMWRITE_JPEG_QUALITY), 80])
if not ok:
raise RuntimeError("cv2.imencode failed")
# 1. buf.tobytes():将numpy缓冲区转为二进制字节串
# 2. base64.b64encode():对二进制数据进行Base64编码
# 3. decode("utf-8"):将Base64二进制编码转为UTF-8字符串(方便JSON传输)
return base64.b64encode(buf.tobytes()).decode("utf-8")
class VLABridgeNode(Node):
"""
VLA桥接节点:实现ROS 2与VLA服务器的通信桥接,核心功能:
1. 订阅ROS话题:
- camera_topic (sensor_msgs/Image):机器人相机原始图像
- instruction_topic (std_msgs/String):控制指令(如"前进"、"左转")
2. 向VLA服务器发起HTTP POST请求:{vla_url}/act(携带指令+Base64编码的图像)
3. 发布ROS话题:
- cmd_vel_topic (geometry_msgs/Twist):机器人运动速度指令(来自VLA服务器返回)
"""
def __init__(self):
super().__init__("vla_bridge_node") # 初始化ROS 2节点,节点名称为"vla_bridge_node"(标识)
# ---- 声明ROS 2参数(支持运行时通过--ros-args -p 覆盖,提高灵活性)----
self.declare_parameter("camera_topic", "/camera/image_raw") # 相机图像话题名(默认:/camera/image_raw)
self.declare_parameter("instruction_topic", "/vla/instruction") # 控制指令话题名(默认:/vla/instruction)
self.declare_parameter("cmd_vel_topic", "/cmd_vel") # 机器人速度指令发布话题名(默认:/cmd_vel)
self.declare_parameter("vla_url", "http://127.0.0.1:8000") # VLA服务器地址(默认:http://127.0.0.1:8000)若VLA服务器是宿主机必须用宿主机局域网 IP
self.declare_parameter("rate_hz", 2.0) # 定时器频率(默认2Hz:每秒向VLA服务器请求2次)
self.declare_parameter("http_timeout_s", 2.0) # HTTP请求超时时间(默认2秒:防止请求卡住)
self.declare_parameter("stop_when_no_instruction", True) # 无指令时是否停止机器人(默认True:安全保护)
# 读取参数值并赋值给实例变量(方便后续调用)
self.camera_topic = self.get_parameter("camera_topic").value
self.instruction_topic = self.get_parameter("instruction_topic").value
self.cmd_vel_topic = self.get_parameter("cmd_vel_topic").value
self.vla_url = self.get_parameter("vla_url").value.rstrip("/")
self.rate_hz = float(self.get_parameter("rate_hz").value)
self.http_timeout_s = float(self.get_parameter("http_timeout_s").value)
self.stop_when_no_instruction = bool(self.get_parameter("stop_when_no_instruction").value)
self.bridge = CvBridge() # 初始化CvBridge:用于ROS Image <-> OpenCV Mat格式转换
self.latest_img: Optional[Image] = None # 存储最新的相机图像(初始为None:表示还未接收到图像)
self.latest_instruction: str = "" # 存储最新的控制指令(初始为空字符串)
# ---- 创建ROS 2订阅器 ----
# 订阅相机图像话题,回调函数on_image,队列大小10(缓存最多10条消息)
self.sub_img = self.create_subscription(Image, self.camera_topic, self.on_image, 10)
# 订阅控制指令话题,回调函数on_instruction,队列大小10
self.sub_inst = self.create_subscription(String, self.instruction_topic, self.on_instruction, 10)
# ---- 创建ROS 2发布器 ----
self.pub_cmd = self.create_publisher(Twist, self.cmd_vel_topic, 10) # 发布机器人速度指令,队列大小10
period = 1.0 / max(self.rate_hz, 0.1) # 计算定时器周期(秒),max避免rate_hz为0导致除零错误
self.timer = self.create_timer(period, self.tick) # 创建定时器:每隔period秒执行一次tick回调函数(核心业务逻辑)
# 打印节点初始化信息(方便调试,确认参数是否正确加载)
self.get_logger().info(f"camera_topic={self.camera_topic}")
self.get_logger().info(f"instruction_topic={self.instruction_topic}")
self.get_logger().info(f"cmd_vel_topic={self.cmd_vel_topic}")
self.get_logger().info(f"vla_url={self.vla_url} (POST {self.vla_url}/act)")
def on_image(self, msg: Image):
"""
相机图像话题回调函数:接收到新图像时,更新最新图像缓存
Args:
msg: ROS 2 sensor_msgs/Image类型的消息(相机原始图像)
"""
self.latest_img = msg
def on_instruction(self, msg: String):
"""
控制指令话题回调函数:接收到新指令时,更新最新指令缓存
Args:
msg: ROS 2 std_msgs/String类型的消息(控制指令文本)
"""
self.latest_instruction = (msg.data or "").strip() # strip():去除指令前后的空格/换行符,避免无效指令
self.get_logger().info(f"Instruction updated: {self.latest_instruction!r}") # 打印指令更新日志(方便调试,确认指令是否正确接收)
def publish_stop(self):
"""发布停止指令:让机器人线性速度和角速度都为0(紧急停止/无指令时使用)"""
tw = Twist() # 创建空的Twist消息
tw.linear.x = 0.0 # 线性速度(前进/后退)为0
tw.angular.z = 0.0 # 角速度(左转/右转)为0
self.pub_cmd.publish(tw) # 发布停止指令
# self.get_logger().info("🛑 发布停止指令")
def tick(self):
"""
定时器回调函数(核心业务逻辑):
1. 检查指令/图像是否有效
2. 图像格式转换(ROS→OpenCV→Base64)
3. 向VLA服务器发送请求(指令+Base64图像)
4. 解析服务器响应,发布运动指令
"""
# 1. 无控制指令时的处理
if not self.latest_instruction:
if self.stop_when_no_instruction:
self.publish_stop() # 无指令则停止机器人
return # 直接返回,不执行后续逻辑
# 2. 无相机图像时的处理
if self.latest_img is None:
self.get_logger().warn("⚠️ 未接收到相机图像,跳过本次请求")
return
# 3. ROS Image → OpenCV Mat 格式转换
try:
# imgmsg_to_cv2:将ROS图像转为OpenCV格式(bgr8:OpenCV默认的BGR通道)
cv_img = self.bridge.imgmsg_to_cv2(self.latest_img, desired_encoding="bgr8")
except Exception as e:
self.get_logger().warn(f"❌ 图像格式转换失败: {e}")
return
# 4. OpenCV图像 → Base64字符串 编码
try:
image_b64 = cv_to_jpeg_b64(cv_img)
except Exception as e:
self.get_logger().warn(f"❌ 图像Base64编码失败: {e}")
return
# 5. 构造向VLA服务器发送的请求体(JSON格式)
payload = {
"instruction": self.latest_instruction, # 控制指令
"image_b64": image_b64, # Base64编码的图像
"timestamp": time.time(), # 请求时间戳(服务器可用于时序校验)
}
# 6. 向VLA服务器发起POST请求
try:
resp = requests.post(
f"{self.vla_url}/act", # 请求地址
json=payload, # 请求体(自动序列化为JSON)
timeout=self.http_timeout_s, # 请求超时时间
)
resp.raise_for_status() # 如果HTTP状态码不是200(如404/500),抛出异常
data = resp.json() # 解析服务器返回的JSON响应
except Exception as e:
self.get_logger().warn(f"❌ VLA服务器请求失败: {e}")
return
# 7. 解析服务器响应(预期格式:{"action": {"linear_x": float, "angular_z": float}}
action = data.get("action", {}) # 提取action字段,无则返回空字典
lin = float(action.get("linear_x", 0.0)) # 提取线性速度(默认0.0,避免KeyError)
ang = float(action.get("angular_z", 0.0)) # 提取角速度(默认0.0,避免KeyError)
# 8. 构造并发布机器人速度指令
tw = Twist()
tw.linear.x = lin # 设置线性速度(正数前进,负数后退)
tw.angular.z = ang # 设置角速度(正数左转,负数右转)
self.pub_cmd.publish(tw)
self.get_logger().info(f"🚀 发布运动指令: 线性速度={lin:.2f} m/s, 角速度={ang:.2f} rad/s")
def main():
"""ROS 2节点主函数:标准启动流程"""
rclpy.init() # 初始化ROS 2上下文
node = VLABridgeNode() # 创建VLA桥接节点实例
try:
rclpy.spin(node) # 自旋节点:持续处理回调函数(订阅/定时器),阻塞直到节点关闭
finally:
node.destroy_node() # 确保节点正常销毁(释放资源)
rclpy.shutdown() # 关闭ROS 2上下文
if __name__ == "__main__":
main()
这一步是在实现一个 ROS2 节点,它做三类事(结构层面):
-
订阅(Subscribe)
-
相机 topic:
/camera/image_raw(Image) -
指令 topic:
/vla/instruction(String)
-
-
调用(Call)
-
通过 HTTP POST 调VLA 服务:
{vla_url}/act
-
-
发布(Publish)
-
速度指令 topic:
/cmd_vel(Twist)
-
代码核心逻辑:ROS 2 数据采集 → 格式转换 → 网络请求 → 响应解析 → ROS 2 指令发布,实现 ROS 2 与 VLA 服务器的桥接。
编写一个最小指令注入器instruction_publisher.py,同样也保存到 ~/Project/vla_ws/src/vla_bridge/vla_bridge目录中,作用是把命令行传参发布字符串指令到指定 ROS 话题,无参数时默认发布forward
import sys
# 导入ROS 2 Python核心库:初始化ROS上下文、创建节点
import rclpy
from rclpy.node import Node
# 导入ROS 2标准字符串消息类型:用于发布文本指令
from std_msgs.msg import String
class InstructionPublisher(Node):
"""
ROS 2指令发布节点:
核心功能:向指定的ROS话题发布字符串类型的控制指令(如"forward"、"turn_left")
支持通过ROS参数自定义发布的话题名,默认发布到 /vla/instruction
"""
def __init__(self):
super().__init__("instruction_publisher")# 初始化ROS 2节点,节点名称为"instruction_publisher"(标识)
# 声明ROS参数:自定义发布的话题名(默认值为/vla/instruction)
# 支持运行时通过 --ros-args -p topic:=/xxx 覆盖默认值
self.declare_parameter("topic", "/vla/instruction")
# 读取参数值并赋值给实例变量,后续发布器将使用该话题名
self.topic = self.get_parameter("topic").value
# 创建ROS 2发布器:
# 消息类型:String(std_msgs/String)
# 话题名:self.topic(由参数指定)
# 队列大小:10(缓存最多10条待发布的消息,避免消息丢失)
self.pub = self.create_publisher(String, self.topic, 10)
# 打印初始化日志,确认发布器创建成功
self.get_logger().info(f"✅ 指令发布节点初始化完成,发布话题:{self.topic}")
def send(self, text: str):
"""
发布指定的文本指令到目标ROS话题
Args:
text: 要发布的指令文本(如"forward"、"stop"、"turn_right")
"""
msg = String() # 创建空的String消息对象(ROS 2消息必须通过对应类实例化)
msg.data = text # 给消息的data字段赋值(String消息的核心内容)
self.pub.publish(msg) # 发布消息到目标话题
self.get_logger().info(f"📤 已发布指令到 {self.topic}:{text!r}") # 打印日志,确认消息发布成功
def main():
"""
节点主函数:
1. 初始化ROS 2上下文
2. 创建指令发布节点
3. 处理命令行参数(获取要发布的指令,无参数则默认发"forward")
4. 发布指令并确保消息发送完成
5. 销毁节点并关闭ROS 2上下文
"""
rclpy.init()# 初始化ROS 2上下文(必须第一步执行,否则无法创建节点/发布消息)
node = InstructionPublisher()# 创建指令发布节点实例
try:
# 处理命令行参数:
# sys.argv[0] 是脚本本身的路径/名称,sys.argv[1:] 是传入的所有参数
# " ".join(...) 将参数列表拼接成字符串(支持带空格的指令,如"move forward")
# strip() 去除首尾空格,避免空指令
text = " ".join(sys.argv[1:]).strip()
# 如果命令行未传入任何参数,使用默认指令"forward"
if not text:
text = "forward"
node.send(text)# 调用send方法发布指令
# 关键:spin_once让节点自旋0.2秒,确保消息被ROS 2的通信层发送出去
# (如果直接销毁节点,可能消息还没发出去就终止了,导致接收端收不到)
rclpy.spin_once(node, timeout_sec=0.2)
finally:
node.destroy_node()# 无论是否出现异常,都确保节点被销毁(释放ROS资源)
rclpy.shutdown()# 关闭ROS 2上下文(必须最后执行,清理资源)
用 colcon 安装到 ROS2 环境里,使 ros2 run 直接启动
cd ~/Project/vla_ws
colcon build --symlink-install
source ~/Project/vla_ws/install/setup.bash
开始桥接ROS 2 与 VLA 服务器
source ~/Project/vla_ws/install/setup.bash
ros2 run vla_bridge vla_agent
若在宿主机上启动 VLA 桥接代理节点,则用ros2 run vla_bridge vla_agent --ros-args -p vla_url:="http://宿主机所局域网ip:8000"
Windows系统通过命令ipconfig查ip,在Linux/macOS 系统通过ifconfig或得到ip

开新终端,发布指令,控制机器人移动
source ~/Project/vla_ws/install/setup.bash
ros2 run vla_bridge send_instruction "forward"
# 试试:
# ros2 run vla_bridge send_instruction "left"
# ros2 run vla_bridge send_instruction "right"
# ros2 run vla_bridge send_instruction "stop"

创作不易,禁止抄袭,转载请附上原文链接及标题

1613

被折叠的 条评论
为什么被折叠?



