你希望实现C#应用与ROS2(机器人操作系统)的通信“握手”,核心是构建工业机器人场景下的协议桥接方案——解决C#(工业工控机/边缘计算系统)与ROS2(机器人控制核心)之间的跨平台、跨协议互通问题,适配工业机器人(如机械臂、AGV、协作机器人)的实时数据采集、运动控制指令下发、故障状态同步等需求,同时满足工业级的低延迟(<100ms)、高可靠性、断网续传要求。

ROS2作为工业机器人的主流控制框架(基于Linux),而C#是工业场景中广泛用于工控机/边缘系统的开发语言(适配Windows/Linux/ARM),两者的桥接核心是打通DDS(ROS2核心通信层)与C#的交互通道,并适配工业机器人的专用协议(如Modbus/TCP、自定义机器人控制协议),最终实现“C#工业系统 ↔ 桥接层 ↔ ROS2机器人系统”的双向通信。

一、核心架构设计(工业机器人场景适配)

ROS2的通信核心是DDS(数据分发服务),C#与ROS2的桥接需兼顾“实时性(控制指令)”和“灵活性(数据采集)”,设计三层架构:

渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. ... subgraph 1.C#工业系统层(Windows/Linux/ARM) -----------------------^

各层核心职责(工业机器人场景)

层级 核心职责 工业级要求
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:使用RELIABLE QoS保证控制指令可靠传输。
// 实时线程优化
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 测试验证

  1. 启动ROS2机器人节点(发布关节状态、提供运动服务);
  2. 启动桥接层(DDS/MQTT);
  3. 启动C#工业系统,验证:
    • 实时采集机器人关节状态;
    • 下发运动指令,机器人执行;
    • 断网后重连,指令自动补发;
    • 故障状态同步至C#监控面板。

总结

关键点回顾

  1. 桥接方案选型
    • 实时控制指令(如机器人运动):优先选DDS原生桥接(延迟<100ms,工业级实时性);
    • 非实时数据采集(如状态/日志):优先选MQTT桥接(部署灵活,适配弱网/边缘);
  2. 工业级核心保障
    • 可靠性:断网续传、指令缓存、自动重连、QoS可靠传输;
    • 实时性:线程优先级优化、GC禁用、DDS低延迟配置;
    • 安全性:指令幂等性校验、数据加密(WSS/MQTTS);
  3. 跨平台适配
    • C#基于.NET 8实现跨平台部署(Windows/Linux/ARM);
    • ROS2与C#的消息格式需严格对齐(自定义消息/服务)。

C#与ROS2的桥接核心是“适配工业场景”——既要利用ROS2的机器人控制能力,又要保留C#在工业工控、边缘计算中的优势,最终实现工业机器人的“上层工业监控+下层ROS2控制”的一体化通信。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐