• ROSBridge - ROS系统与非ROS外部系统的通信的C++客户端实现


    小结

    ROSBridge可以实现ROS系统与非ROS外部系统的通信,文档比较多比较杂,在Windows客户端的实现大多是Java, Python, JS等,这里对C++客户端使用Websockets通信进行了测试,C++ ROSBridge的客户端有轻量级,解耦合的优点。

    回顾

    rosserial

    参考使用rosserial实现ROS与Windows的service服务通信,使用rosserial也是可以实现ROS系统与非ROS外部系统的通信。

    ROSBridge

    有关ROSBridge的官方文档可以参见: ROS Wiki - rosbridge_suite

    ROSBridge Client的C++实现

    用C++实现ROSBridge的客户端主要有两个Github,
    1,Github: Sanic/rosbridge2cpp 这个实现不支持Websockets,所以没有进行测试。
    2,本文主要参考了Github-C++ Rosbridge client uses Websockets and Rapidjson: ppianpak/rosbridgecpp
    Rapidjson主要参考Rapidjson Tutorial

    另外也借鉴了Rosbridge的JS客户端的实现:RobotWebTools/roslibjs/examples/simple.html

    具体使用的ROSBridge的数据协议详情可以参考:Github: RobotWebTools/rosbridge_suite: rosbridge v2.0 Protocol Specification

    ROSBridge Client的其它实现

    Github: C# rosbridge-csharp-connection
    ROSBridgeClient in Java
    Github: ROSBridgeTestclient in Python

    实现及测试

    启动服务器端测试环境

    参考ROS的roslibjs基本功能使用测试:

    #启动roscore
    roscore
    #在/listener这个topic中发布一个"Hello, World"消息
    rostopic pub /listener std_msgs/String "Hello, World"
    #监听/cmd_vel的消息
    rostopic echo /cmd_vel
    #启动add_two_ints_server服务
    rosrun rospy_tutorials add_two_ints_server
    #启动rosbridge_server 
    roslaunch rosbridge_server rosbridge_websocket.launch
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    初始化ROSBridge客户端

    RosbridgeWsClient rbc("192.168.238.129:9090");
    
    • 1

    这里192.168.238.129是服务器地址,端口9090。

    ROSBridge测试/listener的topic

    发布/listener的topic并订阅/listener的topic:

      rbc.addClient("topic_advertiser");
      rbc.advertise("topic_advertiser", "/listener", "std_msgs/String");
      rbc.addClient("topic_subscriber");
      rbc.subscribe("topic_subscriber", "/listener", subscriberCallback);
    
    • 1
    • 2
    • 3
    • 4

    subscriberCallback()如下:

    void subscriberCallback(std::shared_ptr<WsClient::Connection> /*connection*/, std::shared_ptr<WsClient::InMessage> in_message)
    {
      std::cout << "subscriberCallback(): Message Received: " << in_message->string() << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4

    先前服务器已经发布了rostopic pub /listener std_msgs/String "Hello, World",客户端会有以下输出:

    topic_subscriber: Sending message: {"op":"subscribe", "topic":"/listener"}
    subscriberCallback(): Message Received: {"op": "publish", "topic": "/listener", "msg": {"data": "Hello, World"}}
    
    • 1
    • 2

    也可以通过ROSBridge C++客户端的代码进行发布:

    void publisherThread(RosbridgeWsClient& rbc, const std::future<void>& futureObj)
    {
      rbc.addClient("topic_publisher");
    
      rapidjson::Document d;
      d.SetObject();
      d.AddMember("data", "Test message from John /listener", d.GetAllocator());
    
      while (futureObj.wait_for(std::chrono::milliseconds(1)) == std::future_status::timeout)
      {
        rbc.publish("/listener", d);
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
      }
    
      std::cout << "publisherThread stops()" << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    也就是客户端自己发布,自己接收,ROSBridge C++客户端输出如下:

    publish_client: Opened connection
    publish_client: Sending message: {"op":"publish", "topic":"/listener", "msg":{"data":"Test message from John /listener"}}
    
    • 1
    • 2

    有关发送消息的结构是string data如下:

    john@ubuntu:~$ rostopic list
    /client_count
    /cmd_vel
    /connected_clients
    /listener
    /rosout
    /rosout_agg
    john@ubuntu:~$ rostopic info /listener
    Type: std_msgs/String
    
    Publishers: 
     * /rostopic_5673_1660196707336 (http://ubuntu:35063/)
    
    Subscribers: None
    
    john@ubuntu:~$ rosmsg show std_msgs/String
    string data
    
    john@ubuntu:~$ 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    ROSBridge测试/cmd_vel的topic

    /cmd_vel这个消息结构比较常用但是有些复杂,需要使用好Rapidjson。
    消息结构如下:

    john@ubuntu:~$ rostopic info /cmd_vel
    Type: geometry_msgs/Twist
    
    Publishers: 
     * /rosbridge_websocket (http://ubuntu:38019/)
    
    Subscribers: 
     * /rostopic_5710_1660196719502 (http://ubuntu:37659/)
    john@ubuntu:~$ 
    john@ubuntu:~$ rosmsg show geometry_msgs/Twist
    geometry_msgs/Vector3 linear
      float64 x
      float64 y
      float64 z
    geometry_msgs/Vector3 angular
      float64 x
      float64 y
      float64 z
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ROSBridge C++客户端实现代码如下:

    void publisherTwist(RosbridgeWsClient& rbc)
    {
        rbc.addClient("topic_publisher_Twist");
        
        rapidjson::Document d;
        d.SetObject();
        
        float x = 0.1;
        float y = 0.2;
        float z = 0.3;
    
        rapidjson::Value data(rapidjson::kObjectType);
        data.AddMember("x", x, d.GetAllocator());
        data.AddMember("y", y, d.GetAllocator());
        data.AddMember("z", z, d.GetAllocator());
        d.AddMember("linear", data, d.GetAllocator());
        
        x = -0.1;
        y = -0.2;
        z = -0.3;
        rapidjson::Value dataAngular(rapidjson::kObjectType);
        dataAngular.AddMember("x", x, d.GetAllocator());
        dataAngular.AddMember("y", y, d.GetAllocator());
        dataAngular.AddMember("z", z, d.GetAllocator());
    
        d.AddMember("angular", dataAngular, d.GetAllocator());
        rbc.publish("/cmd_vel", d);
    
        std::cout << "topic_publisher_Twist stops()" << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    ROSBridge C++客户端输出如下:

    publish_client: Opened connection
    publish_client: Sending message: {"op":"publish", "topic":"/cmd_vel", "msg":{"linear":{"x":0.10000000149011612,"y":0.20000000298023225,"z":0.30000001192092898},"angular":{"x":-0.10000000149011612,"y":-0.20000000298023225,"z":-0.30000001192092898}}}
    
    • 1
    • 2

    发布完数据后,ROSBridge服务器端接收:

    john@ubuntu:~$ rostopic echo /cmd_vel
    WARNING: topic [/cmd_vel] does not appear to be published yet
    linear: 
      x: 0.10000000149011612
      y: 0.20000000298023224
      z: 0.30000001192092896
    angular: 
      x: -0.10000000149011612
      y: -0.20000000298023224
      z: -0.30000001192092896
    ---
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    ROSBridge测试/add_two_ints服务

    先前在服务器端启动了add_two_ints服务
    john@ubuntu:~$ rosrun rospy_tutorials add_two_ints_server

    传输数据格式如下:

    john@ubuntu:~$ rosservice info /add_two_ints
    Node: /add_two_ints_server
    URI: rosrpc://ubuntu:33809
    Type: rospy_tutorials/AddTwoInts
    Args: a b
    john@ubuntu:~$ 
    
    john@ubuntu:~$ rossrv show rospy_tutorials/AddTwoInts
    int64 a
    int64 b
    ---
    int64 sum
    
    john@ubuntu:~$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ROSBridge C++客户端实现代码如下:

      rapidjson::Value dataTwoInts(rapidjson::kObjectType);
      rapidjson::Document documentAddTwoInt(rapidjson::kObjectType);
      int a = 5;
      int b = 6;
      documentAddTwoInt.AddMember("a", a, documentAddTwoInt.GetAllocator());
      documentAddTwoInt.AddMember("b", b, documentAddTwoInt.GetAllocator());
    
    
      rbc.callService("/add_two_ints", callServiceCallbackAddtwo, documentAddTwoInt);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    callServiceCallbackAddtwo函数如下:

    void callServiceCallbackAddtwo(std::shared_ptr<WsClient::Connection> connection, std::shared_ptr<WsClient::InMessage> in_message)
    {
        std::string messagebuf = in_message->string();
        std::cout << "callServiceCallbackAddtwo(): Message Received: " << messagebuf << std::endl;
    
        rapidjson::Document document;
        if (document.Parse(messagebuf.c_str()).HasParseError())
        {
            std::cerr << "advertiseServiceCallback(): Error in parsing service request message: " << messagebuf << std::endl;
            return;
        }
    
        std::cout << "The returned sum is: " << std::to_string(document["values"]["sum"].GetInt64()) << std::endl;
    
        connection->send_close(1000);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ROSBridge C++客户端输出如下:

    call_service_client: Opened connection
    call_service_client: Sending message: {"op":"call_service", "service":"/add_two_ints", "args":{"a":5,"b":6}}
    callServiceCallbackAddtwo(): Message Received: {"op": "service_response", "service": "/add_two_ints", "values": {"sum": 11}, "result": true}
    The returned sum is: 11
    
    • 1
    • 2
    • 3
    • 4

    ROSBridge服务器端输出如下:

    john@ubuntu:~$ rosrun rospy_tutorials add_two_ints_server
    Returning [5 + 6 = 11]
    
    
    • 1
    • 2
    • 3

    ROSBridge测试/set_bool服务

    SetBool服务的数据结构如下,输入为bool data,输出为bool success string message

    john@ubuntu:~$ rosservice info /set_bool
    Node: /rosbridge_websocket
    URI: rosrpc://ubuntu:39413
    Type: std_srvs/SetBool
    Args: data
    john@ubuntu:~$ rossrv show std_srvs/SetBool
    bool data
    ---
    bool success
    string message
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ROSBridge C++客户端发布这个服务:

      rbc.addClient("service_advertiser");
      rbc.advertiseService("service_advertiser", "/set_bool", "std_srvs/SetBool", advertiseServiceCallback);
    
    • 1
    • 2

    advertiseServiceCallback函数如下, 有返回就回调。可以看到ROSBridge C++客户端使用document["args"]["data"].GetBool()来查看ROSBridge服务器返回的SetBool的值。再返回给ROSBridge服务器bool success string message的内容。

    void advertiseServiceCallback(std::shared_ptr<WsClient::Connection> /*connection*/, std::shared_ptr<WsClient::InMessage> in_message)
    {
      // message->string() is destructive, so we have to buffer it first
      std::string messagebuf = in_message->string();
      std::cout << "advertiseServiceCallback(): Message Received: " << messagebuf << std::endl;
    
      rapidjson::Document document;
      if (document.Parse(messagebuf.c_str()).HasParseError())
      {
        std::cerr << "advertiseServiceCallback(): Error in parsing service request message: " << messagebuf << std::endl;
        return;
      }
    
      bool result = document["args"]["data"].GetBool();
    
      rapidjson::Document values(rapidjson::kObjectType);
      rapidjson::Document::AllocatorType& allocator = values.GetAllocator();
      values.AddMember("success", document["args"]["data"].GetBool(), allocator);
      values.AddMember("message", "from advertiseServiceCallback", allocator);
    
      rbc.serviceResponse(document["service"].GetString(), document["id"].GetString(), true, values);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    ROSBridge C++客户端调用SetBool服务:

      rapidjson::Document document(rapidjson::kObjectType);
      document.AddMember("data", true, document.GetAllocator());
      rbc.callService("/set_bool", callServiceCallback, document);
    
    • 1
    • 2
    • 3

    callServiceCallback的回调函数如下:

    void callServiceCallback(std::shared_ptr<WsClient::Connection> connection, std::shared_ptr<WsClient::InMessage> in_message)
    {
      std::cout << "serviceResponseCallback(): Message Received: " << in_message->string() << std::endl;
      connection->send_close(1000);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ROSBridge C++客户端自发自收的效果如下:
    ROSBridge C++客户端调用/set_bool服务:

    service_advertiser: Opened connection
    service_advertiser: Sending message: {"op":"advertise_service", "service":"/set_bool", "type":"std_srvs/SetBool"}
    call_service_client: Opened connection
    call_service_client: Sending message: {"op":"call_service", "service":"/set_bool", "args":{"data":true}}
    
    • 1
    • 2
    • 3
    • 4

    ROSBridge C++客户端接收到了/set_bool服务调用:

    advertiseServiceCallback(): Message Received: {"op": "call_service", "id": "service_request:/set_bool:1", "service": "/set_bool", "args": {"data": true}}
    
    • 1

    ROSBridge C++客户端响应/set_bool调用后,进行应答rbc.serviceResponse(document["service"].GetString(), document["id"].GetString(), true, values);
    结果如下:

    service_response_client: Opened connection
    service_response_client: Sending message: {"op":"service_response", "service":"/set_bool", "result":true, "id":"service_request:/set_bool:1", "values":{"success":true,"message":"from advertiseServiceCallback"}}
    
    • 1
    • 2

    ROSBridge C++客户端接收到/set_bool调用的应答:

    serviceResponseCallback(): Message Received: {"op": "service_response", "service": "/set_bool", "values": {"success": true, "message": "from advertiseServiceCallback"}, "result": true}
    
    • 1

    参考

    我的AI之路(25)–ROSBridge:机器人与外部系统之间的通讯解决方案
    我的AI之路(27)–用C++实现机器人与机器人后端控制系统之间的双向通讯
    我的AI之路(29)–使用Boost Asio和Rapidjson实现机器人与机器人后端系统之间的自定义协议的通讯
    Github: RobotWebTools/rosbridge_suite
    ROS Wiki - rosbridge_suite
    使用rosserial实现ROS与Windows的service服务通信
    Package RosBridge——打通Ros与非Ros环境的数据壁垒
    Package RosBridge——打通Ros与非Ros环境的数据壁垒 #2 C++端实现功能ppianpak / rosbridgecpp
    ROSBridge简介以及理解使用(上)
    ROSBridge简介以及理解使用(下)
    转:使用rosbridge协议实现安卓跟ros的解耦
    古月居:ROSBridge简介以及理解使用
    Github: RobotWebTools/rosbridge_suite: rosbridge v2.0 Protocol Specification
    ROS Wiki Rosbridge in C++
    Github: Sanic/rosbridge2cpp
    Request for help regarding ROS-> C++ Application (windows) communication problem for video transmission.
    Github: C# rosbridge-csharp-connection
    ROSBridgeClient in Java
    Android使用ROSBridge与ROS通信 简单使用
    Github: ROSBridgeTestclient in Python
    ROS Wiki How to send data in Json format using service client in c++ code
    Rapidjson Tutorial
    Github-C++ Rosbridge client uses Websockets and Rapidjson: ppianpak/rosbridgecpp
    RobotWebTools/roslibjs/examples/simple.html

  • 相关阅读:
    十八、字符串(2)
    【UOJ 494】DNA序列(贪心)(Lyndon分解)
    【学海】中位数(Median)的介绍以及如何在不同条件下计算中位数
    如何不编程用 ChatGPT 爬取网站数据?
    Java中操作字符串有哪些类?他们之间有什么区别?
    分页功能(板子)
    js获取对象的属性值
    vue3源码分析——实现createRenderer,增加runtime-test
    RDB与AOF持久化【Redis】及缓存雪崩、击穿、穿透
    Vue+Echarts图表动态适配
  • 原文地址:https://blog.csdn.net/scruffybear/article/details/126284017