在 C++ 中使用传感器与接口
需要:uBotSensor 0.1.x(含依赖 uBotCore)
在自己的 C++ 模块里依赖 uBot、查找机器人上的传感器、通过接口读取数据和下发速度指令,并让自己的类实现这些接口。
调用传感器和接口
Section titled “调用传感器和接口”-
添加模块依赖
在自己模块的
Build.cs里加入UBotCore和UBotSensor。只用接口、时钟和身份(Identity)时,只加UBotCore就够了。PublicDependencyModuleNames.AddRange(new[]{"Core", "CoreUObject", "Engine", "UBotCore", "UBotSensor"});插件要先在工程里启用。你自己的插件还要在
.uplugin里声明依赖,见编写自己的 uBot Package。 -
查找传感器并调用接口
UBot::Sensors::FindSensors返回 Actor 自身(如果它实现了接口)和所有实现IUBotSensor的组件,按组件顺序排列,不搜索子 Actor(Child Actor)。接口函数用IUBotSensor::Execute_*调用,这样 C++ 和蓝图写的实现都能走到。#include "GameFramework/Actor.h"#include "UBotSensorInterface.h"void LogSensors(const AActor* Robot){TArray<UObject*> Sensors;UBot::Sensors::FindSensors(Robot, Sensors);for (UObject* Sensor : Sensors){const FString Id = IUBotSensor::Execute_GetSensorId(Sensor);const FName Type = IUBotSensor::Execute_GetSensorType(Sensor); // 例如 IMU、Lidar2Dconst FUBotFrameHeader Header = IUBotSensor::Execute_GetLatestSensorHeader(Sensor);UE_LOG(LogTemp, Log, TEXT("%s (%s): sequence %lld"), *Id, *Type.ToString(), Header.Sequence);}} -
读取某一种传感器的数据
把
FindSensors返回的对象转成具体的组件类,就能用它自己的函数。GetLatestIMUData返回最近一次样本,不会触发新的采样。#include "UBotImuSensorComponent.h"void LogGyro(UObject* Sensor){if (const UUBotImuSensorComponent* Imu = Cast<UUBotImuSensorComponent>(Sensor)){const FUBotImuSensorData Data = Imu->GetLatestIMUData();if (Data.Header.bValid){UE_LOG(LogTemp, Log, TEXT("gyro (rad/s): %s"), *Data.AngularVelocityRadPerSecond.ToString());}}} -
下发速度指令
UBot::Actuation::FindVelocityCommandable先找 Actor 本身,再找它的组件,没有就返回nullptr。FUBotVelocityCommand在机体系下使用 UE 坐标轴(X 前、Y 右、Z 上):线速度单位 m/s,角速度单位 rad/s,+Z 为向右偏航。#include "GameFramework/Actor.h"#include "UBotActuatorInterface.h"#include "UBotClock.h"bool DriveForward(AActor* Robot){UObject* Drive = UBot::Actuation::FindVelocityCommandable(Robot);if (!Drive){return false;}FUBotVelocityCommand Command;Command.LinearVelocityMetersPerSecond = FVector(0.5, 0.0, 0.0);Command.AngularVelocityRadiansPerSecond = FVector(0.0, 0.0, 0.2);Command.TimestampSeconds = UBot::Clock::GetSimTimeSeconds(Robot);Command.Source = TEXT("MyCode"); // 自由格式的来源标签return IUBotVelocityCommandable::Execute_ApplyVelocityCommand(Drive, Command); // false 表示被拒绝}其他坐标系的数据要先自己转换再填,转换函数见 UE/ROS 约定库。
-
读取仿真时间和机器人标识
所有 uBot 时间戳都用
UBot::Clock::GetSimTimeSeconds,它的基准是UWorld::GetTimeSeconds(),遵循暂停和时间膨胀。机器人的名字取自AActor::GetName(),不是编辑器里的标签,所以打包后结果不变。#include "GameFramework/Actor.h"#include "UBotClock.h"#include "UBotIdentityComponent.h"void LogIdentity(const AActor* Robot){const double Now = UBot::Clock::GetSimTimeSeconds(Robot); // 秒;没有 World 时为 0const FString RobotId = UBot::Identity::ResolveRobotId(Robot); // 没有 uBot Identity 组件时用 Actor 名const FString RobotNamespace = UBot::Identity::ResolveNamespace(Robot); // 没有组件时用净化后的 Actor 名const FString BaseFrame = UBot::Identity::ResolveBaseFrameId(Robot); // 没有组件时是 base_linkUE_LOG(LogTemp, Log, TEXT("t=%.3f %s ns=%s base=%s"), Now, *RobotId, *RobotNamespace, *BaseFrame);}
在 C++ 里实现接口
Section titled “在 C++ 里实现接口”-
实现传感器接口
让组件继承
IUBotSensor,它就能被FindSensors找到,uBotROS 也是靠这个接口发现传感器的。接口函数是BlueprintNativeEvent,在 C++ 里重写它们的_Implementation版本。// MyRangeSensorComponent.h(模块 MyRangeSensor)#pragma once#include "CoreMinimal.h"#include "Components/SceneComponent.h"#include "UBotSensorInterface.h"#include "UBotTypes.h"#include "MyRangeSensorComponent.generated.h"USTRUCT(BlueprintType)struct MYRANGESENSOR_API FMyRangeData{GENERATED_BODY()UPROPERTY(BlueprintReadOnly, Category = "MyRange")FUBotFrameHeader Header;UPROPERTY(BlueprintReadOnly, Category = "MyRange")float FieldOfViewRadians = 0.0f;UPROPERTY(BlueprintReadOnly, Category = "MyRange")float MinRangeMeters = 0.0f;UPROPERTY(BlueprintReadOnly, Category = "MyRange")float MaxRangeMeters = 0.0f;UPROPERTY(BlueprintReadOnly, Category = "MyRange")float RangeMeters = 0.0f;};DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyRangeEvent, const FMyRangeData&, Data);UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))class MYRANGESENSOR_API UMyRangeSensorComponent : public USceneComponent, public IUBotSensor{GENERATED_BODY()public:UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MyRange")FString SensorId = TEXT("range");UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MyRange")FString FrameId = TEXT("range_link");UPROPERTY(BlueprintAssignable, Category = "MyRange")FMyRangeEvent OnRangeUpdated;/** 记录一个样本并触发 OnRangeUpdated。 */void PublishSample(float RangeMeters);//~ Begin IUBotSensor Interfacevirtual FString GetSensorId_Implementation() const override { return SensorId; }virtual FString GetSensorFrameId_Implementation() const override { return FrameId; }virtual FName GetSensorType_Implementation() const override { return TEXT("Range"); }virtual bool IsSensorRunning_Implementation() const override { return true; }virtual FUBotFrameHeader GetLatestSensorHeader_Implementation() const override { return LatestHeader; }virtual USceneComponent* GetSensorFrameComponent_Implementation() const override{return const_cast<UMyRangeSensorComponent*>(this);}//~ End IUBotSensor Interfaceprivate:UPROPERTY(Transient)FUBotFrameHeader LatestHeader;int64 Sequence = 0;};PublishSample由你自己调用,固定频率采样的做法见注意事项。帧头的时间戳取自 uBot 时钟,序号自己递增:MyRangeSensorComponent.cpp #include "MyRangeSensorComponent.h"#include "UBotClock.h"void UMyRangeSensorComponent::PublishSample(float RangeMeters){FMyRangeData Data;Data.Header.SensorId = SensorId;Data.Header.FrameId = FrameId;Data.Header.Sequence = ++Sequence;Data.Header.TimestampSeconds = UBot::Clock::GetSimTimeSeconds(this);Data.Header.bValid = true;Data.Header.Status = EUBotSampleStatus::Valid;Data.RangeMeters = RangeMeters;LatestHeader = Data.Header;OnRangeUpdated.Broadcast(Data);} -
实现速度指令接口
IUBotVelocityCommandable是独立的能力接口,不要求同时实现IUBotActuator。返回false表示拒绝这条指令。MyDriveComponent.h #pragma once#include "CoreMinimal.h"#include "Components/ActorComponent.h"#include "UBotActuatorInterface.h"#include "MyDriveComponent.generated.h"UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))class UMyDriveComponent : public UActorComponent, public IUBotVelocityCommandable{GENERATED_BODY()public:virtual bool ApplyVelocityCommand_Implementation(const FUBotVelocityCommand& Command) override;virtual void StopMotion_Implementation() override;private:FVector TargetLinear = FVector::ZeroVector;FVector TargetAngular = FVector::ZeroVector;};// MyDriveComponent.cpp#include "MyDriveComponent.h"bool UMyDriveComponent::ApplyVelocityCommand_Implementation(const FUBotVelocityCommand& Command){const FVector& Linear = Command.LinearVelocityMetersPerSecond; // 机体系,UE 轴,m/sconst FVector& Angular = Command.AngularVelocityRadiansPerSecond; // rad/s,+Z 向右偏航if (Linear.ContainsNaN() || Angular.ContainsNaN()){return false;}TargetLinear = Linear;TargetAngular = Angular;return true;}void UMyDriveComponent::StopMotion_Implementation(){TargetLinear = FVector::ZeroVector;TargetAngular = FVector::ZeroVector;}控制器、油门和转向的映射都写在这样的实现里,uBotROS 本身不含控制逻辑。
在蓝图里实现接口
Section titled “在蓝图里实现接口”-
打开「Class Settings」
打开蓝图 Actor 或蓝图组件,点工具栏的「Class Settings」。
-
添加接口
在「Details」面板的「Interfaces」区点「Add」,选择
UBot Velocity Commandable。传感器选UBot Sensor。 -
实现函数
在「My Blueprint」面板的「Interfaces」里,双击有返回值的函数(如
Apply Velocity Command)来实现它。没有返回值的函数(如Stop Motion)在事件图表里右键添加为事件。 -
编译并保存
之后 C++ 里的
FindVelocityCommandable和Execute_ApplyVelocityCommand就会找到并调用这个蓝图。
| 名称 | 头文件 | 作用 |
|---|---|---|
IUBotSensor |
UBotSensorInterface.h |
GetSensorId、GetSensorFrameId、GetSensorType、IsSensorRunning、GetLatestSensorHeader、GetSensorFrameComponent |
IUBotActuator |
UBotActuatorInterface.h |
GetActuatorId、GetActuatorType、IsActuatorReady |
IUBotVelocityCommandable |
UBotActuatorInterface.h |
ApplyVelocityCommand、StopMotion |
IUBotPerceptionMedium |
UBotPerceptionMedium.h |
GetMediumSnapshot,见 感知介质 |
UBot::Sensors::FindSensors |
UBotSensorInterface.h |
Actor 自身(如果实现了接口)加上所有实现 IUBotSensor 的组件 |
UBot::Actuation::FindVelocityCommandable |
UBotActuatorInterface.h |
第一个实现 IUBotVelocityCommandable 的对象,没有则为 nullptr |
UBot::Interfaces::FindImplementers、FindFirstImplementer |
UBotSensorInterface.h |
上面两个函数的通用版本,参数是接口的 UClass,如 UUBotActuator::StaticClass() |
UBot::Clock::GetSimTimeSeconds |
UBotClock.h |
仿真时间,单位秒 |
FUBotExtensionRegistry |
UBotExtensionRegistry.h |
扩展点:RegisterExtensionPoint 声明,RegisterExtension 注册,GetExtensions 查询;例子见为自定义传感器添加 ROS 发布器 |
FUBotRuntimeStatus |
UBotRuntimeStatus.h |
运行状态:SetItem 发布一个状态项,ReportProblem 报告一个问题,显示在 uBot 面板的「Runtime」和「Problems」分区,见 Package 查询与编辑器面板 |
UBot::Identity::Find、ResolveRobotId、ResolveNamespace、ResolveBaseFrameId、ResolveBaseComponent |
UBotIdentityComponent.h |
与 uBot Identity 组件相同的解析规则,Actor 没有组件时回退到默认值 |
Execute_*的返回值就是接口函数的返回值。Execute_ApplyVelocityCommand返回false表示指令被拒绝(没准备好、数值无效等)。Execute_GetLatestSensorHeader返回最近一次采样的帧头,不会触发新的采样。- 传感器自己的事件用
AddDynamic绑定,处理函数必须是UFUNCTION():
// 头文件里声明:UFUNCTION() void HandleImu(const FUBotImuSensorData& Data);void UMyReceiver::Bind(UUBotImuSensorComponent* Imu){ Imu->OnImuDataUpdated.AddDynamic(this, &UMyReceiver::HandleImu);}- 用
Execute_*调用接口,不要把对象Cast成IUBotSensor*再直接调用函数:蓝图里写的实现这样调用不到。 FindSensors返回的对象一定实现了接口。其他对象先用Object->Implements<UUBotSensor>()检查。- 查找函数只搜索 Actor 自身和它的组件,不搜索子 Actor。
- 要按固定频率采样,用
FUBotSampleScheduler(UBotSampleScheduler.h),不要用循环定时器。长帧时循环定时器会在同一帧触发多次,产生重复的时间戳,见仿真时钟与采样调度。
- UBot Sensor、UBot Actuator、UBot Velocity Commandable、UBot Perception Medium:四个接口的全部函数。
- FUBotVelocityCommand:速度指令的字段。
- 插件之间怎么对接:四个接口各自的用途。
- Identity 组件:身份字段和解析规则。
- 为自定义传感器添加 ROS 发布器:让上面的
UMyRangeSensorComponent发布到 ROS 2。