[mrpt-comms]

Communication utilities: serial ports, networking (TCP, DNS,…), pub/sub nodelets.

Library mrpt-comms

This C++ library is part of MRPT and can be installed in Debian-based systems with:

sudo apt install libmrpt-comms-dev

Read also how to import MRPT into your CMake scripts.

Find below some examples of use.

Nodelets-like Pub/Sub mechanism

MRPT provides a Publisher/Subscriber (Pub/Sub) pattern implementation for intra-process (multiple threads) zero-copy super fast communication.

Thread creation and handling is the responsibility of the user.

The main concepts are:

  • A topic : the name of a shared variable. If you are familiar with ROS, you already know what a topic is. Topics are identified by unique strings, e.g. odometry, scan.

  • Directory: The central hub that holds all references to existing topics. Usually only one should exist per process, but there is no actual limitation.

  • Subscriber : An object that binds to a given topic and allows a user-given function to be called whenever new data is published to the topic.

  • Publish: The operation of publishing a new data piece to a named topic. It’s important to note that the call is blocking : all subscribers are executed from the thread invoking publish(). Users are encouraged to design subscribers such that their execution time are minimized, delegating heavy computation to other worker threads.

All these concepts are illustrated in the example below. Note that different subscribers are created: with a lambda, with a regular function, a std::bind(), etc.

See: comms_nodelets_example/NodeletsTest_impl.cpp

#include <mrpt/comms/nodelets.h>
#include <mrpt/math/TPose3D.h>

#include <chrono>
#include <cstdio>  // printf()
#include <iostream>
#include <thread>

// Test payload:
const mrpt::math::TPose3D p_tx(1.0, 2.0, 3.0, 0.2, 0.4, 0.6);

// Create the topic directory. Only once per process, and must be shared
// by all nodelets/threads.
auto dir = mrpt::comms::TopicDirectory::create();

void thread_publisher()
{
    using namespace mrpt::comms;
    using namespace std;

    try
    {
#ifdef NODELETS_TEST_VERBOSE
        printf("[publisher] Started\n");
#endif

        for (int i = 0; i < 5; i++)
        {
            std::this_thread::sleep_for(100ms);
            dir->getTopic("/robot/odom")->publish(p_tx);
        }

#ifdef NODELETS_TEST_VERBOSE
        printf("[publisher] Finish\n");
#endif
    }
    catch (const std::exception& e)
    {
        cerr << e.what() << endl;
    }
    catch (...)
    {
        printf("[thread_publisher] Runtime error!\n");
    }
}

void onNewMsg(const mrpt::math::TPose3D& p)
{
#ifdef NODELETS_TEST_VERBOSE
    std::cout << "sub2: rx TPose3D" << p.asString() << std::endl;
#endif
}

void onNewMsg2(int idx, const mrpt::math::TPose3D& p)
{
#ifdef NODELETS_TEST_VERBOSE
    std::cout << "onNewMsg2: idx=" << idx << " rx TPose3D" << p.asString()
              << std::endl;
#endif
}

void thread_subscriber()
{
    using namespace mrpt::comms;
    using namespace std;

    try
    {
#ifdef NODELETS_TEST_VERBOSE
        printf("[subscriber] Connecting\n");
#endif

#ifdef NODELETS_TEST_VERBOSE
        printf("[subscriber] Connected. Waiting for a message...\n");
#endif

        // Create a subscriber with a lambda:
        Subscriber::Ptr sub1 =
            dir->getTopic("/robot/odom")
                ->createSubscriber<mrpt::math::TPose3D>(
                    [](const mrpt::math::TPose3D& p_rx) -> void {
#ifdef NODELETS_TEST_VERBOSE
                        std::cout << "sub1: rx TPose3D" << p_rx.asString()
                                  << std::endl;
#endif
                        nodelets_test_passed_ok = (p_rx == p_tx);
                    });

        // Create a subscriber with a regular function via std::function:
        auto sub2 =
            dir->getTopic("/robot/odom")
                ->createSubscriber<mrpt::math::TPose3D>(
                    std::function<void(const mrpt::math::TPose3D&)>(&onNewMsg));

        // Create a subscriber with a regular function:
        auto sub3 = dir->getTopic("/robot/odom")
                        ->createSubscriber<mrpt::math::TPose3D>(&onNewMsg);

        // Create a subscriber with std::bind:
        using namespace std::placeholders;
        auto sub4 = dir->getTopic("/robot/odom")
                        ->createSubscriber<mrpt::math::TPose3D>(
                            [](auto&& arg1) { return onNewMsg2(123, arg1); });

        // wait for messages to arrive.
        // The nodelet is up and live until "sub" gets out of scope.
        std::this_thread::sleep_for(2000ms);

#ifdef NODELETS_TEST_VERBOSE
        printf("[subscriber] Finish\n");
#endif
    }
    catch (const std::exception& e)
    {
        cerr << e.what() << endl;
    }
    catch (...)
    {
        cerr << "[thread_subscriber] Runtime error!" << endl;
    }
}

void NodeletsTest()
{
    using namespace std::chrono_literals;

    std::thread(thread_publisher).detach();
    std::thread(thread_subscriber).detach();
    std::this_thread::sleep_for(1000ms);
}

HTTP request methods

mrpt::comms::net::http_get() is an easy way to GET an HTTP resource from any C++ program. You can also use mrpt::comms::net::http_request() to access APIs requiring the POST method.

See: comms_http_client/test.cpp

#include <mrpt/comms/net_utils.h>
#include <mrpt/core/exceptions.h>

#include <iostream>

using namespace mrpt;
using namespace mrpt::comms;
using namespace mrpt::comms::net;

std::string url = "http://www.google.es/";

void Test_HTTP_get()
{
    std::string content;

    mrpt::comms::net::HttpRequestOptions httpOptions;
    mrpt::comms::net::HttpRequestOutput httpOut;

    std::cout << "Retrieving " << url << "..." << std::endl;

    http_errorcode ret = http_get(url, content, httpOptions, httpOut);

    if (ret != net::http_errorcode::Ok)
    {
        std::cout << " Error: " << httpOut.errormsg << std::endl;
        return;
    }

    string typ = httpOut.out_headers.count("Content-Type")
        ? httpOut.out_headers.at("Content-Type")
        : string("???");

    std::cout << "Ok: " << content.size() << " bytes of type: " << typ
              << std::endl;
}

Library contents

// namespaces

namespace mrpt::comms::net;

// enums

enum mrpt::comms::net::http_errorcode;

// structs

struct mrpt::comms::net::HttpRequestOptions;
struct mrpt::comms::net::HttpRequestOutput;
struct mrpt::comms::TFTDIDevice;

// classes

class mrpt::comms::CClientTCPSocket;
class mrpt::comms::CInterfaceFTDI;
class mrpt::comms::CSerialPort;
class mrpt::comms::CServerTCPSocket;
class mrpt::comms::Subscriber;

// global functions

http_errorcode mrpt::comms::net::http_get(
    const string& url,
    std::vector<uint8_t>& out_content,
    const HttpRequestOptions& options = HttpRequestOptions(),
    mrpt::optional_ref<HttpRequestOutput> output = std::nullopt
    );

http_errorcode mrpt::comms::net::http_get(
    const string& url,
    string& out_content,
    const HttpRequestOptions& options = HttpRequestOptions(),
    mrpt::optional_ref<HttpRequestOutput> output = std::nullopt
    );

http_errorcode mrpt::comms::net::http_request(
    const string& http_method,
    const string& http_send_content,
    const string& url,
    std::vector<uint8_t>& out_content,
    const HttpRequestOptions& options = HttpRequestOptions(),
    mrpt::optional_ref<HttpRequestOutput> output = std::nullopt
    );

bool mrpt::comms::net::DNS_resolve_async(
    const std::string& server_name,
    std::string& out_ip,
    const unsigned int timeout_ms = 3000
    );

std::string mrpt::comms::net::getLastSocketErrorStr();

bool mrpt::comms::net::Ping(
    const std::string& address,
    const int max_attempts,
    std::string* output_str = nullptr
    );

Global Functions

http_errorcode mrpt::comms::net::http_get(
    const string& url,
    std::vector<uint8_t>& out_content,
    const HttpRequestOptions& options = HttpRequestOptions(),
    mrpt::optional_ref<HttpRequestOutput> output = std::nullopt
    )

Perform an HTTP GET operation (version for retrieving the data as a std::vector<uint8_t>)

Parameters:

url

Must be a simple string of the form “http://<servername>/<relative-address>”.

port

The server port, if different from 80.

extra_headers

If provided, the given extra HTTP headers will be sent.

errormsg

On exit will contain a description of the error or “Ok”.

out_content

The buffer with the retrieved data.

out_http_responsecode

If provided, will hold the HTTP code, eg: 200, 404…

out_headers

If provided, a copy of all the headers returned by the server will be saved here.

auth_user

auth_pass

Send a basic HTTP authorization request with the given user & password.

Returns:

The error or success code.

See also:

http_request()

http_errorcode mrpt::comms::net::http_request(
    const string& http_method,
    const string& http_send_content,
    const string& url,
    std::vector<uint8_t>& out_content,
    const HttpRequestOptions& options = HttpRequestOptions(),
    mrpt::optional_ref<HttpRequestOutput> output = std::nullopt
    )

Generic function for HTTP GET & POST methods.

See also:

http_get

bool mrpt::comms::net::DNS_resolve_async(
    const std::string& server_name,
    std::string& out_ip,
    const unsigned int timeout_ms = 3000
    )

Resolve a server address by its name, returning its IP address as a string - This method has a timeout for the maximum time to wait for the DNS server.

For example: server_name=”www.google.com” -> out_ip=”209.85.227.99”

Returns:

true on success, false on timeout or other error.

std::string mrpt::comms::net::getLastSocketErrorStr()

Returns a description of the last Sockets error.

bool mrpt::comms::net::Ping(
    const std::string& address,
    const int max_attempts,
    std::string* output_str = nullptr
    )

Ping an IP address.

{ I am redirecting stderr to stdout, so that the overall process is simplified. Otherwise see: https://jineshkj.wordpress.com/2006/12/22/how-to-capture-stdin-stdout-and-stderr-of-child-program/}

Parameters:

address

Address to ping.

max_attempts

Number of attempts to try and ping.

output

String containing output information

Returns:

True if responsive, false otherwise.