[Package] RosBridge -- break through the data barrier between Ros and non Ros environments #2, and implement the function ppianpak / rosbridgecpp on the C + + side

Posted by west4me on Tue, 28 Dec 2021 05:49:46 +0100

1 Overview

In the last article [Package] RosBridge -- break through the data barrier between Ros and non Ros environment In this article, I introduced how to conduct data communication between non Ros and Ros systems through RosBridge, and realized half of the communication architecture, that is, the left half of the picture. In this article, I will introduce the open source project on GitHub ppianpak / rosbridgecpp To implement the right half of the architecture and test the effect of the complete communication structure.
ppianpak / rosbridgecpp implements an efficient Client communicating with RosBridge based on C + +, and uses rapidjson for json related operations. The package contains only header files and does not need other dependencies. It is very convenient to transplant and requires cmake3 0+. Let's see how to use it.

2 use

The file structure of ppianpak / rosbridgecpp package is as follows:
Among them, rosbridge_ws_client.cpp and rosbridge_ws_client.hpp implements a simple demo, including Topic subscription and publishing, and service subscription and publishing. Let's look at the code below:

/*
 *  Created on: Apr 16, 2018
 *      Author: Poom Pianpak
 */

#include "rosbridge_ws_client.hpp"

#include <future>

RosbridgeWsClient rbc("localhost:9090");

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;
  }

  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);
}

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);
}

void publisherThread(RosbridgeWsClient& rbc, const std::future<void>& futureObj)
{
  rbc.addClient("topic_publisher");

  rapidjson::Document d;
  d.SetObject();
  d.AddMember("data", "Test message from /ztopic", d.GetAllocator());

  while (futureObj.wait_for(std::chrono::milliseconds(1)) == std::future_status::timeout)
  {
    rbc.publish("/ztopic", d);
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  }

  std::cout << "publisherThread stops()" << std::endl;
}

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;
}

int main() {
  rbc.addClient("service_advertiser");
  rbc.advertiseService("service_advertiser", "/zservice", "std_srvs/SetBool", advertiseServiceCallback);

  rbc.addClient("topic_advertiser");
  rbc.advertise("topic_advertiser", "/ztopic", "std_msgs/String");

  rbc.addClient("topic_subscriber");
  rbc.subscribe("topic_subscriber", "/ztopic", subscriberCallback);

  // Test calling a service
  rapidjson::Document document(rapidjson::kObjectType);
  document.AddMember("data", true, document.GetAllocator());
  rbc.callService("/zservice", callServiceCallback, document);

  // Test creating and stopping a publisher
  {
    // Create a std::promise object
    std::promise<void> exitSignal;

    // Fetch std::future object associated with promise
    std::future<void> futureObj = exitSignal.get_future();

    // Starting Thread & move the future object in lambda function by reference
    std::thread th(&publisherThread, std::ref(rbc), std::cref(futureObj));

    // Wait for 10 sec
    std::this_thread::sleep_for(std::chrono::seconds(10));

    std::cout << "Asking publisherThread to Stop" << std::endl;

    // Set the value in promise
    exitSignal.set_value();

    // Wait for thread to join
    th.join();
  }

  // Test removing clients
  rbc.removeClient("service_advertiser");
  rbc.removeClient("topic_advertiser");
  rbc.removeClient("topic_subscriber");

  std::cout << "Program terminated" << std::endl;
}

The main parts of the distribution are as follows:

RosbridgeWsClient rbc("localhost:9090");

To initialize the object rbc, the construction parameters need to be set to the corresponding ip in the previous RosBridge. If the machine is tested, localhost can be used.
Then the user-defined Service and Topic operation definitions are given:

void advertiseServiceCallback(std::shared_ptr<WsClient::Connection> /*connection*/, std::shared_ptr<WsClient::InMessage> in_message)
void callServiceCallback(std::shared_ptr<WsClient::Connection> connection, std::shared_ptr<WsClient::InMessage> in_message)
void subscriberCallback(std::shared_ptr<WsClient::Connection> /*connection*/, std::shared_ptr<WsClient::InMessage> in_message)
void publisherThread(RosbridgeWsClient& rbc, const std::future<void>& futureObj)

It should be noted that each related operation needs to generate a new thread and give the corresponding ClientName:

rbc.addClient("service_advertiser");
rbc.addClient("topic_advertiser");
rbc.addClient("topic_subscriber");

Inside the class, the author maintains a map to query the corresponding relationship between the name and the client. Therefore, the subsequent operation on the client is to provide the clientName at the time of registration.
Then, the configuration of corresponding operations of the Client is given:

rbc.advertise("topic_advertiser", "/ztopic", "std_msgs/String");

The client with the registered name of "topic_advertiser" is used to publish topics. The published topic is named / ztopic and the data type is std_msgs/String. Such an operation can be registered. It should be noted that the clientName needs to be unique. Subsequent operations cannot be used if they are not registered here.
Similarly, the method of subscribing to topics is as follows:

rbc.subscribe("topic_subscriber", "/ztopic", subscriberCallback);

Then, the author gives an operation example of publishing a 10 second topic and ending the Client in the code segment:

// Test creating and stopping a publisher
  {
    // Create a std::promise object
    std::promise<void> exitSignal;

    // Fetch std::future object associated with promise
    std::future<void> futureObj = exitSignal.get_future();

    // Starting Thread & move the future object in lambda function by reference
    std::thread th(&publisherThread, std::ref(rbc), std::cref(futureObj));

    // Wait for 10 sec
    std::this_thread::sleep_for(std::chrono::seconds(10));

    std::cout << "Asking publisherThread to Stop" << std::endl;

    // Set the value in promise
    exitSignal.set_value();

    // Wait for thread to join
    th.join();
  }

If you want to publish circularly, use

rbc.publish("/ztopic", d,"topic_advertiser");

The topic and clientName should be consistent with the previous registration. d is the document object in rapidjson, which saves the wrapped json data.

3 Effect

Open after compilation [Package] RosBridge -- break through the data barrier between Ros and non Ros environment And run rosbridge_ws_client can implement the communication architecture given at the beginning:

The above figure realizes the "self attack and self receive" of topic "/ ztopic", and stops sending after running for 10 seconds. You can see that the sent data is received back by the subscribed thread.
The following figure shows the operation results of sending virtual laser data in the non ros environment and receiving it in the ros environment, which are sent and received on different machines in the LAN:

It can be seen that the running effect of this architecture is OK. When you need to debug the display or other ros tools, you can send them to the ros environment for rapid debugging.
If this article helps you, I hope you can collect some praise and pay attention to me, and I will continue to update it.

Topics: C++ Linux interface ROS