C#与ROS2的“握手”:工业机器人通信协议桥接方案
摘要:本文提出了一种C#应用与ROS2(机器人操作系统)的桥接方案,旨在解决工业机器人场景下的跨平台通信问题。核心架构分为三层:C#工业系统层(Windows/Linux/ARM)负责工业协议适配和指令校验;桥接层通过DDS原生或MQTT方案实现低延迟(<100ms)通信;ROS2层执行机器人控制。方案提供两种实现路径:基于RclDotNet的DDS原生桥接(实时控制)和MQTT桥接(灵活数
你希望实现C#应用与ROS2(机器人操作系统)的通信“握手”,核心是构建工业机器人场景下的协议桥接方案——解决C#(工业工控机/边缘计算系统)与ROS2(机器人控制核心)之间的跨平台、跨协议互通问题,适配工业机器人(如机械臂、AGV、协作机器人)的实时数据采集、运动控制指令下发、故障状态同步等需求,同时满足工业级的低延迟(<100ms)、高可靠性、断网续传要求。
ROS2作为工业机器人的主流控制框架(基于Linux),而C#是工业场景中广泛用于工控机/边缘系统的开发语言(适配Windows/Linux/ARM),两者的桥接核心是打通DDS(ROS2核心通信层)与C#的交互通道,并适配工业机器人的专用协议(如Modbus/TCP、自定义机器人控制协议),最终实现“C#工业系统 ↔ 桥接层 ↔ ROS2机器人系统”的双向通信。
一、核心架构设计(工业机器人场景适配)
ROS2的通信核心是DDS(数据分发服务),C#与ROS2的桥接需兼顾“实时性(控制指令)”和“灵活性(数据采集)”,设计三层架构:
各层核心职责(工业机器人场景)
| 层级 | 核心职责 | 工业级要求 |
|---|---|---|
| C#工业系统层 | 工业监控、边缘计算、对接机器人本体工业协议、指令校验/缓存 | 断网续传、指令幂等性、数据校验 |
| 桥接层 | 实现C#与ROS2的通信交互、消息格式转换、路由分发 | 控制指令延迟<100ms、数据采集QoS适配 |
| ROS2机器人系统层 | 执行运动控制、上报机器人状态(关节角度/速度/故障)、对接底层驱动 | 实时性、指令响应<50ms |
二、环境准备(跨平台+工业级)
2.1 ROS2环境(Linux,推荐Humble Hawksbill)
# 1. 安装ROS2 Humble(Ubuntu 22.04)
sudo apt update && sudo apt install ros-humble-desktop-full
# 2. 安装ROS2 MQTT桥接插件(适配方案2)
sudo apt install ros-humble-mqtt-bridge
# 3. 安装ROS2 DDS工具(调试用)
sudo apt install ros-humble-rmw-fastrtps-cli # FastDDS(ROS2默认DDS)
2.2 C#环境(跨平台,.NET 8)
核心NuGet包
# 方案1:ROS2 C#原生客户端(rclcs,对接DDS)
Install-Package RclDotNet -Version 0.10.0
# 方案2:MQTT桥接(适配ROS2 MQTT插件)
Install-Package MQTTnet -Version 4.3.6
# 工业协议适配(机器人Modbus/TCP)
Install-Package NModbus4 -Version 4.0.1
# 工业级缓存/日志
Install-Package SQLitePCL.raw -Version 2.1.8
Install-Package Serilog -Version 3.1.1
跨平台部署准备
- Windows:直接使用.NET 8 SDK;
- Linux:安装.NET 8 Runtime(
sudo apt install dotnet-runtime-8.0); - 工业边缘ARM:编译
linux-arm64版本(dotnet publish -r linux-arm64)。
三、核心桥接方案实现(C#+ROS2)
方案1:DDS原生桥接(低延迟控制指令)
基于RclDotNet(ROS2 C#客户端)直接对接ROS2的DDS层,适用于工业机器人实时控制指令(如机械臂关节运动、AGV路径下发),延迟<100ms,满足工业级实时性要求。
3.1.1 ROS2端:定义机器人控制消息/服务
首先在ROS2中定义工业机器人的自定义消息(如关节状态、运动指令):
# 1. 创建ROS2功能包
mkdir -p ros2_ws/src && cd ros2_ws/src
ros2 pkg create --build-type ament_cmake robot_msgs
# 2. 创建消息文件(robot_msgs/msg/JointState.msg)
echo -e "float64[] joint_angles\nfloat64[] joint_speeds\nstring robot_status" > robot_msgs/msg/JointState.msg
# 3. 创建服务文件(robot_msgs/srv/MoveJoint.srv)
echo -e "float64[] target_angles\nfloat64 speed\n---\nbool success\nstring message" > robot_msgs/srv/MoveJoint.srv
# 4. 编译功能包
cd ../ && colcon build --packages-select robot_msgs
source install/setup.bash
3.1.2 C#端:ROS2 DDS原生通信(发布/订阅/服务调用)
using Rcl;
using Rcl.Logging;
using robot_msgs.msg;
using robot_msgs.srv;
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// C#与ROS2 DDS原生桥接(工业机器人实时控制)
/// </summary>
public class Ros2DdsBridge : IDisposable
{
private readonly RclContext _context;
private readonly RclNode _node;
private readonly CancellationTokenSource _cts;
// 机器人配置
private readonly string _robotName = "industrial_arm";
private readonly string _jointStateTopic = $"/{_robotName}/joint_state";
private readonly string _moveJointService = $"/{_robotName}/move_joint";
public Ros2DdsBridge()
{
// 初始化ROS2上下文(适配跨平台,自动识别Linux/Windows)
_context = new RclContext(args: Array.Empty<string>(), domainId: 0);
_context.Initialize();
// 创建ROS2节点(工业机器人桥接节点)
_node = _context.CreateNode($"csharp_bridge_node_{Guid.NewGuid():N}");
_node.Logger.SetLogLevel(LogLevel.Info);
_cts = new CancellationTokenSource();
_node.Logger.Info("C# ROS2 DDS桥接节点初始化完成");
}
/// <summary>
/// 订阅ROS2机器人关节状态(实时采集)
/// </summary>
public void SubscribeJointState()
{
// 创建订阅者(QoS:RELIABLE,工业级可靠传输)
var subscriber = _node.CreateSubscriber<JointState>(
_jointStateTopic,
QoSProfile.Reliable);
// 注册消息接收回调(工业机器人状态解析)
subscriber.MessageReceived += (msg) =>
{
// 解析ROS2关节状态消息,转换为C#工业数据模型
var jointData = new IndustrialRobotJointData
{
RobotName = _robotName,
JointAngles = msg.JointAngles.ToArray(),
JointSpeeds = msg.JointSpeeds.ToArray(),
RobotStatus = msg.RobotStatus,
CollectTime = DateTime.Now
};
// 推送至C#工业监控系统(如边缘计算/面板)
OnJointStateReceived(jointData);
_node.Logger.Info($"接收机器人关节状态:{string.Join(",", msg.JointAngles)}");
};
_node.Logger.Info($"已订阅ROS2话题:{_jointStateTopic}");
}
/// <summary>
/// 调用ROS2机器人运动服务(下发关节运动指令)
/// </summary>
/// <param name="targetAngles">目标关节角度</param>
/// <param name="speed">运动速度</param>
public async Task<(bool Success, string Message)> CallMoveJointService(double[] targetAngles, double speed)
{
// 创建服务客户端(工业级:超时10秒)
var client = _node.CreateClient<MoveJoint>(_moveJointService, QoSProfile.Reliable);
if (!await client.WaitForServiceAsync(TimeSpan.FromSeconds(10)))
{
_node.Logger.Error($"ROS2服务{_moveJointService}不可用");
return (false, "服务不可用");
}
// 构造运动指令(ROS2服务请求)
var request = new MoveJoint.Request();
request.TargetAngles.AddRange(targetAngles);
request.Speed = speed;
// 发送请求并等待响应(工业级:指令幂等性校验)
var response = await client.CallAsync(request, _cts.Token);
_node.Logger.Info($"运动指令执行结果:{response.Success} - {response.Message}");
return (response.Success, response.Message);
}
/// <summary>
/// 发布机器人故障状态至ROS2(C#→ROS2)
/// </summary>
/// <param name="faultMsg">故障信息</param>
public void PublishRobotFault(string faultMsg)
{
var publisher = _node.CreatePublisher<JointState>(
_jointStateTopic,
QoSProfile.Reliable);
var msg = new JointState();
msg.RobotStatus = $"Fault: {faultMsg}";
publisher.Publish(msg);
_node.Logger.Warn($"发布机器人故障状态:{faultMsg}");
}
/// <summary>
/// 关节状态接收事件(对接C#工业系统)
/// </summary>
public event Action<IndustrialRobotJointData> JointStateReceived;
private void OnJointStateReceived(IndustrialRobotJointData data)
{
JointStateReceived?.Invoke(data);
}
// 资源释放(工业级:优雅关闭)
public void Dispose()
{
_cts.Cancel();
_node.Dispose();
_context.Shutdown();
_context.Dispose();
_cts.Dispose();
_node.Logger.Info("C# ROS2 DDS桥接节点已关闭");
}
/// <summary>
/// 工业机器人关节数据模型(C#侧标准化)
/// </summary>
public class IndustrialRobotJointData
{
public string RobotName { get; set; }
public double[] JointAngles { get; set; } // 关节角度(°)
public double[] JointSpeeds { get; set; } // 关节速度(°/s)
public string RobotStatus { get; set; } // 正常/故障/待机
public DateTime CollectTime { get; set; }
}
}
// 调用示例
// var ros2Bridge = new Ros2DdsBridge();
// // 订阅关节状态
// ros2Bridge.JointStateReceived += (data) =>
// {
// Console.WriteLine($"机器人{data.RobotName}关节1角度:{data.JointAngles[0]}°");
// };
// ros2Bridge.SubscribeJointState();
// // 调用运动服务(控制机械臂关节运动)
// var result = await ros2Bridge.CallMoveJointService(new double[] { 0, 30, 60, 0, 0, 0 }, 5.0);
// Console.WriteLine($"运动指令执行结果:{result.Success}");
// // 释放资源
// ros2Bridge.Dispose();
方案2:MQTT桥接(灵活数据交互)
基于MQTT实现C#与ROS2的间接桥接(ROS2通过mqtt_bridge插件对接MQTT服务器),适用于非实时数据采集(如机器人运行日志、故障统计),适配弱网/边缘场景,部署灵活。
3.2.1 ROS2端:配置MQTT桥接
创建ROS2 MQTT桥接配置文件(mqtt_bridge_config.yaml):
mqtt:
client:
protocol: "tcp"
host: "192.168.1.100" # MQTT服务器IP(工业边缘网关)
port: 1883
client_id: "ros2_robot_bridge"
bridge:
- topic: /industrial_arm/joint_state
type: robot_msgs/msg/JointState
direction: to_mqtt # ROS2→MQTT
qos: 1 # 工业级:至少一次
- topic: /industrial_arm/move_joint
type: robot_msgs/srv/MoveJoint
direction: from_mqtt # MQTT→ROS2
qos: 1
启动ROS2 MQTT桥接节点:
ros2 run mqtt_bridge mqtt_bridge_node --ros-args --params-file mqtt_bridge_config.yaml
3.2.2 C#端:MQTT通信(对接ROS2桥接)
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
using System;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// C#与ROS2 MQTT桥接(工业机器人数据采集/指令下发)
/// </summary>
public class Ros2MqttBridge : IDisposable
{
private readonly IMqttClient _mqttClient;
private readonly MqttClientOptions _mqttOptions;
private readonly CancellationTokenSource _cts;
private readonly string _robotName = "industrial_arm";
// MQTT主题(与ROS2桥接配置一致)
private readonly string _jointStateTopic = $"/industrial_arm/joint_state";
private readonly string _moveJointTopic = $"/industrial_arm/move_joint";
public Ros2MqttBridge(string mqttServerIp = "192.168.1.100", int port = 1883)
{
_cts = new CancellationTokenSource();
var factory = new MqttFactory();
_mqttClient = factory.CreateMqttClient();
// MQTT客户端配置(工业级:遗嘱消息+会话保留)
_mqttOptions = new MqttClientOptionsBuilder()
.WithTcpServer(mqttServerIp, port)
.WithClientId($"csharp_robot_bridge_{Guid.NewGuid():N}")
.WithCleanSession(false) // 保留会话,断网重连后接收离线消息
// 遗嘱消息:C#端离线时上报
.WithWillMessage(new MqttApplicationMessageBuilder()
.WithTopic($"/industrial_arm/bridge_status")
.WithPayload("offline")
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build())
.Build();
// 注册MQTT事件
_mqttClient.ConnectedAsync += OnMqttConnected;
_mqttClient.DisconnectedAsync += OnMqttDisconnected;
_mqttClient.ApplicationMessageReceivedAsync += OnMqttMessageReceived;
}
/// <summary>
/// 启动MQTT桥接(自动重连)
/// </summary>
public async Task StartAsync()
{
await ConnectMqttAsync();
// 订阅机器人关节状态(ROS2→MQTT→C#)
await _mqttClient.SubscribeAsync(new MqttTopicFilterBuilder()
.WithTopic(_jointStateTopic)
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build());
Console.WriteLine("C# ROS2 MQTT桥接启动成功");
}
/// <summary>
/// MQTT连接(工业级:自动重连)
/// </summary>
private async Task ConnectMqttAsync()
{
try
{
await _mqttClient.ConnectAsync(_mqttOptions, _cts.Token);
}
catch (Exception ex)
{
Console.WriteLine($"MQTT连接失败:{ex.Message},5秒后重连");
await Task.Delay(5000);
await ConnectMqttAsync();
}
}
/// <summary>
/// 下发机器人运动指令(C#→MQTT→ROS2)
/// </summary>
public async Task SendMoveJointCommand(double[] targetAngles, double speed)
{
if (!_mqttClient.IsConnected)
{
await ConnectMqttAsync();
}
// 构造指令(适配ROS2服务格式)
var command = new
{
target_angles = targetAngles,
speed = speed
};
var payload = JsonSerializer.Serialize(command);
// 发布指令(QoS 1,工业级可靠传输)
var msg = new MqttApplicationMessageBuilder()
.WithTopic(_moveJointTopic)
.WithPayload(Encoding.UTF8.GetBytes(payload))
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.WithRetainFlag(false)
.Build();
await _mqttClient.PublishAsync(msg, _cts.Token);
Console.WriteLine($"下发运动指令:{payload}");
}
/// <summary>
/// 接收MQTT消息(ROS2机器人状态)
/// </summary>
private Task OnMqttMessageReceived(MqttApplicationMessageReceivedEventArgs e)
{
var topic = e.ApplicationMessage.Topic;
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
if (topic == _jointStateTopic)
{
// 解析ROS2关节状态消息
var jointState = JsonSerializer.Deserialize<Ros2JointState>(payload);
var industrialData = new IndustrialRobotJointData
{
RobotName = _robotName,
JointAngles = jointState.joint_angles,
JointSpeeds = jointState.joint_speeds,
RobotStatus = jointState.robot_status,
CollectTime = DateTime.Now
};
// 推送至C#工业系统
JointStateReceived?.Invoke(industrialData);
Console.WriteLine($"接收机器人关节状态:{string.Join(",", jointState.joint_angles)}");
}
return Task.CompletedTask;
}
// MQTT连接成功/断开事件
private Task OnMqttConnected(MqttClientConnectedEventArgs e)
{
Console.WriteLine("MQTT连接成功");
// 上报桥接状态
return _mqttClient.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic($"/industrial_arm/bridge_status")
.WithPayload("online")
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build());
}
private Task OnMqttDisconnected(MqttClientDisconnectedEventArgs e)
{
Console.WriteLine("MQTT连接断开,自动重连");
_ = ConnectMqttAsync();
return Task.CompletedTask;
}
// 资源释放
public void Dispose()
{
_cts.Cancel();
_mqttClient.DisconnectAsync().Wait();
_mqttClient.Dispose();
_cts.Dispose();
}
// 事件与数据模型
public event Action<IndustrialRobotJointData> JointStateReceived;
public class IndustrialRobotJointData
{
public string RobotName { get; set; }
public double[] JointAngles { get; set; }
public double[] JointSpeeds { get; set; }
public string RobotStatus { get; set; }
public DateTime CollectTime { get; set; }
}
// ROS2 JointState消息JSON模型(与ROS2格式对齐)
private class Ros2JointState
{
public double[] joint_angles { get; set; }
public double[] joint_speeds { get; set; }
public string robot_status { get; set; }
}
}
// 调用示例
// var mqttBridge = new Ros2MqttBridge();
// await mqttBridge.StartAsync();
// mqttBridge.JointStateReceived += (data) =>
// {
// Console.WriteLine($"机器人关节状态:{data.RobotStatus}");
// };
// // 下发运动指令
// await mqttBridge.SendMoveJointCommand(new double[] { 0, 30, 60, 0, 0, 0 }, 5.0);
// mqttBridge.Dispose();
3.3 工业协议适配层(C#对接机器人本体)
工业机器人本体可能采用Modbus/TCP等工业协议,C#需先对接机器人本体协议,再转换为ROS2消息格式:
/// <summary>
/// C#对接工业机器人Modbus/TCP协议,转换为ROS2消息
/// </summary>
public class RobotModbusAdapter
{
private readonly string _robotModbusIp;
private readonly int _modbusPort = 502;
private readonly byte _slaveId = 1;
private readonly Ros2DdsBridge _ros2Bridge;
public RobotModbusAdapter(string robotModbusIp, Ros2DdsBridge ros2Bridge)
{
_robotModbusIp = robotModbusIp;
_ros2Bridge = ros2Bridge;
}
/// <summary>
/// 采集机器人Modbus数据,发布至ROS2
/// </summary>
public async Task CollectAndPublishToRos2Async()
{
using var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(_robotModbusIp, _modbusPort);
var modbusMaster = ModbusIpMaster.CreateIp(tcpClient);
// 读取机器人关节角度寄存器(0x0000~0x0005:6轴关节)
ushort[] jointRegisters = modbusMaster.ReadHoldingRegisters(_slaveId, 0, 6);
double[] jointAngles = jointRegisters.Select(r => r / 100.0).ToArray(); // 寄存器值放大100倍
// 读取机器人状态寄存器(0x0006:0=正常,1=故障)
ushort statusRegister = modbusMaster.ReadHoldingRegisters(_slaveId, 6, 1)[0];
string robotStatus = statusRegister == 0 ? "normal" : "fault";
// 构造ROS2关节状态消息,发布至ROS2
var jointData = new Ros2DdsBridge.IndustrialRobotJointData
{
RobotName = "industrial_arm",
JointAngles = jointAngles,
JointSpeeds = new double[6],
RobotStatus = robotStatus,
CollectTime = DateTime.Now
};
// 若机器人故障,发布故障状态至ROS2
if (robotStatus == "fault")
{
_ros2Bridge.PublishRobotFault("机器人关节超限");
}
Console.WriteLine($"采集机器人Modbus数据:关节1角度{jointAngles[0]}°");
}
}
四、工业级增强(可靠性+实时性)
4.1 断网续传与指令缓存
/// <summary>
/// 工业级指令缓存(断网时缓存,重连后补发)
/// </summary>
private readonly Queue<string> _commandCache = new();
private readonly object _cacheLock = new();
// 缓存指令(断网时)
private void CacheCommand(string commandJson)
{
lock (_cacheLock)
{
if (_commandCache.Count < 1000) // 工业级:限制缓存数量
{
_commandCache.Enqueue(commandJson);
}
}
}
// 重连后补发指令
private async Task ResendCachedCommandsAsync()
{
lock (_cacheLock)
{
while (_commandCache.Count > 0 && _mqttClient.IsConnected)
{
var command = _commandCache.Dequeue();
// 补发指令
var msg = new MqttApplicationMessageBuilder()
.WithTopic(_moveJointTopic)
.WithPayload(Encoding.UTF8.GetBytes(command))
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build();
_mqttClient.PublishAsync(msg).Wait();
}
}
}
4.2 实时性优化(C#侧)
- 线程优先级:设置ROS2通信线程为高优先级;
- GC优化:禁用GC卡顿(工业实时控制场景);
- DDS QoS:使用
RELIABLEQoS保证控制指令可靠传输。
// 实时线程优化
var realTimeThread = new Thread(async () =>
{
Thread.CurrentThread.Priority = ThreadPriority.Highest;
GC.TryStartNoGCRegion(1024 * 1024 * 10); // 禁用GC 10MB内存
while (!_cts.Token.IsCancellationRequested)
{
await CollectAndPublishToRos2Async();
await Task.Delay(100); // 100ms采集间隔
}
GC.EndNoGCRegion();
});
realTimeThread.Start();
五、部署与测试
5.1 跨平台部署
- ROS2端:Linux(Ubuntu 22.04)运行ROS2节点+MQTT桥接;
- C#端:
- Windows工控机:运行C# WinForm/WPF监控面板+DDS/MQTT桥接;
- Linux边缘网关:发布
linux-x64版本(dotnet publish -c Release -r linux-x64); - ARM工业网关:发布
linux-arm64版本(dotnet publish -c Release -r linux-arm64)。
5.2 测试验证
- 启动ROS2机器人节点(发布关节状态、提供运动服务);
- 启动桥接层(DDS/MQTT);
- 启动C#工业系统,验证:
- 实时采集机器人关节状态;
- 下发运动指令,机器人执行;
- 断网后重连,指令自动补发;
- 故障状态同步至C#监控面板。
总结
关键点回顾
- 桥接方案选型:
- 实时控制指令(如机器人运动):优先选DDS原生桥接(延迟<100ms,工业级实时性);
- 非实时数据采集(如状态/日志):优先选MQTT桥接(部署灵活,适配弱网/边缘);
- 工业级核心保障:
- 可靠性:断网续传、指令缓存、自动重连、QoS可靠传输;
- 实时性:线程优先级优化、GC禁用、DDS低延迟配置;
- 安全性:指令幂等性校验、数据加密(WSS/MQTTS);
- 跨平台适配:
- C#基于.NET 8实现跨平台部署(Windows/Linux/ARM);
- ROS2与C#的消息格式需严格对齐(自定义消息/服务)。
C#与ROS2的桥接核心是“适配工业场景”——既要利用ROS2的机器人控制能力,又要保留C#在工业工控、边缘计算中的优势,最终实现工业机器人的“上层工业监控+下层ROS2控制”的一体化通信。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)