Service Server Example

Please follow the instructions in Setup and run UE Project to setup the UE project and open ROS2ServiceExample.umap.

C++ Service Server

Code

//ROS2ServiceServerNode.h

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

public:
    AROS2ServiceServerNode();

    virtual void BeginPlay() override;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString ServiceName = TEXT("add_two_ints");

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

    /**
    * @brief Service callback function
    *
    * @param Service
    */
    UFUNCTION()
    void SrvCallback(UROS2GenericSrv* InService);
};
//AROS2ServiceServerNode.cpp

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

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

void AROS2ServiceServerNode::BeginPlay()
{
    Super::BeginPlay();
    Node->Init();

    ROS2_CREATE_SERVICE_SERVER(Node, this, ServiceName, UROS2AddTwoIntsSrv::StaticClass(), &AROS2ServiceServerNode::SrvCallback);
}

void AROS2ServiceServerNode::SrvCallback(UROS2GenericSrv* InService)
{
    UROS2AddTwoIntsSrv* AddTwoIntsService = Cast<UROS2AddTwoIntsSrv>(InService);

    FROSAddTwoIntsReq req;
    AddTwoIntsService->GetRequest(req);

    // Add two ints.
    FROSAddTwoIntsRes res;
    res.Sum = req.A + req.B;

    // Set response.
    AddTwoIntsService->SetResponse(res);

    // Log request and response
    UE_LOG_WITH_INFO_NAMED(
        LogTurtlebot3, Log, TEXT("[%s][C++][receive request] %d + %d = %d"), *ServiceName, req.A, req.B, res.Sum);
}

Code explanations

On an AROS2ServiceServerNode Actor, similar to the AROS2PublisherNode, NodeComponent is created and initialized in the constructor but ROS2 Node is not created here. Please check Code explanations for the reason.

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

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

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

void AROS2ServiceServerNode::BeginPlay()
{
    Super::BeginPlay();
    Node->Init();

You can create a service server by using the ROS2_CREATE_SERVICE_SERVER macro, which creates a service server and adds it to the node. When the node receives a service request, AROS2ServiceServerNode::SrvCallback is called.

ROS2_CREATE_SERVICE_SERVER(Node, this, ServiceName, UROS2AddTwoIntsSrv::StaticClass(), &AROS2ServiceServerNode::SrvCallback);

The SrvCallback method adds two integers and sets the result to the response.

To retrieve the request, you need to create a request structure (FROSAddTwoIntsReq) for the corresponding service (UROS2AddTwoIntsSrv) and retrieve the request by calling GetRequest().

To set the response, you need to create a response structure (FROSAddTwoIntsResp) for the corresponding service (UROS2AddTwoIntsSrv) and set the response to the structure. Then, call SetResponse() to set the structure to the response.

Finally, log the request and the response.

void AROS2ServiceServerNode::SrvCallback(UROS2GenericSrv* InService)
{
    UROS2AddTwoIntsSrv* AddTwoIntsService = Cast<UROS2AddTwoIntsSrv>(InService);

    FROSAddTwoIntsReq req;
    AddTwoIntsService->GetRequest(req);

    // Add two ints.
    FROSAddTwoIntsRes res;
    res.Sum = req.A + req.B;

    // Set response.
    AddTwoIntsService->SetResponse(res);

    // Log request and response
    UE_LOG_WITH_INFO_NAMED(
        LogTurtlebot3, Log, TEXT("[%s][C++][receive request] %d + %d = %d"), *ServiceName, req.A, req.B, res.Sum);
}

The implementation of ROS2_CREATE_SERVICE_SERVER is as follows. It uses Unreal Engine’s dynamic delegate to call the bound function when the node receives the message. You can find more information about Unreal Engine’s dynamic delegate here.

DECLARE_DYNAMIC_DELEGATE_OneParam(FServiceCallback, UROS2GenericSrv*, InService /*Service*/);

#define ROS2_CREATE_SERVICE_SERVER(InROS2Node, InUserObject, InServiceName, InSrvClass, InResponseCallback) \
    if (ensure(IsValid(InROS2Node)))                                                                        \
    {                                                                                                       \
        FServiceCallback res;                                                                               \
        res.BindDynamic(InUserObject, InResponseCallback);                                                  \
        InROS2Node->CreateServiceServer(InServiceName, InSrvClass, res);                                    \
    }

BP Service Server

Blueprint implementation of a service server 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).

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

../_images/service_server_overview.png

The Service server component is attached to an Actor, which is displayed in the Components panel on the left.

../_images/service_server_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.

Compared to C++, which uses ROS2_CREATE_SUBSCRIBER, in Blueprint, the Subscriber is already generated as a Component before BeginPlay. Therefore, we use UROS2NodeComponent::AddServiceServer to initialize the UROS2ServiceServer and UROS2ServiceServer::SetDelegates to bind callback method instead. The ROS2_CREATE_SERVICE_SERVER macro in C++ internally calls CreateServiceServer which calls AddServiceServer and SetDelegates.

../_images/service_server_req.png

Callback function is bound to a custom event, indicated by the red node on the left. This callback function is called when the node receives a request and send response.