Closed ikhalip closed 3 years ago
Revision history:
Hi @ikhalip. To all of you who would like documentation on how to use services (in c++) with this plugin, here it is. I hope you all find this useful. If you wanted to use blueprints, I would guess that you would likely have to write some wrapper code in c++ to give you the functionality that you want.
To keep matters simple, I will use the rospy_tutorials/AddTwoInts
service, since this is already provided in the source code. I will also write the code such that the callbacks are member functions of the class.
CAUTION: These callbacks are NOT called in the game thread. It is okay to set member variables inside these callbacks, but UE4 will crash if you attempt to invoke commands on UObjects in these callbacks, such as manually moving the position of an actor with SetActorLocation(...). In this case, I suggest you set flags inside these callbacks that are set to true once you have received the data, and then inside the Tick() function you can act on that new data if the flag is true.
Notes:
I have tested the ROS service feature for this plugin with UE 4.26.1 on a Windows 10 computer and both the client-side and server-side code work. I have not tested the examples I wrote below (I simply made these up for instruction purposes), so do not be alarmed if I have a typo by accident or missed an include statement (although I think this code should be fine).
If you have a UE4 client and a ROS service, then you need to make sure your ROS service is able to process and send a response in less than 5 seconds. If you exceed 5 seconds, then the ROSIntegration plugin will immediately send the UE4 client an empty response field and will ignore whatever the ROS service replies. I believe this is because the ROSIntegration plugin constantly monitors its health every 5 seconds. If you experience this, then to get around this issue you should instead split up the service call into 2 service calls. in which sending the request is its own service call (UE4 --> ROS) and sending the response is its own service call (ROS --> UE4).
You are also welcome to use this code structure when creating UTopics.
You have a UE4 actor (or component) that you want to act as the client. Header file: ClientActor.h
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ROSIntegration/Public/ROSBaseServiceResponse.h"
#include "ClientActor.generated.h"
UCLASS()
class YOURAWESOME_API AClientActor: public AActor
{
GENERATED_BODY()
public:
// Your public code here...
protected:
virtual void BeginPlay() override;
private:
// Your private code here...
UPROPERTY()
class UROSIntegrationGameInstance* ROSInst;
UPROPERTY()
class UService* AddTwoIntsClient;
void AddTwoIntsResponseCB(TSharedPtr<FROSBaseServiceResponse> Response);
};
Source file: ClientActor.cpp
#include "ClientActor.h"
#include "ROSIntegration/Classes/ROSIntegrationGameInstance.h"
#include "ROSIntegration/Classes/RI/Service.h"
#include "rospy_tutorials/AddTwoIntsRequest.h"
#include "rospy_tutorials/AddTwoIntsResponse.h"
// Rest of your code
void AClientActor::BeginPlay()
{
Super::BeginPlay();
ROSInst = Cast<UROSIntegrationGameInstance>(GetGameInstance());
if (ROSInst)
{
AddTwoIntsClient = NewObject<UService>(UService::StaticClass());
AddTwoIntsClient->Init(ROSInst->ROSIntegrationCore, TEXT("/add_two_ints"), TEXT("rospy_tutorials/AddTwoInts"));
}
}
// For simplicity, I am putting the code to request the service in some member function.
void AClientActor::CallService()
{
if (ROSInst)
{
TSharedPtr<rospy_tutorials::FAddTwoIntsRequest> Request(new rospy_tutorials::FAddTwoIntsRequest());
Request->_a = 1.0;
Request->_b = 2.0;
AddTwoIntsClient->CallService(Request, std::bind(&AClientActor::AddTwoIntsResponseCB, this, std::placeholders::_1));
}
}
void AClientActor::AddTwoIntsResponseCB(TSharedPtr<FROSBaseServiceResponse> Response)
{
auto CastResponse = StaticCastSharedPtr<rospy_tutorials::FAddTwoIntsResponse>(Response);
if (!CastResponse)
{
UE_LOG(LogTemp, Warning, TEXT("Failed to cast Response to rospy_tutorials/AddTwoIntsResponse."));
return;
}
UE_LOG(LogTemp, Warning, TEXT("The sum is: %f"), CastResponse->_sum);
}
You have a UE4 actor (or component) that you want to act as the server. Header file: ServerActor.h
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ROSIntegration/Public/ROSBaseServiceRequest.h"
#include "ROSIntegration/Public/ROSBaseServiceResponse.h"
#include "ServerActor.generated.h"
UCLASS()
class YOURAWESOME_API AServerActor: public AActor
{
GENERATED_BODY()
public:
// Your public code here...
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private:
// Your private code here...
UPROPERTY()
class UROSIntegrationGameInstance* ROSInst;
UPROPERTY()
class UService* AddTwoIntsServer;
void AddTwoIntsServerCB(TSharedPtr<FROSBaseServiceRequest> Request, TSharedPtr<FROSBaseServiceResponse> Response);
};
Source file: ServerActor.cpp
#include "ServerActor.h"
#include "ROSIntegration/Classes/ROSIntegrationGameInstance.h"
#include "ROSIntegration/Classes/RI/Service.h"
#include "rospy_tutorials/AddTwoIntsRequest.h"
#include "rospy_tutorials/AddTwoIntsResponse.h"
// Rest of your code
void AServerActor::BeginPlay()
{
Super::BeginPlay();
ROSInst = Cast<UROSIntegrationGameInstance>(GetGameInstance());
if (ROSInst)
{
AddTwoIntsServer = NewObject<UService>(UService::StaticClass());
AddTwoIntsServer->Init(ROSInst->ROSIntegrationCore, TEXT("/add_two_ints"), TEXT("rospy_tutorials/AddTwoInts"));
// The second param indicates if we wish to execute the callback in the game thread.
// I chose false, to be consistent with how topic callbacks work.
AddTwoIntsServer->Advertise(std::bind(&AServerAActor::AddTwoIntsServerCB, this, std::placeholders::_1, std::placeholders::_2), false);
}
}
void AServerActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
AddTwoIntsServer->Unadvertise(); // If you do not unadvertise/unsubscribe in EndPlay, weird behavior may arise
}
void AServerActor::AddTwoIntsServerCB(TSharedPtr<FROSBaseServiceRequest> Request, TSharedPtr<FROSBaseServiceResponse> Response)
{
auto CastRequest = StaticCastSharedPtr<rospy_tutorials::FAddTwoIntsRequest>(Request);
auto CastResponse = StaticCastSharedPtr<rospy_tutorials::FAddTwoIntsResponse>(Response);
if (!CastRequest)
{
UE_LOG(LogTemp, Warning, TEXT("Failed to cast Request to rospy_tutorials/AddTwoIntsRequest."));
return;
}
if (!CastResponse)
{
UE_LOG(LogTemp, Warning, TEXT("Failed to cast Response to rospy_tutorials/AddTwoIntsResponse."));
return;
}
CastResponse->_sum = CastRequest->_a + CastRequest->_b;
}
Hi @tsender ! Thanks for providing these examples. I've linked to your post from the main README.md of this repo 👍
With respect to executing on the game thread: You can also check out AsyncTasks: https://answers.unrealengine.com/questions/548541/view.html
Currently there isn't much if any documentation regarding services and how to use them. It would be great to see some c++ and or blueprint examples/documentation.