ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

虚拟机ROS与Windows下ABB RobotStudio通信:从网络配置到RWS接口实战

虚拟机ROS与Windows下ABB RobotStudio通信:从网络配置到RWS接口实战 有段时间实验室的哥们儿跑来找我说他在MoveIt里把一条轨迹规划得漂漂亮亮但老板要求他在ABB工业臂上验证一下。真机在产线上排期想碰一下得等生产结束时间窗口短得可怜。他的解决办法是用ABB官方仿真软件RobotStudio先做验证可问题随之而来MoveIt跑在Ubuntu的ROS环境里RobotStudio跑在Windows10上两个系统之间怎么打通这就引出了这篇的主题——在虚拟机Ubuntu中搭建ROS环境与Windows10中的ABB Robot Studio建立稳定的通信连接。这类需求在机器人算法验证、离线编程、数字孪生场景里越来越常见。把虚拟机里的ROS和宿主机上的RobotStudio连起来本质上就是打通两个世界一边是ROS生态里丰富的运动规划、感知、仿真工具另一边是ABB工业机器人的官方虚拟控制器。本文会从网络规划、RobotStudio侧配置、ROS侧节点实现到故障排查完整走一遍这条路。适合正在做ABB机器人仿真联动、想在低成本环境里验证ROS控制算法的人参考也适合刚接触RWS接口的开发者当作入门笔记。1. 为什么要把虚拟机里的ROS和RobotStudio连起来一个被反复问到的需求1.1 仿真验证场景正在成为刚需先别急着看技术细节想清楚“为什么要连”比“怎么连”更重要。工业机器人项目里最贵的资源不是软件而是产线上的真机时间。真机一旦投入生产用来做算法验证的时间窗口非常有限有时候一周能给你两个小时就不错了。而且直接拿真机调试运动规划算法风险也不小万一轨迹有问题轻则报警停机重则撞工具撞夹具。RobotStudio的价值就在这里它里面跑的虚拟控制器和真机控制器使用同源代码RAPID程序、运动学模型、IO逻辑在虚拟环境里跑跟在真机上的行为高度一致。把ROS里的规划结果发到虚拟控制器上执行可以提前暴露大量问题等真机时间窗口来临时直接切换就能上手。所以“虚拟机Ubuntu中ROS与Windows10中ABB Robot Studio通信连接”不是炫技是真实工程里的省钱方案。用一个虚拟机加一套仿真软件就能把原本只能在产线旁做的实验搬到办公桌上。1.2 三层架构控制算法层、通信链路层、仿真执行层这套系统的整体结构并不复杂拆开看就三层层级作用运行位置关键组件控制算法层运动规划、状态监听Ubuntu虚拟机ROS、MoveIt、通信节点通信链路层数据交换与指令下发Windows宿主机80端口RWS HTTP接口仿真执行层运动学执行与状态反馈RobotStudio工作站虚拟控制器、RAPID任务ROS节点在虚拟机里运行通过HTTP请求访问Windows宿主机上的RobotStudio RWS服务RWS再与虚拟控制器通信。虚拟控制器执行运动后状态数据原路返回ROS节点拿到数据再发布到话题上。整个过程就像你在浏览器里访问一个网站浏览器是ROS节点网站服务器是RWS网站背后干活的系统是虚拟控制器。第一版实现里我用一个Python写的ROS节点轮询RWS接口拿关节角同时监听目标话题收到目标值就调用RWS写接口把关节角发给虚拟控制器。这套架构简洁直接后续加MoveIt联动也只是在算法层多接一个节点。1.3 技术选型为什么是RWS而不是PC SDK或Ethernet/IPABB RobotStudio对外提供好几种通信方式最容易混淆的是RWS、PC SDK和Ethernet/IP。我最终选了RWS理由很现实。PC SDK功能很强大能操作RAPID程序、文件系统、IO信号但它是一套.NET库原生跑在Windows上。ROS跑在Linux虚拟机里要跨进程调.NET库得在Windows侧写一个中间服务把PC SDK封装成网络接口相当于自己造一个RWS。既然ABB已经给了现成的RWS没必要重复造轮子。Ethernet/IP是工业现场总线协议主要用来和PLC通信走的是隐式报文和显式报文那套机制。ROS里想直接发Ethernet/IP包不是不行但需要额外引入协议栈而且配置IO和EDS文件相当繁琐属于把简单问题复杂化。RWS是ABB基于HTTP的RESTful接口跨平台、协议直观、调试方便。用Python的requests库就能直接调返回XML或JSON都好解析。打个比方PC SDK像是给你一把能开所有门的钥匙但你得自己走到门前RWS则像是给每扇门装了一个门铃你按一下门铃对方把门打开简单直接。2. 网络是一切通信的生命线虚拟机的联网模式选择与IP规划2.1 三种网络模式对比通信连接网络是地基。虚拟机如果只有一个孤立的网络环境后面所有HTTP请求都发不出去。VMware里常见的虚拟机网络模式有三种我一开始在这个问题上吃了不少亏先把对比列出来。网络模式虚拟机是否有独立IP宿主机访问虚拟机虚拟机访问宿主机适用场景桥接模式有与宿主机同网段直接访问直接访问本次通信首选NAT模式有但和宿主机不同网段直接访问默认不行需配置端口转发虚拟机用来上外网Host-Only有私有网段能访问不能出外网能访问宿主机不能出外网本地隔离测试NAT模式最大的坑在于虚拟机访问宿主机需要额外做端口转发而且转发的地址和端口管理起来很不直观。Host-Only又太封闭没法访问外部网络。RobotStudio的RWS服务跑在Windows宿主机上虚拟机里的ROS要主动去访问它双方还得能双向通讯所以桥接模式是最自然的选择。2.2 我给这套环境的IP规划通信之前先把IP规划写清楚。我的实验环境里Windows宿主机用的是有线网卡网段在192.168.1.0/24。规划如下设备操作系统固定IP说明Windows宿主机Windows10192.168.1.100运行RobotStudio与RWS服务Ubuntu虚拟机Ubuntu 20.04192.168.1.101运行ROS与通信节点网关路由器192.168.1.1用于验证网络链路为什么不直接用DHCP自动分配因为RWS服务是常驻的ROS节点每次都通过IP去找它虚拟机的IP如果频繁变动通信节点就得频繁改配置。固定IP写死之后两边都省心。2.3 桥接模式下Ubuntu静态IP配置步骤VMware里把虚拟机的网络适配器切换到桥接模式具体路径是“虚拟机”菜单 - “设置” - “网络适配器” - 选中“桥接模式”。这一步做完之后虚拟机还需要配置静态IP。Ubuntu 20.04用的是netplan配置文件在/etc/netplan/下通常是01-network-manager-all.yaml内容改成这样network: version: 2 renderer: NetworkManager ethernets: ens33: dhcp4: no addresses: - 192.168.1.101/24 routes: - to: default via: 192.168.1.1 nameservers: addresses: [114.114.114.114]保存后执行sudo netplan apply然后确认网络已经生效ip addr show ens33注意网卡名字不一定是ens33如果你的机器上是eth0或者ens160就把配置文件里的名字改掉。这一步如果写错网卡名apply的时候会直接报错顺着报错提示调整即可。有个小细节值得单独提出来如果用无线网卡做桥接连接可能不稳定桥接模式在无线网卡上的表现偶尔会抽风。遇到这种情况打开VMware的“虚拟网络编辑器”把VMnet0桥接到你的无线网卡型号通常能缓解。2.4 自测清单从互Ping到curl网络配完不要急着写代码先跑一遍自测清单确认链路是通的在Ubuntu虚拟机里ping网关192.168.1.1通了说明虚拟机出得去。在Ubuntu虚拟机里ping宿主机192.168.1.100通了说明虚拟机到宿主机没问题。在Windows里ping虚拟机192.168.1.101通了说明反向也OK。在Ubuntu里检查80端口通不通nc -vz 192.168.1.100 80通了说明RWS端口可达。在Ubuntu里用curl访问RWS接口能返回数据说明跨系统通信已建立。第5步值得展开说一下。RobotStudio的RWS服务跑起来后在Ubuntu虚拟机里执行curl -u admin:robotics http://192.168.1.100/rw/motion/robjoints如果网络和RWS都正常会返回一段XML里面是机器人当前的关节角数据。当你看到这段XML的时候意味着虚拟机里的ROS和宿主机上的RobotStudio在物理链路上已经打通剩下的就是写代码解析和处理数据了。3. RobotStudio侧的“对外开放”虚拟控制器与RWS接口准备3.1 创建虚拟控制器两分钟搞定一个ABB工作站RobotStudio的安装这里不展开默认你已经装好了。安装版本我以RobotStudio 2021.1搭配RobotWare 6.08为例其他版本流程大同小异。打开RobotStudio新建“空工作站”然后在“控制器”菜单里选择“虚拟控制器” - “创建新控制器”。这一步会弹出向导让你选机器人型号和RobotWare版本。我实验用的是ABB IRB 1200-5/0.9这个型号小巧、虚拟控制器启动快做通信测试足够。RobotWare版本用默认的就行不用刻意选最新。创建过程中RobotStudio会自动下载依赖的系统文件首次创建可能需要几分钟耐心等一下。完成后左侧控制器树里会出现一个控制器节点展开能看到“任务”下的T_ROB1。T_ROB1是ABB机器人系统的默认任务名RWS访问机器人运动接口时很多地方都要用到这个任务名后面写代码时会遇到。3.2 RWS服务到底需不需要手动打开我第一次搭这套环境时最困惑的就是RWS服务需不需要额外开启。经验是RobotStudio 2021.1这种新版本虚拟控制器启动后RWS服务默认随控制器一起运行不需要手动勾选。老版本可能会在控制器属性里有个Web Services选项需要手动启用。新版如果访问http://localhost/rw能弹出认证框就说明服务已经起来了。在Windows宿主机上可以先自测一下。浏览器访问http://localhost/rw会弹出一个HTTP Basic认证对话框输入用户名admin、密码robotics能进到RWS的欢迎页或者返回一段XML就说明RWS服务正常。如果你在浏览器里访问时发现打了账号密码还是401先确认一下你用的RobotWare版本。RobotWare不同版本默认密码不一样有的版本是admin/admin有的是admin/robotics。可以在RobotStudio的控制器属性里找到用户管理确认当前系统用户的账号密码。这个信息直接关系到后面ROS节点能不能通过认证先确认清楚再往下走。3.3 Windows防火墙与RWS默认端口RWS默认跑在80端口。如果ROS节点从虚拟机访问宿主机IP时发现TCP连接被拒或者直接卡住多半是Windows防火墙把80端口拦了。处理方式是在Windows防火墙高级设置里添加入站规则放行TCP 80端口。具体路径控制面板 - Windows Defender防火墙 - 高级设置 - 入站规则 - 新建规则 - 端口 - TCP - 特定本地端口填80 - 允许连接。这一步做完后从Ubuntu虚拟机再执行nc -vz 192.168.1.100 80应该能连上。这里有个安全提醒80端口只建议在专用网络里放行如果你所在的网络环境是公共WiFi别为了省事把防火墙全关掉只要放行必要端口就好。RWS的认证虽然走的是HTTP Basic但它毕竟是明文传输在不可信网络里裸奔风险很大。3.4 在Windows上先自测接口回到Windows宿主机上打开命令行先验证一下RWS接口curl -u admin:robotics http://localhost/rw/motion/robjoints返回的XML里会有一堆joint j1-0.025115/joint这样的字段j1到j6对应机器人六个关节轴数据单位是弧度。看到这段XML就说明虚拟控制器已经把当前关节角暴露出来了。这里有个关键技术点RWS返回的关节数据中除了机器人本体六个关节还可能有外部轴extax的数据。外部轴是ABB系统里用于导轨、变位机这类附加轴的表示如果你们的设备只有机器人本体没有外部轴extax字段会一直是0。ROS节点解析数据时要注意区分别把外部轴的值当成机器人关节用。4. ROS侧通信节点的设计与实现从轮询到指令下发4.1 节点任务划分与话题设计网络通了RWS接口也能访问了剩下的核心工作就是在ROS侧写一个通信节点把这套链路串起来。我的通信节点设计比较简单就干三件事按固定频率轮询RWS的robjoints接口拿到机器人当前关节角。把关节角发布到/joint_states话题这样ROS里的MoveIt、RViz都能订阅使用。订阅/joint_cmd话题收到目标关节角后通过RWS写接口把目标值发给虚拟控制器。话题设计如下话题名消息类型方向说明/joint_statessensor_msgs/JointState节点发布机器人当前关节状态/joint_cmdstd_msgs/Float64MultiArray节点订阅目标关节角顺序为joint1~joint6之所以用Float64MultiArray而不是JointState作为指令消息是因为规划端往往只关心6个关节的目标值用一个双精度数组表达最直接。发布指令时只要按顺序填6个弧度值就好。4.2 完整Python节点代码我的实现用Python的requests库发HTTP请求用xml.etree.ElementTree解析返回数据。完整代码如下#!/usr/bin/env python3 import rospy import requests import xml.etree.ElementTree as ET from sensor_msgs.msg import JointState from std_msgs.msg import Float64MultiArray RWS_HOST 192.168.1.100 RWS_USER admin RWS_PWD robotics TASK T_ROB1 RWS_NS {rw: http://www.abb.com/RWS} session requests.Session() session.auth (RWS_USER, RWS_PWD) session.headers.update({Accept: application/xml}) def read_robot_joints(): url http://{}/rw/motion/robjoints.format(RWS_HOST) resp session.get(url, timeout2.0) resp.raise_for_status() root ET.fromstring(resp.text) joints {} for item in root.findall(.//rw:joint, RWS_NS): jname item.attrib.get(j) joints[jname] float(item.text) return [joints[str(i)] for i in range(1, 7)] def write_robot_joints(q): url http://{}/rw/motion/tasks/{}/robjoints.format(RWS_HOST, TASK) data {} for i, val in enumerate(q, start1): data[joint-{}.format(i)] str(val) resp session.post(url, datadata, timeout2.0) resp.raise_for_status() def main(): rospy.init_node(abb_rws_bridge) pub rospy.Publisher(/joint_states, JointState, queue_size1) rate rospy.Rate(10) def on_cmd(msg): rospy.loginfo(收到目标关节角下发至虚拟控制器) write_robot_joints(list(msg.data)) rospy.Subscriber(/joint_cmd, Float64MultiArray, on_cmd) joint_names [joint1, joint2, joint3, joint4, joint5, joint6] while not rospy.is_shutdown(): try: q read_robot_joints() msg JointState() msg.header rospy.Time.now().to_msg() msg.name joint_names msg.position q pub.publish(msg) except Exception as e: rospy.logwarn(通信异常: {}.format(e)) rate.sleep() if __name__ __main__: main()这段代码有几个细节值得注意。第一用requests.Session而不是裸的requests.get是因为Session会复用TCP连接多次轮询时性能好很多不容易出现连接被反复拆除再建立的情况。第二解析XML时必须带上命名空间直接root.findall(.//joint)是找不到节点的需要把{rw: http://www.abb.com/RWS}传进findall的命名空间参数。第三发布JointState时header里的时间戳用rospy.Time.now()而不是Python的time.time()这样ROS的TF和轨迹回放功能才能正确识别消息时序。4.3 指令下发手动模式下写关节角write_robot_joints函数是核心下行通路。首次调试时RobotStudio里的虚拟控制器如果处于自动模式写robjoints接口可能不会产生预期的移动效果因为自动模式下运动指令由RAPID程序控制。所以在RobotStudio的操作界面上把控制模式切到手动Manual模式。手动模式下通过RWS写入关节角会让虚拟控制器尝试驱动机械臂达到目标位置。我在RobotStudio 2021.1 RobotWare 6.08上测试时写接口的字段名用的是joint-1到joint-6。如果你的RobotWare版本较新或较旧字段命名可能有差异。如果发现POST后没有反应可以用RobotStudio自带的抓包工具或者WireShark看一下RWS请求的实际字段格式多半是joint-1和j1的区别。这个版本差异我踩过印象太深了。4.4 跑起来rostopic echo验证与控制实验节点写完放到ROS环境里跑起来。假设你已经建好了catkin工作区把文件放到某个包的src目录下然后chmod x abb_rws_bridge.py source ~/catkin_ws/devel/setup.bash rosrun your_package abb_rws_bridge.py另开一个终端查看关节状态rostopic echo /joint_states这时在RobotStudio里用手动操纵功能拖动虚拟机械臂RViz里如果订阅了/joint_states能看到虚拟机械臂跟着同步运动。ROS和RobotStudio之间已经实现了实时状态同步。再验证下行控制链路。发布一组目标关节角rostopic pub -1 /joint_cmd std_msgs/Float64MultiArray data: [0.1, -0.2, 0.3, 0.0, 0.0, 0.0]如果RobotStudio里的虚拟控制器处于手动模式机械臂会动起来移动到目标角度附近。到了这一步双向通信就已经完全打通了。5. 三次翻车与完整排查链路网络、认证、数据解析5.1 故障一虚拟机ping不通宿主机第一次搭建时我掉进了最简单的坑里。配完静态IP后虚拟机里ping 192.168.1.100一直request timeout。当时我的第一反应是Windows防火墙把ICMP拦了但仔细想想不对劲防火墙一般只拦入站ICMP不会让宿主机彻底不可达。排查链路应该是这样的先ping网关192.168.1.1发现也是timeout。这个结果说明问题不在Windows防火墙而在虚拟机自身的网络路径上。跑去检查VMware的虚拟网络适配器发现还挂在NAT模式上根本不在桥接的网段里。切到桥接模式后网关通了宿主机也通通了。如果发现网关通、宿主机不通那才轮到Windows防火墙的ICMP回显问题。Windows默认会拦ICMP回显请求需要在“高级安全Windows Defender防火墙”里放行“文件和打印机共享回显请求 - ICMPv4-In”规则。这一步做完双向ping就通了。5.2 故障二401 Unauthorized连续报错网络通了之后我在Ubuntu虚拟机里执行curl访问RWS接口返回401 Unauthorized。这是HTTP Basic认证失败的标准响应说明账号密码不对。排查过程是这样的先试了admin/admin401。再试admin/robotics还是401。当时一度怀疑RWS服务挂了但浏览器访问Windows本机的http://localhost/rw时明明能弹出认证框。问题可能出在账号密码上而不是服务本身。后来我在RobotStudio的控制器属性里翻了半天确认虚拟控制器的用户管理里当前激活的密码是安装系统时设置的自定义密码。把这个密码在curl里试了一下返回正常XML。这个坑提醒我网上很多教程写的默认密码不一定适用你的版本最可靠的方法是去RobotStudio的控制器属性里查用户信息。5.3 故障三数据握手成功但角度永远对不上链路通了、认证过了、数据也能读了但把读到的数据发布到/joint_states后RViz里的模型角度和RobotStudio里的实际角度对不上看起来像“抽筋”一样乱跳。这个问题分两部分。第一部分是解析时没有正确处理命名空间导致读出来的数据全是0或者缺值。在ElementTree里findall(.//joint)找不到带默认命名空间的节点必须用findall(.//rw:joint, {rw: http://www.abb.com/RWS})。我第一次写代码时偷懒没理命名空间解析结果为空列表程序却也不报错只是发布出去的数据全是默认值场面一度十分迷惑。第二部分是顺序问题。RWS返回的robjoints里可能同时包含外部轴和机器人本体数据。如果不加筛选直接把所有数据塞进JointState顺序和数量都对不上。正确做法是只取j1到j6这六个机器人本体关节。如果你的URDF里关节名不叫joint1~joint6发布时name列表也要跟着改否则TF树匹配不上。5.4 给排查过程做个减法回头复盘这几个坑我发现它们都有一个共同特征问题都出在“看似理所当然”的环节。网络模式想当然觉得是桥接结果还挂在NAT上账号密码想当然用网上的默认值结果被自定义密码挡在门外XML解析想当然找节点结果忘了命名空间这回事。我的经验是碰到跨系统通信问题时先砍掉所有中间环节做最小系统验证。网络不通就先测网关网关通了再测宿主机宿主机通了再测端口端口通了再测接口认证一步一步缩小范围。不要一上来就在ROS节点里写满日志那样反而把问题淹没在信息流里。6. 从“能通信”到“能干活”数据对齐、频率控制与后续扩展6.1 单位、坐标系、关节名字数据对齐的三座大山通信链路通了只是第一步真正要让这套系统干实事还得过三关单位、坐标系、关节名字。单位方面ABB RWS返回的关节角单位是弧度不是度。如果你在MoveIt里处理的角速度、加速度是别的方式换算的发布给虚拟控制器之前必须统一。很多人第一次接手时想当然认为是度结果机器人猛转一下吓一跳。坐标系方面ABB机器人的基坐标系定义和ROS里URDF的基坐标系定义可能存在差异尤其是关节零位和正方向。如果发现RViz模型和RobotStudio里角度一致但姿态看起来不对多半要检查关节正方向和零位偏移。关节名字方面URDF里的关节名、MoveIt规划组的关节名、以及RWS返回的j1~j6这三者要一一对应。我的做法是在通信节点里用一个映射表把RWS的索引翻译成URDF关节名这样上层完全感知不到底层接口的差异。6.2 轮询频率与HTTP连接池我上面给的节点用的轮询频率是10Hz实测在虚拟控制器上完全够用。如果你觉得状态刷新太慢可以试着调到20Hz但要留意几个问题。一个是HTTP请求的响应时间。RWS接口正常情况下几毫秒就能返回但如果RobotStudio里虚拟控制器负载较高或者虚拟机CPU资源紧张响应时间可能飙升。轮询频率太高但接口响应不过来反而会导致请求堆积占满连接池。另一个问题是requests.Session的连接复用。Session会自动维护HTTP连接池但如果服务器主动断开连接Session可能不知道下次请求会报ConnectionResetError。我的经验是给get和post请求都加上timeout参数并且在外层做异常捕获。出现ConnectionResetError时重新建立Session基本就能解决。6.3 下一步MoveIt轨迹联动与真机切换通信打通后这套系统的扩展空间很大。最常见的扩展方向是MoveIt联动。MoveIt规划出来的轨迹是一串带时间戳的关节角序列你可以让MoveIt而不是人肉去发布/joint_cmd。这样RViz里规划的轨迹就能同步到RobotStudio的虚拟控制器里执行。如果后续要换成真机只要把RWS_HOST从宿主机IP换成真机控制器的IP通信节点基本不用改RWS接口在真机虚拟控制器上同样可用。另一个扩展方向是状态闭环。目前我的实现是单向读取关节角加单向下发目标值如果要做轨迹跟踪或者力控这类闭环算法可以在节点里引入PID反馈把读取到的关节角和目标值做误差计算再通过RWS写接口调整指令值。这样做的前提是控制周期要足够短RWS接口的响应速度会成为瓶颈需要做性能测试。说起这套系统我个人最大的体会是跨系统通信的难点不在写代码而在于把网络、协议、认证这些基础问题钉死在纸面上。只要基础链路稳固后续的业务逻辑都是水到渠成的事。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进