Publisher Example

Please follow the instructions in Setup and run UE Project to set up the UE project and open ROS2TopicExample.umap.

C++ Publisher

Code

//ROS2PublisherNode.h

UCLASS()
class TURTLEBOT3_API AROS2PublisherNode : public AActor
{
    GENERATED_BODY()

public:
    AROS2PublisherNode();

    virtual void BeginPlay() override;

    UFUNCTION()
    void UpdateMessage(UROS2GenericMsg* InMessage);

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UROS2NodeComponent* Node = nullptr;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UROS2Publisher* Publisher = nullptr;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UROS2Publisher* LoopPublisher = nullptr;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    URRROS2StringPublisher* StringPublisher = nullptr;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString TopicName = TEXT("test_topic");

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float PublicationFrequencyHz = 1.f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString Message = TEXT("Hello from C++");

    UPROPERTY()
    int32 Count = 0;
};
//ROS2PublisherNode.cpp

AROS2PublisherNode::AROS2PublisherNode()
{
    Node = CreateDefaultSubobject<UROS2NodeComponent>(TEXT("ROS2NodeComponent"));

    // these parameters can be change from BP
    Node->Name = TEXT("publisher_node");
    Node->Namespace = TEXT("cpp");
}

void AROS2PublisherNode::BeginPlay()
{
    Super::BeginPlay();

    Node->Init();

    // Create publisher with 3 different ways.
    // 1. Non Loop Publisher
    // 2. Loop Publisher
    // 3. Custom Publisher class

    // 1. Non Loop Publisher
    // 1.1 Create publisher
    Publisher = Node->CreatePublisher(TopicName, UROS2Publisher::StaticClass(), UROS2StrMsg::StaticClass(), UROS2QoS::KeepLast);

    // 1.2 Create msg
    FROSStr msg;
    msg.Data = FString::Printf(TEXT("%s from non loop publisher"), *Message);
    CastChecked<UROS2StrMsg>(Publisher->TopicMessage)->SetMsg(msg);

    // 1.3 publish
    Publisher->Publish();

    // 2. Loop Publisher
    ROS2_CREATE_LOOP_PUBLISHER_WITH_QOS(Node,
                                        this,
                                        TopicName,
                                        UROS2Publisher::StaticClass(),
                                        UROS2StrMsg::StaticClass(),
                                        PublicationFrequencyHz,
                                        &AROS2PublisherNode::UpdateMessage,
                                        UROS2QoS::Default,
                                        LoopPublisher);

    // 3. Use Custom Publisher class
    // UpdateMessage is overriden in child class.
    StringPublisher = CastChecked<URRROS2StringPublisher>(
        Node->CreateLoopPublisherWithClass(TopicName, URRROS2StringPublisher::StaticClass(), 1.f));
    StringPublisher->Message = FString::Printf(TEXT("%s from custom class"), *Message);
}

void AROS2PublisherNode::UpdateMessage(UROS2GenericMsg* InMessage)
{
    FROSStr msg;
    msg.Data = FString::Printf(TEXT("%s %d"), *Message, Count++);
    CastChecked<UROS2StrMsg>(InMessage)->SetMsg(msg);
}

Code explanations

In the constructor, the UROS2NodeComponent is created using CreateDefaultSubobject and its parameters such as Node Name and Namespace are set.

Although the UROS2NodeComponent is created, the ROS2 Node is not yet initialized at this point. Initialization of the ROS2 Node occurs during the BeginPlay method, which is called when the simulation starts.

One reason to initialize in BeginPlay is to create a ROS2 Node when the simulation starts. It is important to note that the UE Class constructor may also be called before simulation starts, for example, when an Actor is placed in the level. For a better understanding of Actor Lifecycle, please refer to the UE Actor LifeCycle However, understanding the entire lifecycle is not necessary to use rclUE.

Another significant difference is that by initializing in BeginPlay, variables such as publication frequency can be set in the editor and their changes will be reflected when running the simulation. If, in contrast, we initialize things in the constructor, variables changed in the editor would not reflect in the simulation, unless we restart the editor.

AROS2PublisherNode::AROS2PublisherNode()
{
    Node = CreateDefaultSubobject<UROS2NodeComponent>(TEXT("ROS2NodeComponent"));

    // these parameters can be change from BP
    Node->Name = TEXT("publisher_node");
    Node->Namespace = TEXT("cpp");
}

When simulation starts, BeginPlay is called. In the BeginPlay, firstly create and initialize ROS2 Node by calling UROS2NodeComponent::Init .

void AROS2PublisherNode::BeginPlay()
{
    Super::BeginPlay();

    Node->Init();

then starts adding publishers. We introduce 3 ways to add publisher here.

Non Loop Publisher

This will create a publisher and publish a message once. It is a straightforward way to publish a message, involving creating a publisher, creating a message, and publishing the message.

To create a publisher, use UROS2NodeComponent::CreatePublisher

Create a structure (FROSStr) for the contrasting message (UROSStrMsg) and assign characters to the Data field. Then, assign the structure to the message using the SetMsg method.

Finally, call the UROS2Publisher::Publish method to publish the topic.

// 1. Non Loop Publisher
// 1.1 Create publisher
Publisher = Node->CreatePublisher(TopicName, UROS2Publisher::StaticClass(), UROS2StrMsg::StaticClass(), UROS2QoS::KeepLast);

// 1.2 Create msg
FROSStr msg;
msg.Data = FString::Printf(TEXT("%s from non loop publisher"), *Message);
CastChecked<UROS2StrMsg>(Publisher->TopicMessage)->SetMsg(msg);

// 1.3 publish
Publisher->Publish();

Loop Publisher

You can create a loop publisher by using the ROS2_CREATE_LOOP_PUBLISHER_WITH_QOS macro. This macro creates a publisher and adds it to the node, and then it periodically calls the AROS2PublisherNode::UpdateMessage method.

// 2. Loop Publisher
ROS2_CREATE_LOOP_PUBLISHER_WITH_QOS(Node,
                                    this,
                                    TopicName,
                                    UROS2Publisher::StaticClass(),
                                    UROS2StrMsg::StaticClass(),
                                    PublicationFrequencyHz,
                                    &AROS2PublisherNode::UpdateMessage,
                                    UROS2QoS::Default,
                                    LoopPublisher);

UpdateMessage method does similar things as a non-loop publisher. Since the loop publisher automatically calls the “Publish()” method, you only need to create and set the message.

void AROS2PublisherNode::UpdateMessage(UROS2GenericMsg* InMessage)
{
    FROSStr msg;
    msg.Data = FString::Printf(TEXT("%s %d"), *Message, Count++);
    CastChecked<UROS2StrMsg>(InMessage)->SetMsg(msg);
}

The implementation of ROS2_CREATE_LOOP_PUBLISHER_WITH_QOS is as follows. It uses Unreal Engine’s dynamic delegate to call a bound function periodically. You can find more information about Unreal Engine’s dynamic delegate here.

DECLARE_DYNAMIC_DELEGATE_OneParam(FTopicCallback, UROS2GenericMsg*, InTopicMessage);

#define ROS2_CREATE_LOOP_PUBLISHER_WITH_QOS(                                                                                  \
    InROS2Node, InUserObject, InTopicName, InPublisherClass, InMsgClass, InPubFrequency, InCallback, InQoS, OutPublisher)     \
    if (ensure(IsValid(InROS2Node)))                                                                                          \
    {                                                                                                                         \
        FTopicCallback cb;                                                                                                    \
        cb.BindDynamic(InUserObject, InCallback);                                                                             \
        OutPublisher = InROS2Node->CreateLoopPublisher(InTopicName, InPublisherClass, InMsgClass, InPubFrequency, cb, InQoS); \
    }

Custom Publisher class

You can create a publisher by defining a child class of UROS2Publisher. This is useful when you want to add some custom logic to the publisher that will be used in multiple places.

// 3. Use Custom Publisher class
// UpdateMessage is overriden in child class.
StringPublisher = CastChecked<URRROS2StringPublisher>(
    Node->CreateLoopPublisherWithClass(TopicName, URRROS2StringPublisher::StaticClass(), 1.f));
StringPublisher->Message = FString::Printf(TEXT("%s from custom class"), *Message);

The implementation of UROS2StringPublisher is as follows. UROS2StringPublisher is a child class of UROS2Publisher.

The constructor is used to set default publisher settings. You can add user-defined logic by overriding the UpdateMessage method, which is called periodically if PublicationFrequencyHz > 0.

// RRROS2StringPublisher.cpp
#include "Msgs/ROS2Str.h"

URRROS2StringPublisher::URRROS2StringPublisher()
{
    MsgClass = UROS2StrMsg::StaticClass();
    PublicationFrequencyHz = 1;
    QoS = UROS2QoS::DynamicBroadcaster;
    SetDefaultDelegates();    //use UpdateMessage as update delegate
}

void URRROS2StringPublisher::UpdateMessage(UROS2GenericMsg* InMessage)
{
    FROSStr msg;
    msg.Data = Message;
    CastChecked<UROS2StrMsg>(InMessage)->SetMsg(msg);
}

BP Publisher

Blueprint implementation of a publisher is very similar to a C++ implementation. Blueprints allow you to set logic/processes, parameters, and other details from the editor.

You can add component such as UROS2Publisher from Components panel in the editor(left side in the fig below) and set each component parameters in Details panel in the editor(right side in the fig below).

* Please enlarge the image if the font is too small, or open the BP class in the editor.

Overview of BP Publisher

../_images/publisher_overview.png

Similar to the C++ implementation, the BP class has three types of publishers: Non-Loop Publisher, Loop Publisher, and Custom Publisher.

The main difference from the C++ implementation is that it uses UROS2PublisherComponent instead of UROS2Publisher. As UROS2PublisherComponent is a child class of UActorComponent and has UROS2Publisher as a member variable, you can easily add it to the Actor and set parameters from the editor.

Each publisher is attached to an Actor, which is displayed in the Components panel on the left.

Details of BP Publisher

../_images/publisher_node.png

Initialize the ROS2 Node using the BeginPlay event. You can set the ROSNode parameters, such as Name and Namespace, from the Details panel on the right.

Non Loop Publisher

../_images/publisher_non_loop.png

Compared to C++, which uses CreatePublisher(), in Blueprint, the Publisher is already generated as a Component before BeginPlay. Therefore, we use UROS2NodeComponent::AddPublisher <../doxygen_generated/html/d7/d68/class_u_r_o_s2_node_component.html#a582299af64efaaa34b046c00b1c96828>_ to initialize the Publisher instead. The CreatePublisher function in C++ internally calls AddPublisher.

You can see that parameters such as Topic Name, Publisher Class, and Msg Class are set in the right Details panel.

Loop Publisher

../_images/publisher_loop.png

Callback function is bound to a custom event, indicated by the red node in the center. This callback function is executed at regular intervals with a specified frequency.

Custom Publisher class

../_images/publisher_custom.png

To set parameters for a custom publisher object, we need to create a variable of the custom publisher class and then assign it to the publisher component.