72 changed files with 2410 additions and 959 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,24 +0,0 @@ |
|||
{ |
|||
"FileVersion": 3, |
|||
"Version": 1, |
|||
"VersionName": "1.0", |
|||
"FriendlyName": "AvatarCore_FaceDetector", |
|||
"Description": "Tracking faces in webcame streams", |
|||
"Category": "Other", |
|||
"CreatedBy": "b.ReX", |
|||
"CreatedByURL": "", |
|||
"DocsURL": "", |
|||
"MarketplaceURL": "", |
|||
"SupportURL": "", |
|||
"CanContainContent": true, |
|||
"IsBetaVersion": false, |
|||
"IsExperimentalVersion": false, |
|||
"Installed": false, |
|||
"Modules": [ |
|||
{ |
|||
"Name": "AvatarCore_FaceDetector", |
|||
"Type": "Runtime", |
|||
"LoadingPhase": "Default" |
|||
} |
|||
] |
|||
} |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,60 +0,0 @@ |
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
|||
|
|||
using System.IO; |
|||
using UnrealBuildTool; |
|||
|
|||
public class AvatarCore_FaceDetector : ModuleRules |
|||
{ |
|||
public AvatarCore_FaceDetector(ReadOnlyTargetRules Target) : base(Target) |
|||
{ |
|||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; |
|||
|
|||
PublicIncludePaths.AddRange( |
|||
new string[] { |
|||
// ... add public include paths required here ...
|
|||
} |
|||
); |
|||
|
|||
|
|||
PrivateIncludePaths.AddRange( |
|||
new string[] { |
|||
// ... add other private include paths required here ...
|
|||
} |
|||
); |
|||
|
|||
|
|||
PublicDependencyModuleNames.AddRange( |
|||
new string[] |
|||
{ |
|||
"Core", |
|||
// ... add other public dependencies that you statically link with here ...
|
|||
} |
|||
); |
|||
|
|||
|
|||
PrivateDependencyModuleNames.AddRange( |
|||
new string[] |
|||
{ |
|||
"CoreUObject", |
|||
"Engine", |
|||
"Projects", |
|||
"Json", |
|||
"JsonUtilities", |
|||
"Slate", |
|||
"SlateCore", |
|||
// ... add private dependencies that you statically link with here ...
|
|||
} |
|||
); |
|||
|
|||
|
|||
DynamicallyLoadedModuleNames.AddRange( |
|||
new string[] |
|||
{ |
|||
// ... add any modules that your module loads dynamically here ...
|
|||
} |
|||
); |
|||
|
|||
RuntimeDependencies.Add(Path.Combine(ModuleDirectory, "..", "ThirdParty", "FaceDetector.exe")); |
|||
RuntimeDependencies.Add(Path.Combine(ModuleDirectory, "..", "ThirdParty", "face_landmarker.task")); |
|||
} |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
|||
|
|||
#include "AvatarCore_FaceDetector.h" |
|||
|
|||
#define LOCTEXT_NAMESPACE "FAvatarCore_FaceDetectorModule" |
|||
|
|||
void FAvatarCore_FaceDetectorModule::StartupModule() |
|||
{ |
|||
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
|||
} |
|||
|
|||
void FAvatarCore_FaceDetectorModule::ShutdownModule() |
|||
{ |
|||
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
|||
// we call this function before unloading the module.
|
|||
} |
|||
|
|||
#undef LOCTEXT_NAMESPACE |
|||
|
|||
IMPLEMENT_MODULE(FAvatarCore_FaceDetectorModule, AvatarCore_FaceDetector) |
|||
@ -1,307 +0,0 @@ |
|||
#include "AvatarFaceDetectorSubsystem.h" |
|||
|
|||
#include "Async/Async.h" |
|||
#include "Interfaces/IPluginManager.h" |
|||
#include "Misc/Paths.h" |
|||
#include "Serialization/JsonReader.h" |
|||
#include "Serialization/JsonSerializer.h" |
|||
|
|||
bool UAvatarFaceDetectorSubsystem::InitializeFaceDetector(const FFaceDetectorSettings& Settings) |
|||
{ |
|||
ShutdownFaceDetector(); |
|||
return StartProcess(Settings); |
|||
} |
|||
|
|||
void UAvatarFaceDetectorSubsystem::ShutdownFaceDetector() |
|||
{ |
|||
StopProcess(); |
|||
} |
|||
|
|||
bool UAvatarFaceDetectorSubsystem::IsFaceDetectorRunning() const |
|||
{ |
|||
FProcHandle HandleCopy = ProcHandle; |
|||
return HandleCopy.IsValid() && FPlatformProcess::IsProcRunning(HandleCopy); |
|||
} |
|||
|
|||
void UAvatarFaceDetectorSubsystem::Deinitialize() |
|||
{ |
|||
ShutdownFaceDetector(); |
|||
Super::Deinitialize(); |
|||
} |
|||
|
|||
FString UAvatarFaceDetectorSubsystem::ResolveThirdPartyFilePath(const FString& FileName) const |
|||
{ |
|||
const TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("AvatarCore_FaceDetector")); |
|||
if (!Plugin.IsValid()) |
|||
{ |
|||
return FString(); |
|||
} |
|||
|
|||
const FString ThirdPartyDir = FPaths::Combine(Plugin->GetBaseDir(), TEXT("Source"), TEXT("ThirdParty")); |
|||
return FPaths::Combine(ThirdPartyDir, FileName); |
|||
} |
|||
|
|||
bool UAvatarFaceDetectorSubsystem::StartProcess(const FFaceDetectorSettings& Settings) |
|||
{ |
|||
FScopeLock Lock(&StateCriticalSection); |
|||
|
|||
const FString ExePath = ResolveThirdPartyFilePath(TEXT("FaceDetector.exe")); |
|||
if (ExePath.IsEmpty() || !FPaths::FileExists(ExePath)) |
|||
{ |
|||
AsyncTask(ENamedThreads::GameThread, [this]() |
|||
{ |
|||
OnFaceDetectorError.Broadcast(TEXT("FaceDetector.exe not found (expected under Plugin/Source/ThirdParty).")); |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
FString Args; |
|||
if (!Settings.WebcamName.IsEmpty()) |
|||
{ |
|||
Args += FString::Printf(TEXT(" --device \"%s\""), *Settings.WebcamName); |
|||
} |
|||
if (!Settings.ModelName.IsEmpty()) |
|||
{ |
|||
const FString ModelPath = ResolveThirdPartyFilePath(Settings.ModelName); |
|||
Args += FString::Printf(TEXT(" --model \"%s\""), *ModelPath); |
|||
} |
|||
Args += FString::Printf(TEXT(" --num_faces %d"), Settings.NumFaces); |
|||
if (Settings.bDebugMode) |
|||
{ |
|||
Args += TEXT(" --debugMode"); |
|||
} |
|||
|
|||
Args += FString::Printf(TEXT(" --fps_limit %d"), Settings.FrameRateLimit); |
|||
|
|||
FPlatformProcess::CreatePipe(ReadPipe, WritePipe); |
|||
|
|||
bStopRequested = false; |
|||
StdoutRemainder.Reset(); |
|||
|
|||
ProcHandle = FPlatformProcess::CreateProc( |
|||
*ExePath, |
|||
*Args, |
|||
false, |
|||
true, |
|||
true, |
|||
nullptr, |
|||
0, |
|||
nullptr, |
|||
WritePipe, |
|||
ReadPipe); |
|||
|
|||
if (!ProcHandle.IsValid()) |
|||
{ |
|||
FPlatformProcess::ClosePipe(ReadPipe, WritePipe); |
|||
ReadPipe = nullptr; |
|||
WritePipe = nullptr; |
|||
|
|||
AsyncTask(ENamedThreads::GameThread, [this]() |
|||
{ |
|||
OnFaceDetectorError.Broadcast(TEXT("Failed to launch FaceDetector.exe")); |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
ReaderFuture = Async(EAsyncExecution::Thread, [this]() |
|||
{ |
|||
ReaderThreadMain(); |
|||
}); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
void UAvatarFaceDetectorSubsystem::StopProcess() |
|||
{ |
|||
TFuture<void> LocalFuture; |
|||
FProcHandle LocalProc; |
|||
void* LocalReadPipe = nullptr; |
|||
void* LocalWritePipe = nullptr; |
|||
|
|||
{ |
|||
FScopeLock Lock(&StateCriticalSection); |
|||
|
|||
bStopRequested = true; |
|||
LocalFuture = MoveTemp(ReaderFuture); |
|||
LocalProc = ProcHandle; |
|||
LocalReadPipe = ReadPipe; |
|||
LocalWritePipe = WritePipe; |
|||
|
|||
ProcHandle.Reset(); |
|||
ReadPipe = nullptr; |
|||
WritePipe = nullptr; |
|||
StdoutRemainder.Reset(); |
|||
} |
|||
|
|||
if (LocalProc.IsValid()) |
|||
{ |
|||
FPlatformProcess::TerminateProc(LocalProc, true); |
|||
FPlatformProcess::CloseProc(LocalProc); |
|||
} |
|||
|
|||
if (LocalFuture.IsValid()) |
|||
{ |
|||
LocalFuture.Wait(); |
|||
} |
|||
|
|||
if (LocalReadPipe != nullptr || LocalWritePipe != nullptr) |
|||
{ |
|||
FPlatformProcess::ClosePipe(LocalReadPipe, LocalWritePipe); |
|||
} |
|||
} |
|||
|
|||
void UAvatarFaceDetectorSubsystem::ReaderThreadMain() |
|||
{ |
|||
while (!bStopRequested) |
|||
{ |
|||
FString NewOutput; |
|||
{ |
|||
FScopeLock Lock(&StateCriticalSection); |
|||
if (ReadPipe == nullptr) |
|||
{ |
|||
break; |
|||
} |
|||
NewOutput = FPlatformProcess::ReadPipe(ReadPipe); |
|||
} |
|||
|
|||
if (NewOutput.IsEmpty()) |
|||
{ |
|||
if (!IsFaceDetectorRunning()) |
|||
{ |
|||
break; |
|||
} |
|||
FPlatformProcess::Sleep(0.01f); |
|||
continue; |
|||
} |
|||
|
|||
FString Combined; |
|||
{ |
|||
FScopeLock Lock(&StateCriticalSection); |
|||
Combined = StdoutRemainder + NewOutput; |
|||
StdoutRemainder.Reset(); |
|||
} |
|||
|
|||
TArray<FString> Lines; |
|||
Combined.ParseIntoArrayLines(Lines, false); |
|||
|
|||
const bool bEndsWithNewline = Combined.EndsWith(TEXT("\n")) || Combined.EndsWith(TEXT("\r\n")); |
|||
if (!bEndsWithNewline && Lines.Num() > 0) |
|||
{ |
|||
FScopeLock Lock(&StateCriticalSection); |
|||
StdoutRemainder = Lines.Pop(); |
|||
} |
|||
|
|||
for (const FString& Line : Lines) |
|||
{ |
|||
const FString Trimmed = Line.TrimStartAndEnd(); |
|||
if (!Trimmed.IsEmpty()) |
|||
{ |
|||
HandleLineOnWorkerThread(Trimmed); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!bStopRequested) |
|||
{ |
|||
AsyncTask(ENamedThreads::GameThread, [this]() |
|||
{ |
|||
OnFaceDetectorError.Broadcast(TEXT("FaceDetector process stopped.")); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
void UAvatarFaceDetectorSubsystem::HandleLineOnWorkerThread(const FString& Line) |
|||
{ |
|||
TSharedPtr<FJsonObject> Root; |
|||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Line); |
|||
if (!FJsonSerializer::Deserialize(Reader, Root) || !Root.IsValid()) |
|||
{ |
|||
AsyncTask(ENamedThreads::GameThread, [this, Line]() |
|||
{ |
|||
OnFaceDetectorLog.Broadcast(Line); |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
FString Type; |
|||
if (!Root->TryGetStringField(TEXT("type"), Type)) |
|||
{ |
|||
AsyncTask(ENamedThreads::GameThread, [this, Line]() |
|||
{ |
|||
OnFaceDetectorLog.Broadcast(Line); |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
if (Type.Equals(TEXT("error"), ESearchCase::IgnoreCase)) |
|||
{ |
|||
FString Message; |
|||
Root->TryGetStringField(TEXT("message"), Message); |
|||
AsyncTask(ENamedThreads::GameThread, [this, Message]() |
|||
{ |
|||
OnFaceDetectorError.Broadcast(Message); |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
if (Type.Equals(TEXT("log"), ESearchCase::IgnoreCase)) |
|||
{ |
|||
FString Message; |
|||
Root->TryGetStringField(TEXT("message"), Message); |
|||
AsyncTask(ENamedThreads::GameThread, [this, Message]() |
|||
{ |
|||
OnFaceDetectorLog.Broadcast(Message); |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
if (Type.Equals(TEXT("faces"), ESearchCase::IgnoreCase)) |
|||
{ |
|||
TArray<FDetectedFace> Faces; |
|||
|
|||
const TArray<TSharedPtr<FJsonValue>>* FacesJson = nullptr; |
|||
if (Root->TryGetArrayField(TEXT("faces"), FacesJson) && FacesJson != nullptr) |
|||
{ |
|||
Faces.Reserve(FacesJson->Num()); |
|||
for (const TSharedPtr<FJsonValue>& FaceVal : *FacesJson) |
|||
{ |
|||
if (!FaceVal.IsValid() || FaceVal->Type != EJson::Object) |
|||
{ |
|||
continue; |
|||
} |
|||
const TSharedPtr<FJsonObject> FaceObj = FaceVal->AsObject(); |
|||
if (!FaceObj.IsValid()) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
double X = 0.0; |
|||
double Y = 0.0; |
|||
double IdNum = (double)INDEX_NONE; |
|||
bool bActive = false; |
|||
|
|||
FaceObj->TryGetNumberField(TEXT("x"), X); |
|||
FaceObj->TryGetNumberField(TEXT("y"), Y); |
|||
FaceObj->TryGetNumberField(TEXT("id"), IdNum); |
|||
FaceObj->TryGetBoolField(TEXT("active"), bActive); |
|||
|
|||
FDetectedFace Face; |
|||
Face.Position = FVector2D((float)X, (float)Y); |
|||
Face.Id = (int32)IdNum; |
|||
Face.TrackingLost = !bActive; |
|||
Faces.Add(Face); |
|||
} |
|||
} |
|||
|
|||
AsyncTask(ENamedThreads::GameThread, [this, Faces = MoveTemp(Faces)]() mutable |
|||
{ |
|||
OnFaceDetectorUpdate.Broadcast(Faces); |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
AsyncTask(ENamedThreads::GameThread, [this, Line]() |
|||
{ |
|||
OnFaceDetectorLog.Broadcast(Line); |
|||
}); |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
|||
|
|||
#pragma once |
|||
|
|||
#include "Modules/ModuleManager.h" |
|||
|
|||
class FAvatarCore_FaceDetectorModule : public IModuleInterface |
|||
{ |
|||
public: |
|||
|
|||
/** IModuleInterface implementation */ |
|||
virtual void StartupModule() override; |
|||
virtual void ShutdownModule() override; |
|||
}; |
|||
@ -1,56 +0,0 @@ |
|||
#pragma once |
|||
|
|||
#include "CoreMinimal.h" |
|||
#include "HAL/PlatformProcess.h" |
|||
#include "Async/Future.h" |
|||
#include "Subsystems/GameInstanceSubsystem.h" |
|||
#include "FaceDetectorTypes.h" |
|||
#include "AvatarFaceDetectorSubsystem.generated.h" |
|||
|
|||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceDetectorErrorSig, const FString&, ErrorMessage); |
|||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceDetectorLogSig, const FString&, LogMessage); |
|||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceDetectorUpdateSig, const TArray<FDetectedFace>&, Faces); |
|||
|
|||
UCLASS() |
|||
class AVATARCORE_FACEDETECTOR_API UAvatarFaceDetectorSubsystem : public UGameInstanceSubsystem |
|||
{ |
|||
GENERATED_BODY() |
|||
|
|||
public: |
|||
UFUNCTION(BlueprintCallable, Category="FaceDetector") |
|||
bool InitializeFaceDetector(const FFaceDetectorSettings& Settings); |
|||
|
|||
UFUNCTION(BlueprintCallable, Category="FaceDetector") |
|||
void ShutdownFaceDetector(); |
|||
|
|||
UFUNCTION(BlueprintCallable, Category="FaceDetector") |
|||
bool IsFaceDetectorRunning() const; |
|||
|
|||
UPROPERTY(BlueprintAssignable, Category="FaceDetector") |
|||
FFaceDetectorErrorSig OnFaceDetectorError; |
|||
|
|||
UPROPERTY(BlueprintAssignable, Category="FaceDetector") |
|||
FFaceDetectorLogSig OnFaceDetectorLog; |
|||
|
|||
UPROPERTY(BlueprintAssignable, Category="FaceDetector") |
|||
FFaceDetectorUpdateSig OnFaceDetectorUpdate; |
|||
|
|||
virtual void Deinitialize() override; |
|||
|
|||
private: |
|||
bool StartProcess(const FFaceDetectorSettings& Settings); |
|||
void StopProcess(); |
|||
void ReaderThreadMain(); |
|||
void HandleLineOnWorkerThread(const FString& Line); |
|||
|
|||
FString ResolveThirdPartyFilePath(const FString& FileName) const; |
|||
|
|||
private: |
|||
FProcHandle ProcHandle; |
|||
void* ReadPipe = nullptr; |
|||
void* WritePipe = nullptr; |
|||
TAtomic<bool> bStopRequested = false; |
|||
TFuture<void> ReaderFuture; |
|||
FCriticalSection StateCriticalSection; |
|||
FString StdoutRemainder; |
|||
}; |
|||
@ -1,45 +0,0 @@ |
|||
#pragma once |
|||
|
|||
#include "CoreMinimal.h" |
|||
#include "FaceDetectorTypes.generated.h" |
|||
|
|||
USTRUCT(BlueprintType) |
|||
struct FDetectedFace |
|||
{ |
|||
GENERATED_BODY() |
|||
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") |
|||
FVector2D Position = FVector2D::ZeroVector; |
|||
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") |
|||
int32 Id = INDEX_NONE; |
|||
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") |
|||
bool TrackingLost = false; |
|||
}; |
|||
|
|||
USTRUCT(BlueprintType) |
|||
struct FFaceDetectorSettings |
|||
{ |
|||
GENERATED_BODY() |
|||
|
|||
//Name of the webcam (pick first if ambigious)
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") |
|||
FString WebcamName; |
|||
|
|||
//Create a DebugWindow to see detected faces
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") |
|||
bool bDebugMode = false; |
|||
|
|||
//Override Model (full path to modelfile)
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") |
|||
FString ModelName; |
|||
|
|||
//Limit Framerate of Videostream (0 to disable)
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") |
|||
int32 FrameRateLimit = 24; |
|||
|
|||
//Max faces to track
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") |
|||
int32 NumFaces = 5; |
|||
}; |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,34 +0,0 @@ |
|||
{ |
|||
"FileVersion": 3, |
|||
"Version": 1, |
|||
"VersionName": "1.1", |
|||
"FriendlyName": "Avatar Core nDisplay", |
|||
"Description": "nDisplay integration for the Avatar Core Framework", |
|||
"Category": "Other", |
|||
"CreatedBy": "b.ReX", |
|||
"CreatedByURL": "b.ReX", |
|||
"DocsURL": "", |
|||
"MarketplaceURL": "", |
|||
"SupportURL": "", |
|||
"CanContainContent": true, |
|||
"IsBetaVersion": false, |
|||
"IsExperimentalVersion": false, |
|||
"Installed": false, |
|||
"Modules": [ |
|||
{ |
|||
"Name": "AvatarCore_nDisplay", |
|||
"Type": "Runtime", |
|||
"LoadingPhase": "PreLoadingScreen" |
|||
} |
|||
], |
|||
"Plugins": [ |
|||
{ |
|||
"Name": "nDisplay", |
|||
"Enabled": true |
|||
}, |
|||
{ |
|||
"Name": "AvatarCore_Manager", |
|||
"Enabled": true |
|||
} |
|||
] |
|||
} |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,54 +0,0 @@ |
|||
// Some copyright should be here...
|
|||
|
|||
using UnrealBuildTool; |
|||
|
|||
public class AvatarCore_nDisplay : ModuleRules |
|||
{ |
|||
public AvatarCore_nDisplay(ReadOnlyTargetRules Target) : base(Target) |
|||
{ |
|||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; |
|||
|
|||
PublicIncludePaths.AddRange( |
|||
new string[] { |
|||
// ... add public include paths required here ...
|
|||
} |
|||
); |
|||
|
|||
|
|||
PrivateIncludePaths.AddRange( |
|||
new string[] { |
|||
// ... add other private include paths required here ...
|
|||
} |
|||
); |
|||
|
|||
|
|||
PublicDependencyModuleNames.AddRange( |
|||
new string[] |
|||
{ |
|||
"Core", |
|||
"DisplayCluster", |
|||
// ... add other public dependencies that you statically link with here ...
|
|||
} |
|||
); |
|||
|
|||
|
|||
PrivateDependencyModuleNames.AddRange( |
|||
new string[] |
|||
{ |
|||
"CoreUObject", |
|||
"Engine", |
|||
"Slate", |
|||
"SlateCore", |
|||
// ... add private dependencies that you statically link with here ...
|
|||
} |
|||
); |
|||
|
|||
|
|||
DynamicallyLoadedModuleNames.AddRange( |
|||
new string[] |
|||
{ |
|||
// ... add any modules that your module loads dynamically here ...
|
|||
} |
|||
); |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
|||
|
|||
#include "AvatarCore_nDisplay.h" |
|||
|
|||
#define LOCTEXT_NAMESPACE "FAvatarCore_nDisplayModule" |
|||
|
|||
void FAvatarCore_nDisplayModule::StartupModule() |
|||
{ |
|||
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
|||
|
|||
} |
|||
|
|||
void FAvatarCore_nDisplayModule::ShutdownModule() |
|||
{ |
|||
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
|||
// we call this function before unloading the module.
|
|||
|
|||
} |
|||
|
|||
#undef LOCTEXT_NAMESPACE |
|||
|
|||
IMPLEMENT_MODULE(FAvatarCore_nDisplayModule, AvatarCore_nDisplay) |
|||
@ -1,161 +0,0 @@ |
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
|||
|
|||
#include "AvatarCore_nDisplayBPLibrary.h" |
|||
#include "Engine/Engine.h" |
|||
#include "AvatarCore_nDisplay.h" |
|||
#include "Animation/AnimInstance.h" |
|||
#include "Components/SkeletalMeshComponent.h" |
|||
#include "ReplicatedFaceCPPAnimInstance.h" |
|||
#include "Serialization/BufferArchive.h" |
|||
#include "Serialization/MemoryReader.h" |
|||
|
|||
UAvatarCore_nDisplayBPLibrary::UAvatarCore_nDisplayBPLibrary(const FObjectInitializer& ObjectInitializer) |
|||
: Super(ObjectInitializer) |
|||
{ |
|||
|
|||
} |
|||
|
|||
FDisplayClusterClusterEventBinary UAvatarCore_nDisplayBPLibrary::MakeClusterPoseEvent( |
|||
UAnimInstance* AnimInstance, |
|||
int32 EventID, |
|||
FName SnapshotName) |
|||
{ |
|||
FDisplayClusterClusterEventBinary OutEvent; |
|||
if (!AnimInstance) |
|||
{ |
|||
return OutEvent; |
|||
} |
|||
|
|||
// 1) Capture skeleton pose into AnimInstance's internal local-space snapshot
|
|||
AnimInstance->SavePoseSnapshot(SnapshotName); |
|||
|
|||
// 2) Retrieve the snapshot pointer
|
|||
const FPoseSnapshot* PosePtr = AnimInstance->GetPoseSnapshot(SnapshotName); |
|||
if (!PosePtr || !PosePtr->bIsValid) |
|||
{ |
|||
return OutEvent; |
|||
} |
|||
|
|||
// 3) Serialize the FPoseSnapshot struct
|
|||
FBufferArchive Buffer(true); |
|||
const UScriptStruct* Struct = FPoseSnapshot::StaticStruct(); |
|||
Struct->SerializeBin(Buffer, const_cast<FPoseSnapshot*>(PosePtr)); |
|||
|
|||
// 4) Capture morph target weights directly from the mesh component after graph evaluation
|
|||
TArray<FName> MorphNames; |
|||
TArray<float> MorphWeights; |
|||
|
|||
//Get Morph Curve list and save them to buffer
|
|||
TMap<FName, float> InOutCurveList; |
|||
|
|||
AnimInstance->GetAnimationCurveList(EAnimCurveType::MorphTargetCurve, InOutCurveList); |
|||
AnimInstance->GetAnimationCurveList(EAnimCurveType::AttributeCurve, InOutCurveList); |
|||
|
|||
const auto* foundItem = InOutCurveList.Find(FName(TEXT("CTRL_expressions_jawOpen"))); |
|||
float JawOpenValue = foundItem ? *foundItem : -1.0f; |
|||
|
|||
//UAvatarCore_nDisplayBPLibrary::PrintHighestMorphTarget(InOutCurveList); //Debug Printing
|
|||
|
|||
InOutCurveList.GenerateKeyArray(MorphNames); |
|||
InOutCurveList.GenerateValueArray(MorphWeights); |
|||
Buffer << MorphNames; |
|||
Buffer << MorphWeights; |
|||
|
|||
// 5) Package into cluster event data
|
|||
OutEvent.EventId = EventID; |
|||
OutEvent.EventData = MoveTemp(Buffer); |
|||
return OutEvent; |
|||
} |
|||
|
|||
|
|||
void UAvatarCore_nDisplayBPLibrary::ApplyClusterPoseEvent( |
|||
UAnimInstance* AnimInstance, |
|||
const FDisplayClusterClusterEventBinary& EventData) |
|||
{ |
|||
if (!AnimInstance) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Deserialize skeleton pose and morph target data in one go
|
|||
FPoseSnapshot Pose; |
|||
TArray<FName> MorphNames; |
|||
TArray<float> MorphWeights; |
|||
|
|||
|
|||
const UScriptStruct* Struct = FPoseSnapshot::StaticStruct(); |
|||
{ |
|||
FMemoryReader Reader(EventData.EventData, /*bIsPersistent=*/ true); |
|||
// 1) Skeleton pose
|
|||
Struct->SerializeBin(Reader, &Pose); |
|||
// 2) Morph targets
|
|||
Reader << MorphNames; |
|||
Reader << MorphWeights; |
|||
} |
|||
|
|||
// 3) Apply skeleton pose
|
|||
if (Pose.bIsValid) |
|||
{ |
|||
AnimInstance->RemovePoseSnapshot(Pose.SnapshotName); |
|||
FPoseSnapshot& InternalPose = AnimInstance->AddPoseSnapshot(Pose.SnapshotName); |
|||
InternalPose = Pose; |
|||
} |
|||
|
|||
TMap<FName, float> InOutCurveList; |
|||
|
|||
if (MorphNames.Num() == MorphWeights.Num()) |
|||
{ |
|||
for (int32 i = 0; i < MorphNames.Num(); ++i) |
|||
{ |
|||
InOutCurveList.Add(MorphNames[i], MorphWeights[i]); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (GEngine) |
|||
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("MorphNames and MorphWeights arrays have different lengths!"))); |
|||
} |
|||
|
|||
//UAvatarCore_nDisplayBPLibrary::PrintHighestMorphTarget(InOutCurveList); //Debug Printing
|
|||
|
|||
// First apply to animation instance for the animation system to pick up
|
|||
AnimInstance->AppendAnimationCurveList(EAnimCurveType::MorphTargetCurve, InOutCurveList); |
|||
AnimInstance->UpdateCurvesPostEvaluation(); |
|||
|
|||
// Then apply directly to the skeletal mesh component to ensure it works on all nDisplay nodes
|
|||
if (auto* SkelMesh = AnimInstance->GetSkelMeshComponent()) |
|||
{ |
|||
// Clear old targets
|
|||
SkelMesh->ClearMorphTargets(); |
|||
if (auto* MI = Cast<UReplicatedFaceCPPAnimInstance>(SkelMesh->GetAnimInstance())) |
|||
{ |
|||
MI->SetMorphTargetCurves(InOutCurveList); |
|||
} |
|||
|
|||
// (Optional) also set them directly on the mesh if you need immediate, nDisplay-safe updates:
|
|||
for (auto& KV : InOutCurveList) |
|||
{ |
|||
SkelMesh->SetMorphTarget(KV.Key, KV.Value, false); ///*bRemoveZeroWeight=*/
|
|||
} |
|||
} |
|||
} |
|||
|
|||
void UAvatarCore_nDisplayBPLibrary::PrintHighestMorphTarget(const TMap<FName, float>& MorphTargets) |
|||
{ |
|||
FName HighestKey; |
|||
float HighestValue = -FLT_MAX; |
|||
bool bFound = false; |
|||
for (const auto& Pair : MorphTargets) |
|||
{ |
|||
if (!bFound || Pair.Value > HighestValue) |
|||
{ |
|||
HighestKey = Pair.Key; |
|||
HighestValue = Pair.Value; |
|||
bFound = true; |
|||
} |
|||
} |
|||
if (bFound && GEngine) |
|||
{ |
|||
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("MorphName %s with %f is the highest"), *HighestKey.ToString(), HighestValue)); |
|||
} |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
// Fill out your copyright notice in the Description page of Project Settings.
|
|||
|
|||
|
|||
#include "ReplicatedFaceCPPAnimInstance.h" |
|||
|
|||
void UReplicatedFaceCPPAnimInstance::SetMorphTargetCurves(const TMap<FName, float>& NewCurves) |
|||
{ |
|||
PendingMorphCurves = NewCurves; |
|||
} |
|||
|
|||
void UReplicatedFaceCPPAnimInstance::NativePostEvaluateAnimation() |
|||
{ |
|||
Super::NativePostEvaluateAnimation(); |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
|||
|
|||
#pragma once |
|||
|
|||
#include "Modules/ModuleManager.h" |
|||
|
|||
class FAvatarCore_nDisplayModule : public IModuleInterface |
|||
{ |
|||
public: |
|||
|
|||
/** IModuleInterface implementation */ |
|||
virtual void StartupModule() override; |
|||
virtual void ShutdownModule() override; |
|||
}; |
|||
@ -1,59 +0,0 @@ |
|||
// Copyright Epic Games, Inc. All Rights Reserved.
|
|||
|
|||
#pragma once |
|||
#include "CoreMinimal.h" |
|||
#include "Cluster/DisplayClusterClusterEvent.h" |
|||
#include "Animation/PoseSnapshot.h" |
|||
#include "Animation/AnimInstance.h" |
|||
#include "Kismet/BlueprintFunctionLibrary.h" |
|||
#include "AvatarCore_nDisplayBPLibrary.generated.h" |
|||
|
|||
/*
|
|||
* Function library class. |
|||
* Each function in it is expected to be static and represents blueprint node that can be called in any blueprint. |
|||
* |
|||
* When declaring function you can define metadata for the node. Key function specifiers will be BlueprintPure and BlueprintCallable. |
|||
* BlueprintPure - means the function does not affect the owning object in any way and thus creates a node without Exec pins. |
|||
* BlueprintCallable - makes a function which can be executed in Blueprints - Thus it has Exec pins. |
|||
* DisplayName - full name of the node, shown when you mouse over the node and in the blueprint drop down menu. |
|||
* Its lets you name the node using characters not allowed in C++ function names. |
|||
* CompactNodeTitle - the word(s) that appear on the node. |
|||
* Keywords - the list of keywords that helps you to find node when you search for it using Blueprint drop-down menu. |
|||
* Good example is "Print String" node which you can find also by using keyword "log". |
|||
* Category - the category your node will be under in the Blueprint drop-down menu. |
|||
* |
|||
* For more info on custom blueprint nodes visit documentation: |
|||
* https://wiki.unrealengine.com/Custom_Blueprint_Node_Creation
|
|||
*/ |
|||
UCLASS() |
|||
class UAvatarCore_nDisplayBPLibrary : public UBlueprintFunctionLibrary |
|||
{ |
|||
GENERATED_UCLASS_BODY() |
|||
|
|||
public: |
|||
/**
|
|||
* Prints the morph target name with the highest value from the provided map. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, Category = "Animation|nDisplay") |
|||
static void PrintHighestMorphTarget(const TMap<FName, float>& MorphTargets); |
|||
/**
|
|||
* Capture the current pose on Master, serialize to bytes, assign a unique EventId, |
|||
* and wrap in a cluster-binary event for broadcasting. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, Category = "Animation|nDisplay") |
|||
static FDisplayClusterClusterEventBinary MakeClusterPoseEvent( |
|||
UAnimInstance* AnimInstance, |
|||
int32 EventID, |
|||
UPARAM(DisplayName = "Snapshot Name") FName SnapshotName = FName("ClusterPose") |
|||
); |
|||
|
|||
/**
|
|||
* Unpack a received cluster-binary event, deserialize bytes into a pose snapshot, |
|||
* and save it into the target AnimInstance under the same snapshot name. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, Category = "Animation|nDisplay") |
|||
static void ApplyClusterPoseEvent( |
|||
UAnimInstance* AnimInstance, |
|||
const FDisplayClusterClusterEventBinary& EventData |
|||
); |
|||
}; |
|||
@ -1,30 +0,0 @@ |
|||
// Fill out your copyright notice in the Description page of Project Settings.
|
|||
|
|||
#pragma once |
|||
|
|||
#include "CoreMinimal.h" |
|||
#include "Animation/AnimInstance.h" |
|||
#include "ReplicatedFaceCPPAnimInstance.generated.h" |
|||
|
|||
/**
|
|||
* |
|||
*/ |
|||
UCLASS() |
|||
class AVATARCORE_NDISPLAY_API UReplicatedFaceCPPAnimInstance : public UAnimInstance |
|||
{ |
|||
GENERATED_BODY() |
|||
|
|||
public: |
|||
// Called by your gRPC callback when new morph data arrives
|
|||
UFUNCTION(BlueprintCallable, Category = "Morph") |
|||
void SetMorphTargetCurves(const TMap<FName, float>& NewCurves); |
|||
|
|||
// Temporary storage of the latest values until injection
|
|||
UPROPERTY(BlueprintReadOnly, Transient) |
|||
TMap<FName, float> PendingMorphCurves; |
|||
|
|||
protected: |
|||
// AnimInstance hook where we inject our curves
|
|||
virtual void NativePostEvaluateAnimation() override; |
|||
|
|||
}; |
|||
Binary file not shown.
@ -0,0 +1,15 @@ |
|||
Simple Arduino program: |
|||
|
|||
Arduino: |
|||
Analog to Digital: |
|||
- Test an analog signal on A0 |
|||
- Read the sensor value(0-1023) |
|||
- Map the sensor range (0-1023) to a range of 1-100 |
|||
- Print the mapped value to the serial port |
|||
|
|||
Unreal Engine: |
|||
|
|||
- Copy the BP_Arduino_Comm.uasset inside the "Content" folder of your UE Project. |
|||
- Change the "Port" number to the correspondent Port of your Arduino Board. |
|||
- Play: The values from 0 to 100 will print on your screen. |
|||
|
|||
@ -0,0 +1,18 @@ |
|||
|
|||
|
|||
void setup() { |
|||
// initialize serial communication at 9600 bits per second:
|
|||
Serial.begin(9600); |
|||
} |
|||
|
|||
|
|||
void loop() { |
|||
// read the sensor value:
|
|||
int sensorValue = analogRead(A0); |
|||
// map the sensor range (0-1023) to a range of 1-100:
|
|||
int mappedValue = map(sensorValue, 0, 1023, 0, 100); |
|||
// print the mapped value to the serial port:
|
|||
Serial.println(mappedValue); |
|||
// wait a bit for the next reading:
|
|||
delay(100); |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,39 @@ |
|||
|
|||
BLUEPRINT FOR UNREAL ENGINE (5.1.0) |
|||
------------------------------------------------------------------------------------------------- |
|||
|
|||
#1 Inside this folder "BLUEPRINTS", there is the same blueprint example for each different UE version. |
|||
|
|||
#2 Drag and Drop this file "BP_SerialCom_UExx.uasset" into your project.s |
|||
|
|||
#3 Drag and Drop ""BP_Serial_UEXxx" blueprint inside your Level. |
|||
|
|||
#4 Inside the BP_Serial_UExx, change the "Port" number to your correspondent port in your micro-controller or Serial Communication Application. |
|||
|
|||
#5 Connect your Arduino or any other Serial Communication device you your computer. |
|||
|
|||
#6 If you see "PORT IS INITIALIZED!" on your screen, that is a confirmation that the port is connected to a Serial Port. |
|||
|
|||
|
|||
#7 Once you confirm your Com port, you are ready to test the blueprint. |
|||
- Press the "SPACE BAR" to print a string on the serial port and send another message to the UE screen. |
|||
- Press the letter "Q" to close the Serial Port. |
|||
- Press the letter "Z" to reconnect to the Serial Port. |
|||
|
|||
Enjoy it! |
|||
|
|||
|
|||
https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port/ |
|||
|
|||
For support and comments, |
|||
|
|||
Discord: https://discord.ramiroslab.com |
|||
|
|||
Github: https://github.reamiroslab.com |
|||
|
|||
YouTube: https://youtube.ramiroslab.com |
|||
|
|||
Thank you! |
|||
Ramiro Montes De Oca, |
|||
Ramiro's Lab |
|||
|
|||
@ -0,0 +1,29 @@ |
|||
|
|||
BLUEPRINT FOR UNREAL ENGINE (5.4.2) |
|||
------------------------------------------------------------- |
|||
|
|||
#1 Select the Zipfile for your correct Engine: |
|||
- SERIALCOM_5_UE532/SERIALCOM_5UE532.zip (Unreal Engine 5.4.2) |
|||
|
|||
#2 Unzip the correspondent Zip file into your project "Plugins" Folder. If doesn't exist, create one. |
|||
---------->Your_Project_Folder--> |
|||
----------------->Binaries |
|||
----------------->Config |
|||
----------------->Content |
|||
----------------->DerivedDataCache |
|||
----------------->Intermidiate |
|||
----------------->Plugins_ |
|||
|------>SERIALCOM_EUXXX |
|||
----------------->Saved |
|||
Your_Project.uproject |
|||
|
|||
#3 Activate "Communication Serial Port (Serial COM)" plugin from your plugin list. |
|||
|
|||
#4 Re-Start your Project. You are ready to use the plugin!!! |
|||
|
|||
|
|||
Enjoy it! |
|||
|
|||
|
|||
https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port/ |
|||
|
|||
@ -0,0 +1,23 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2015 v1 FusionLabz/Gryzly32 (UE4Duino) |
|||
Copyright (c) 2018-2020 v2 Rodrigo Villani Pereira (UE4Duino) |
|||
Copyright (c) 2021-2024 v3/v4/v5 Ramiro Montes De Oca (SerialCOM) fork of (UE4Duino 2.2.5) |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
@ -0,0 +1,32 @@ |
|||
|
|||
|
|||
"Serial COM" v5.5.4.2.1 RELEASE NOTES |
|||
-------------------------------------------------------------- |
|||
- Version compatible with Unreal Engine v5.4.2 |
|||
|
|||
|
|||
Known Issues: |
|||
---------------------------- |
|||
|
|||
- "Serial COM" v5.5.4.2.1 will not be compatible with "UE4Duino" 2.2.5 because the library doesn't share the "UE4DUINO" name anymore, using its own new "SERIALCOM" identifier. Changing to this library will make the old EU4Duino modules in red, indicating that those components are not available anymore. |
|||
Solution: Each component can be replaced with the current version by replacing each one by hand. The replacement equivalent chart is available at: |
|||
https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port (Modules lists comparison chart) |
|||
|
|||
- Arduino doesn't connects again if you close the project without closing the port. |
|||
Solution: Use the best practice of creating an "Event End Play" with "Close Serial Port" connected to the event. This is the cleanest solution for this problem and best practice. |
|||
For more information, visit https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port where we describe the best practices for the use of this plugin. |
|||
|
|||
|
|||
|
|||
----------------------------------------------------------------------------------------------------- |
|||
Ramiro Montes De Oca (Ramiro's Lab) |
|||
Discord: https://discord.ramiroslab.com |
|||
YouTube: https://youtube.ramiroslab.com |
|||
Github: https://github.ramiroslab.com |
|||
|
|||
---------------------------------------------------------------------------------------------------- |
|||
Copyright (c) 2015 v1 FusionLabz/Gryzly32 (UE4Duino) |
|||
Copyright (c) 2018-2020 v2 Rodrigo Villani Pereira (UE4Duino) |
|||
Copyright (c) 2021-2024 v3/v4/v5 Ramiro Montes De Oca (SerialCOM) fork of (UE4Duino 2.2.5) + Major modifications (See previous releases notes) |
|||
|
|||
|
|||
Binary file not shown.
@ -0,0 +1,28 @@ |
|||
{ |
|||
"FileVersion": 3, |
|||
"Version": 3, |
|||
"VersionName": "5.5.4.2.1", |
|||
"FriendlyName": "Communication Serial Port (Serial COM)", |
|||
"Description": "This library enables Serial communication with Unreal Engine 4/5. Added DTR/RTS flow control. Compatible with Arduino projects and any other device requiring Serial Communication. This project was forked from EU4Duino (v1 Gryzly32/FuzionLabs, v2 Rodrigo Villani). Serial Com v3/v4/v5 by Ramiro Montes De Oca.", |
|||
"Category": "Input", |
|||
"CreatedBy": "Ramiro Montes De Oca", |
|||
"CreatedByURL": "https://github.com/videofeedback/RamirosLab", |
|||
"DocsURL": "https://github.com/videofeedback/Unreal_Engine_SerialCOM_Plugin", |
|||
"MarketplaceURL": "", |
|||
"SupportURL": "https://discord.ramiroslab.com/", |
|||
"EngineVersion": "5.6.0", |
|||
"CanContainContent": true, |
|||
"IsBetaVersion": true, |
|||
"Installed": true, |
|||
"Modules": [ |
|||
{ |
|||
"Name": "SERIALCOM", |
|||
"Type": "Runtime", |
|||
"LoadingPhase": "PreDefault", |
|||
"PlatformAllowList": [ |
|||
"Win64" |
|||
] |
|||
} |
|||
], |
|||
"IsExperimentalVersion": false |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,7 @@ |
|||
{ |
|||
"BuildId": "33043543", |
|||
"Modules": |
|||
{ |
|||
"SERIALCOM": "UnrealEditor-SERIALCOM.dll" |
|||
} |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,15 @@ |
|||
Simple Arduino program: |
|||
|
|||
Arduino: |
|||
Analog to Digital: |
|||
- Test an analog signal on A0 |
|||
- Read the sensor value(0-1023) |
|||
- Map the sensor range (0-1023) to a range of 1-100 |
|||
- Print the mapped value to the serial port |
|||
|
|||
Unreal Engine: |
|||
|
|||
- Copy the BP_Arduino_Comm.uasset inside the "Content" folder of your UE Project. |
|||
- Change the "Port" number to the correspondent Port of your Arduino Board. |
|||
- Play: The values from 0 to 100 will print on your screen. |
|||
|
|||
@ -0,0 +1,18 @@ |
|||
|
|||
|
|||
void setup() { |
|||
// initialize serial communication at 9600 bits per second:
|
|||
Serial.begin(9600); |
|||
} |
|||
|
|||
|
|||
void loop() { |
|||
// read the sensor value:
|
|||
int sensorValue = analogRead(A0); |
|||
// map the sensor range (0-1023) to a range of 1-100:
|
|||
int mappedValue = map(sensorValue, 0, 1023, 0, 100); |
|||
// print the mapped value to the serial port:
|
|||
Serial.println(mappedValue); |
|||
// wait a bit for the next reading:
|
|||
delay(100); |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,39 @@ |
|||
|
|||
BLUEPRINT FOR UNREAL ENGINE (5.1.0) |
|||
------------------------------------------------------------------------------------------------- |
|||
|
|||
#1 Inside this folder "BLUEPRINTS", there is the same blueprint example for each different UE version. |
|||
|
|||
#2 Drag and Drop this file "BP_SerialCom_UExx.uasset" into your project.s |
|||
|
|||
#3 Drag and Drop ""BP_Serial_UEXxx" blueprint inside your Level. |
|||
|
|||
#4 Inside the BP_Serial_UExx, change the "Port" number to your correspondent port in your micro-controller or Serial Communication Application. |
|||
|
|||
#5 Connect your Arduino or any other Serial Communication device you your computer. |
|||
|
|||
#6 If you see "PORT IS INITIALIZED!" on your screen, that is a confirmation that the port is connected to a Serial Port. |
|||
|
|||
|
|||
#7 Once you confirm your Com port, you are ready to test the blueprint. |
|||
- Press the "SPACE BAR" to print a string on the serial port and send another message to the UE screen. |
|||
- Press the letter "Q" to close the Serial Port. |
|||
- Press the letter "Z" to reconnect to the Serial Port. |
|||
|
|||
Enjoy it! |
|||
|
|||
|
|||
https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port/ |
|||
|
|||
For support and comments, |
|||
|
|||
Discord: https://discord.ramiroslab.com |
|||
|
|||
Github: https://github.reamiroslab.com |
|||
|
|||
YouTube: https://youtube.ramiroslab.com |
|||
|
|||
Thank you! |
|||
Ramiro Montes De Oca, |
|||
Ramiro's Lab |
|||
|
|||
@ -0,0 +1,29 @@ |
|||
|
|||
BLUEPRINT FOR UNREAL ENGINE (5.4.2) |
|||
------------------------------------------------------------- |
|||
|
|||
#1 Select the Zipfile for your correct Engine: |
|||
- SERIALCOM_5_UE532/SERIALCOM_5UE532.zip (Unreal Engine 5.4.2) |
|||
|
|||
#2 Unzip the correspondent Zip file into your project "Plugins" Folder. If doesn't exist, create one. |
|||
---------->Your_Project_Folder--> |
|||
----------------->Binaries |
|||
----------------->Config |
|||
----------------->Content |
|||
----------------->DerivedDataCache |
|||
----------------->Intermidiate |
|||
----------------->Plugins_ |
|||
|------>SERIALCOM_EUXXX |
|||
----------------->Saved |
|||
Your_Project.uproject |
|||
|
|||
#3 Activate "Communication Serial Port (Serial COM)" plugin from your plugin list. |
|||
|
|||
#4 Re-Start your Project. You are ready to use the plugin!!! |
|||
|
|||
|
|||
Enjoy it! |
|||
|
|||
|
|||
https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port/ |
|||
|
|||
@ -0,0 +1,23 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2015 v1 FusionLabz/Gryzly32 (UE4Duino) |
|||
Copyright (c) 2018-2020 v2 Rodrigo Villani Pereira (UE4Duino) |
|||
Copyright (c) 2021-2024 v3/v4/v5 Ramiro Montes De Oca (SerialCOM) fork of (UE4Duino 2.2.5) |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
@ -0,0 +1,32 @@ |
|||
|
|||
|
|||
"Serial COM" v5.5.4.2.1 RELEASE NOTES |
|||
-------------------------------------------------------------- |
|||
- Version compatible with Unreal Engine v5.4.2 |
|||
|
|||
|
|||
Known Issues: |
|||
---------------------------- |
|||
|
|||
- "Serial COM" v5.5.4.2.1 will not be compatible with "UE4Duino" 2.2.5 because the library doesn't share the "UE4DUINO" name anymore, using its own new "SERIALCOM" identifier. Changing to this library will make the old EU4Duino modules in red, indicating that those components are not available anymore. |
|||
Solution: Each component can be replaced with the current version by replacing each one by hand. The replacement equivalent chart is available at: |
|||
https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port (Modules lists comparison chart) |
|||
|
|||
- Arduino doesn't connects again if you close the project without closing the port. |
|||
Solution: Use the best practice of creating an "Event End Play" with "Close Serial Port" connected to the event. This is the cleanest solution for this problem and best practice. |
|||
For more information, visit https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port where we describe the best practices for the use of this plugin. |
|||
|
|||
|
|||
|
|||
----------------------------------------------------------------------------------------------------- |
|||
Ramiro Montes De Oca (Ramiro's Lab) |
|||
Discord: https://discord.ramiroslab.com |
|||
YouTube: https://youtube.ramiroslab.com |
|||
Github: https://github.ramiroslab.com |
|||
|
|||
---------------------------------------------------------------------------------------------------- |
|||
Copyright (c) 2015 v1 FusionLabz/Gryzly32 (UE4Duino) |
|||
Copyright (c) 2018-2020 v2 Rodrigo Villani Pereira (UE4Duino) |
|||
Copyright (c) 2021-2024 v3/v4/v5 Ramiro Montes De Oca (SerialCOM) fork of (UE4Duino 2.2.5) + Major modifications (See previous releases notes) |
|||
|
|||
|
|||
Binary file not shown.
@ -0,0 +1,28 @@ |
|||
{ |
|||
"FileVersion": 3, |
|||
"Version": 3, |
|||
"VersionName": "5.5.4.2.1", |
|||
"FriendlyName": "Communication Serial Port (Serial COM)", |
|||
"Description": "This library enables Serial communication with Unreal Engine 4/5. Added DTR/RTS flow control. Compatible with Arduino projects and any other device requiring Serial Communication. This project was forked from EU4Duino (v1 Gryzly32/FuzionLabs, v2 Rodrigo Villani). Serial Com v3/v4/v5 by Ramiro Montes De Oca.", |
|||
"Category": "Input", |
|||
"CreatedBy": "Ramiro Montes De Oca", |
|||
"CreatedByURL": "https://github.com/videofeedback/RamirosLab", |
|||
"DocsURL": "https://github.com/videofeedback/Unreal_Engine_SerialCOM_Plugin", |
|||
"MarketplaceURL": "", |
|||
"SupportURL": "https://discord.ramiroslab.com/", |
|||
"EngineVersion": "5.4.0", |
|||
"CanContainContent": true, |
|||
"IsBetaVersion": true, |
|||
"Installed": true, |
|||
"Modules": [ |
|||
{ |
|||
"Name": "SERIALCOM", |
|||
"Type": "Runtime", |
|||
"LoadingPhase": "PreDefault", |
|||
"PlatformAllowList": [ |
|||
"Win64" |
|||
] |
|||
} |
|||
], |
|||
"IsExperimentalVersion": false |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
#include "SERIALCOMModule.h" |
|||
|
|||
IMPLEMENT_MODULE(SERIALCOMModule, SERIALCOM); |
|||
|
|||
#define LOCTEXT_NAMESPACE "SERIALCOM" |
|||
|
|||
SERIALCOMModule::SERIALCOMModule() |
|||
{ |
|||
} |
|||
|
|||
void SERIALCOMModule::StartupModule() |
|||
{ |
|||
// Startup LOG MSG
|
|||
UE_LOG(SERIALCOMLog, Warning, TEXT("SERIALCOM: Log Started")); |
|||
} |
|||
|
|||
void SERIALCOMModule::ShutdownModule() |
|||
{ |
|||
// Shutdown LOG MSG
|
|||
UE_LOG(SERIALCOMLog, Warning, TEXT("SERIALCOM: Log Ended")); |
|||
} |
|||
@ -0,0 +1,539 @@ |
|||
//Based off the "Arduino and C++ (for Windows)" code found at: http://playground.arduino.cc/Interfacing/CPPWindows
|
|||
|
|||
#include "SerialCom.h" |
|||
|
|||
#include "Windows/AllowWindowsPlatformTypes.h" |
|||
#include "Windows/MinWindows.h" |
|||
#include "Windows/HideWindowsPlatformTypes.h" |
|||
|
|||
|
|||
#define BOOL2bool(B) B == 0 ? false : true |
|||
|
|||
USerialCom* USerialCom::OpenComPortWithFlowControl(bool& bOpened, int32 Port, int32 BaudRate, bool DTR, bool RTS) |
|||
{ |
|||
USerialCom* Serial = NewObject<USerialCom>(); |
|||
bOpened = Serial->OpenWFC(Port, BaudRate, DTR, RTS); |
|||
return Serial; |
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
USerialCom* USerialCom::OpenComPort(bool& bOpened, int32 Port, int32 BaudRate) |
|||
{ |
|||
USerialCom* Serial = NewObject<USerialCom>(); |
|||
bOpened = Serial->OpenWFC(Port, BaudRate); |
|||
return Serial; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
int32 USerialCom::BytesToInt(TArray<uint8> Bytes) |
|||
{ |
|||
if (Bytes.Num() != 4) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
return *reinterpret_cast<int32*>(Bytes.GetData()); |
|||
} |
|||
|
|||
TArray<uint8> USerialCom::IntToBytes(const int32 &Int) |
|||
{ |
|||
TArray<uint8> Bytes; |
|||
Bytes.Append(reinterpret_cast<const uint8*>(&Int), 4); |
|||
return Bytes; |
|||
} |
|||
|
|||
|
|||
|
|||
float USerialCom::BytesToFloat(TArray<uint8> Bytes) |
|||
{ |
|||
if (Bytes.Num() != 4) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
return *reinterpret_cast<float*>(Bytes.GetData()); |
|||
} |
|||
|
|||
|
|||
|
|||
TArray<uint8> USerialCom::FloatToBytes(const float &Float) |
|||
{ |
|||
TArray<uint8> Bytes; |
|||
Bytes.Append(reinterpret_cast<const uint8*>(&Float), 4); |
|||
return Bytes; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
USerialCom::USerialCom() |
|||
: WriteLineEnd(ELineEnd::n) |
|||
, m_hIDComDev(nullptr) |
|||
, m_OverlappedRead(nullptr) |
|||
, m_OverlappedWrite(nullptr) |
|||
, m_Port(-1) |
|||
, m_Baud(-1) |
|||
{ |
|||
// Allocate the OVERLAPPED structs
|
|||
m_OverlappedRead = new OVERLAPPED(); |
|||
m_OverlappedWrite = new OVERLAPPED(); |
|||
|
|||
FMemory::Memset(m_OverlappedRead, 0, sizeof(OVERLAPPED)); |
|||
FMemory::Memset(m_OverlappedWrite, 0, sizeof(OVERLAPPED)); |
|||
} |
|||
|
|||
USerialCom::~USerialCom() |
|||
{ |
|||
Close(); |
|||
|
|||
// Delete allocated OVERLAPPED structs
|
|||
delete m_OverlappedRead; |
|||
delete m_OverlappedWrite; |
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
bool USerialCom::OpenWFC(int32 nPort, int32 nBaud, bool bDTR, bool bRTS) |
|||
{ |
|||
if (nPort < 0) |
|||
{ |
|||
UE_LOG(LogTemp, Error, TEXT("Invalid port number: %d"), nPort); |
|||
return false; |
|||
} |
|||
if (m_hIDComDev) |
|||
{ |
|||
UE_LOG(LogTemp, Warning, TEXT("Trying to use opened Serial instance to open a new one. " |
|||
"Current open instance port: %d | Port tried: %d"), m_Port, nPort); |
|||
return false; |
|||
} |
|||
|
|||
FString szPort; |
|||
if (nPort < 10) |
|||
szPort = FString::Printf(TEXT("COM%d"), nPort); |
|||
else |
|||
szPort = FString::Printf(TEXT("\\\\.\\COM%d"), nPort); |
|||
DCB dcb; |
|||
|
|||
m_hIDComDev = CreateFile(*szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); |
|||
if (m_hIDComDev == NULL) |
|||
{ |
|||
unsigned long dwError = GetLastError(); |
|||
UE_LOG(LogTemp, Error, TEXT("Failed to open port COM%d (%s). Error: %08X"), nPort, *szPort, dwError); |
|||
return false; |
|||
} |
|||
|
|||
FMemory::Memset(m_OverlappedRead, 0, sizeof(OVERLAPPED)); |
|||
FMemory::Memset(m_OverlappedWrite, 0, sizeof(OVERLAPPED)); |
|||
|
|||
COMMTIMEOUTS CommTimeOuts; |
|||
//CommTimeOuts.ReadIntervalTimeout = 10;
|
|||
CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF; |
|||
CommTimeOuts.ReadTotalTimeoutMultiplier = 0; |
|||
CommTimeOuts.ReadTotalTimeoutConstant = 0; |
|||
CommTimeOuts.WriteTotalTimeoutMultiplier = 0; |
|||
CommTimeOuts.WriteTotalTimeoutConstant = 10; |
|||
SetCommTimeouts(m_hIDComDev, &CommTimeOuts); |
|||
|
|||
m_OverlappedRead->hEvent = CreateEvent(NULL, true, false, NULL); |
|||
m_OverlappedWrite->hEvent = CreateEvent(NULL, true, false, NULL); |
|||
|
|||
dcb.DCBlength = sizeof(DCB); |
|||
GetCommState(m_hIDComDev, &dcb); |
|||
dcb.BaudRate = nBaud; |
|||
dcb.ByteSize = 8; |
|||
if (bDTR == true) |
|||
{ |
|||
dcb.fDtrControl = DTR_CONTROL_ENABLE; |
|||
} |
|||
else |
|||
{ |
|||
dcb.fDtrControl = DTR_CONTROL_DISABLE; |
|||
} |
|||
|
|||
if (bRTS == true) |
|||
{ |
|||
dcb.fRtsControl = RTS_CONTROL_ENABLE; |
|||
} |
|||
else |
|||
{ |
|||
dcb.fRtsControl = RTS_CONTROL_DISABLE; |
|||
} |
|||
|
|||
if (!SetCommState(m_hIDComDev, &dcb) || |
|||
!SetupComm(m_hIDComDev, 10000, 10000) || |
|||
m_OverlappedRead->hEvent == NULL || |
|||
m_OverlappedWrite->hEvent == NULL) |
|||
{ |
|||
unsigned long dwError = GetLastError(); |
|||
if (m_OverlappedRead->hEvent != NULL) CloseHandle(m_OverlappedRead->hEvent); |
|||
if (m_OverlappedWrite->hEvent != NULL) CloseHandle(m_OverlappedWrite->hEvent); |
|||
CloseHandle(m_hIDComDev); |
|||
m_hIDComDev = NULL; |
|||
UE_LOG(LogTemp, Error, TEXT("Failed to setup port COM%d. Error: %08X"), nPort, dwError); |
|||
return false; |
|||
} |
|||
|
|||
//FPlatformProcess::Sleep(0.05f);
|
|||
AddToRoot(); |
|||
m_Port = nPort; |
|||
m_Baud = nBaud; |
|||
return true; |
|||
} |
|||
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
void USerialCom::Close() |
|||
{ |
|||
if (!m_hIDComDev) return; |
|||
|
|||
if (m_OverlappedRead->hEvent != NULL) CloseHandle(m_OverlappedRead->hEvent); |
|||
if (m_OverlappedWrite->hEvent != NULL) CloseHandle(m_OverlappedWrite->hEvent); |
|||
CloseHandle(m_hIDComDev); |
|||
m_hIDComDev = NULL; |
|||
|
|||
RemoveFromRoot(); |
|||
} |
|||
|
|||
FString USerialCom::ReadString(bool &bSuccess) |
|||
{ |
|||
return ReadStringUntil(bSuccess, '\0'); |
|||
} |
|||
|
|||
FString USerialCom::Readln(bool &bSuccess) |
|||
{ |
|||
return ReadStringUntil(bSuccess, '\n'); |
|||
} |
|||
|
|||
FString USerialCom::ReadStringUntil(bool &bSuccess, uint8 Terminator) |
|||
{ |
|||
bSuccess = false; |
|||
if (!m_hIDComDev) return TEXT(""); |
|||
|
|||
TArray<uint8> Chars; |
|||
uint8 Byte = 0x0; |
|||
bool bReadStatus; |
|||
unsigned long dwBytesRead, dwErrorFlags; |
|||
COMSTAT ComStat; |
|||
|
|||
ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); |
|||
if (!ComStat.cbInQue) return TEXT(""); |
|||
|
|||
do { |
|||
bReadStatus = BOOL2bool(ReadFile( |
|||
m_hIDComDev, |
|||
&Byte, |
|||
1, |
|||
&dwBytesRead, |
|||
m_OverlappedRead)); |
|||
|
|||
if (!bReadStatus) |
|||
{ |
|||
if (GetLastError() == ERROR_IO_PENDING) |
|||
{ |
|||
WaitForSingleObject(m_OverlappedRead->hEvent, 2000); |
|||
} |
|||
else |
|||
{ |
|||
Chars.Add(0x0); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (Byte == Terminator || dwBytesRead == 0) |
|||
{ |
|||
// when Terminator is \n, we know we're expecting lines from Arduino. But those
|
|||
// are ended in \r\n. That means that if we found the line Terminator (\n), our previous
|
|||
// character could be \r. If it is, we remove that from the array.
|
|||
if (Chars.Num() > 0 && Terminator == '\n' && Chars.Top() == '\r') Chars.Pop(false); |
|||
|
|||
Chars.Add(0x0); |
|||
break; |
|||
} |
|||
else Chars.Add(Byte); |
|||
|
|||
} while (Byte != 0x0 && Byte != Terminator); |
|||
|
|||
bSuccess = true; |
|||
auto Convert = FUTF8ToTCHAR((ANSICHAR*)Chars.GetData()); |
|||
return FString(Convert.Get()); |
|||
} |
|||
|
|||
float USerialCom::ReadFloat(bool &bSuccess) |
|||
{ |
|||
bSuccess = false; |
|||
|
|||
TArray<uint8> Bytes = ReadBytes(4); |
|||
if (Bytes.Num() == 0) return 0; |
|||
|
|||
bSuccess = true; |
|||
return *(reinterpret_cast<float*>(Bytes.GetData())); |
|||
} |
|||
|
|||
int32 USerialCom::ReadInt(bool &bSuccess) |
|||
{ |
|||
bSuccess = false; |
|||
|
|||
TArray<uint8> Bytes = ReadBytes(4); |
|||
if (Bytes.Num() == 0) return 0; |
|||
|
|||
bSuccess = true; |
|||
return *(reinterpret_cast<int32*>(Bytes.GetData())); |
|||
} |
|||
|
|||
uint8 USerialCom::ReadByte(bool &bSuccess) |
|||
{ |
|||
bSuccess = false; |
|||
if (!m_hIDComDev) return 0x0; |
|||
|
|||
uint8 Byte = 0x0; |
|||
bool bReadStatus; |
|||
unsigned long dwBytesRead, dwErrorFlags; |
|||
COMSTAT ComStat; |
|||
|
|||
ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); |
|||
if (!ComStat.cbInQue) return 0x0; |
|||
|
|||
bReadStatus = BOOL2bool(ReadFile( |
|||
m_hIDComDev, |
|||
&Byte, |
|||
1, |
|||
&dwBytesRead, |
|||
m_OverlappedRead)); |
|||
|
|||
if (!bReadStatus) |
|||
{ |
|||
if (GetLastError() == ERROR_IO_PENDING) |
|||
{ |
|||
WaitForSingleObject(m_OverlappedRead->hEvent, 2000); |
|||
} |
|||
else |
|||
{ |
|||
return 0x0; |
|||
} |
|||
} |
|||
|
|||
bSuccess = dwBytesRead > 0; |
|||
return Byte; |
|||
} |
|||
|
|||
TArray<uint8> USerialCom::ReadBytes(int32 Limit) |
|||
{ |
|||
TArray<uint8> Data; |
|||
|
|||
if (!m_hIDComDev) return Data; |
|||
|
|||
Data.Empty(Limit); |
|||
|
|||
uint8* Buffer = new uint8[Limit]; |
|||
bool bReadStatus; |
|||
unsigned long dwBytesRead, dwErrorFlags; |
|||
COMSTAT ComStat; |
|||
|
|||
ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); |
|||
if (!ComStat.cbInQue) return Data; |
|||
|
|||
bReadStatus = BOOL2bool(ReadFile( |
|||
m_hIDComDev, |
|||
Buffer, |
|||
Limit, |
|||
&dwBytesRead, |
|||
m_OverlappedRead)); |
|||
|
|||
if (!bReadStatus) |
|||
{ |
|||
if (GetLastError() == ERROR_IO_PENDING) |
|||
{ |
|||
WaitForSingleObject(m_OverlappedRead->hEvent, 2000); |
|||
} |
|||
else |
|||
{ |
|||
return Data; |
|||
} |
|||
} |
|||
|
|||
Data.Append(Buffer, dwBytesRead); |
|||
return Data; |
|||
} |
|||
|
|||
bool USerialCom::Print(FString String) |
|||
{ |
|||
auto Convert = FTCHARToUTF8(*String); |
|||
TArray<uint8> Data; |
|||
Data.Append((uint8*)Convert.Get(), Convert.Length()); |
|||
|
|||
return WriteBytes(Data); |
|||
} |
|||
|
|||
bool USerialCom::Println(FString String) |
|||
{ |
|||
return Print(String + LineEndToStr(WriteLineEnd)); |
|||
} |
|||
|
|||
bool USerialCom::WriteFloat(float Value) |
|||
{ |
|||
TArray<uint8> Buffer; |
|||
Buffer.Append(reinterpret_cast<uint8*>(&Value), 4); |
|||
return WriteBytes(Buffer); |
|||
} |
|||
|
|||
bool USerialCom::WriteInt(int32 Value) |
|||
{ |
|||
TArray<uint8> Buffer; |
|||
Buffer.Append(reinterpret_cast<uint8*>(&Value), 4); |
|||
return WriteBytes(Buffer); |
|||
} |
|||
|
|||
bool USerialCom::WriteByte(uint8 Value) |
|||
{ |
|||
TArray<uint8> Buffer({ Value }); |
|||
return WriteBytes(Buffer); |
|||
} |
|||
|
|||
bool USerialCom::WriteBytes(TArray<uint8> Buffer) |
|||
{ |
|||
if (!m_hIDComDev) false; |
|||
|
|||
bool bWriteStat; |
|||
unsigned long dwBytesWritten; |
|||
|
|||
bWriteStat = BOOL2bool(WriteFile(m_hIDComDev, Buffer.GetData(), Buffer.Num(), &dwBytesWritten, m_OverlappedWrite)); |
|||
if (!bWriteStat && (GetLastError() == ERROR_IO_PENDING)) |
|||
{ |
|||
if (WaitForSingleObject(m_OverlappedWrite->hEvent, 1000)) |
|||
{ |
|||
dwBytesWritten = 0; |
|||
return false; |
|||
} |
|||
else |
|||
{ |
|||
GetOverlappedResult(m_hIDComDev, m_OverlappedWrite, &dwBytesWritten, false); |
|||
m_OverlappedWrite->Offset += dwBytesWritten; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
void USerialCom::Flush() |
|||
{ |
|||
if (!m_hIDComDev) return; |
|||
|
|||
TArray<uint8> Data; |
|||
|
|||
do { |
|||
Data = ReadBytes(8192); |
|||
} while (Data.Num() > 0); |
|||
} |
|||
|
|||
FString USerialCom::LineEndToStr(ELineEnd LineEnd) |
|||
{ |
|||
switch (LineEnd) |
|||
{ |
|||
case ELineEnd::rn: |
|||
return TEXT("\r\n"); |
|||
case ELineEnd::n: |
|||
return TEXT("\n"); |
|||
case ELineEnd::r: |
|||
return TEXT("\r"); |
|||
case ELineEnd::nr: |
|||
return TEXT("\n\r"); |
|||
default: |
|||
return TEXT("null"); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
/////////////////////////////////////////////////////////////////////
|
|||
////////////////////////////////////////////////////////////////////
|
|||
/*
|
|||
|
|||
FString USerialCom::LineEndToStrBD(ELineEnd LineEnd) |
|||
{ |
|||
switch (LineEnd) |
|||
{ |
|||
case ELineEnd::A: |
|||
return TEXT("150"); |
|||
case ELineEnd::B: |
|||
return TEXT("200"); |
|||
case ELineEnd::C: |
|||
return TEXT("300"); |
|||
case ELineEnd::D: |
|||
return TEXT("600"); |
|||
case ELineEnd::E: |
|||
return TEXT("1200"); |
|||
/*
|
|||
case ELineEnd::1800: |
|||
return TEXT("1800"); |
|||
case ELineEnd::2400: |
|||
return TEXT("2400"); |
|||
case ELineEnd::4800: |
|||
return TEXT("4800"); |
|||
case ELineEnd::9600: |
|||
return TEXT("9600"); |
|||
case ELineEnd::19200: |
|||
return TEXT("19200"); |
|||
case ELineEnd::28800: |
|||
return TEXT("28800"); |
|||
case ELineEnd::38400: |
|||
return TEXT("38400"); |
|||
case ELineEnd::57600: |
|||
return TEXT("57600"); |
|||
case ELineEnd::76800: |
|||
return TEXT("76800"); |
|||
case ELineEnd::115200: |
|||
return TEXT("115200"); |
|||
case ELineEnd::230400: |
|||
return TEXT("230400"); |
|||
case ELineEnd::460800: |
|||
return TEXT("460800"); |
|||
case ELineEnd::576000: |
|||
return TEXT("576000"); |
|||
case ELineEnd::921600: |
|||
return TEXT("921600"); |
|||
*/ |
|||
|
|||
/*
|
|||
default: |
|||
return TEXT("9600"); |
|||
} |
|||
} |
|||
|
|||
*/ |
|||
|
|||
/////////////////////////////////////////////////////////////////////
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
@ -0,0 +1,16 @@ |
|||
#pragma once |
|||
|
|||
#include "Modules/ModuleManager.h" |
|||
|
|||
DECLARE_LOG_CATEGORY_CLASS(SERIALCOMLog, Log, All); |
|||
|
|||
class SERIALCOMModule : public IModuleInterface |
|||
{ |
|||
private: |
|||
|
|||
public: |
|||
SERIALCOMModule(); |
|||
|
|||
virtual void StartupModule() override; |
|||
virtual void ShutdownModule() override; |
|||
}; |
|||
@ -0,0 +1,321 @@ |
|||
#pragma once |
|||
|
|||
#define FC_DTRDSR 0x01 |
|||
#define FC_RTSCTS 0x02 |
|||
#define FC_XONXOFF 0x04 |
|||
#define ASCII_BEL 0x07 |
|||
#define ASCII_BS 0x08 |
|||
#define ASCII_LF 0x0A |
|||
#define ASCII_CR 0x0D |
|||
#define ASCII_XON 0x11 |
|||
#define ASCII_XOFF 0x13 |
|||
|
|||
#include "CoreTypes.h" |
|||
#include "SerialCom.generated.h" |
|||
|
|||
|
|||
// Forward declaration
|
|||
typedef struct _OVERLAPPED OVERLAPPED; |
|||
|
|||
UENUM(BlueprintType, Category = "Communication Serial") |
|||
enum class ELineEnd : uint8 |
|||
{ |
|||
rn UMETA(DisplayName = "\r\n"), |
|||
n UMETA(DisplayName = "\n"), |
|||
r UMETA(DisplayName = "\r"), |
|||
nr UMETA(DisplayName = "\n\r") |
|||
}; |
|||
|
|||
UCLASS(BlueprintType, Category = "Communication Serial", meta = (Keywords = "com arduino serial arduino duino")) |
|||
class SERIALCOM_API USerialCom : public UObject |
|||
{ |
|||
GENERATED_BODY() |
|||
|
|||
public: |
|||
/** Determines the line ending used when writing lines to serial port with PrintLine. */ |
|||
UPROPERTY(BlueprintReadWrite, Category = "Communication Serial | String") |
|||
ELineEnd WriteLineEnd; |
|||
|
|||
public: |
|||
USerialCom(); |
|||
~USerialCom(); |
|||
|
|||
/**
|
|||
* Open a serial port and return the created Serial instance. |
|||
* Don't forget to close the port before exiting the game. |
|||
* |
|||
* @param bOpened If the serial port was successfully opened. |
|||
* @param Port The serial port to open. |
|||
* @param BaudRate BaudRate to open the serial port with. |
|||
* @param DTR Enable/Disable DTR communication protocol. |
|||
* @param RTS Enable/Disable RTS communication protocol. |
|||
* @return A Serial instance to work with the opened port. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port With Flow Control"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial port start open serial with flow control")) |
|||
static USerialCom* OpenComPortWithFlowControl(bool &bOpened, int32 Port = 1, int32 BaudRate = 9600, bool DTR = true, bool RTS = true); |
|||
|
|||
/**
|
|||
* Utility function to convert 4 bytes into an Integer. If the input array's length is not 4, returns 0. |
|||
* |
|||
* @param Bytes A byte array with 4 values representing the integer in little-endian format. |
|||
* @return The final integer value or 0 for an invalid array. |
|||
*/ |
|||
|
|||
|
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial port start open serial")) |
|||
static USerialCom* OpenComPort(bool &bOpened, int32 Port = 1, int32 BaudRate = 9600); |
|||
|
|||
/**
|
|||
* Utility function to convert 4 bytes into an Integer. If the input array's length is not 4, returns 0. |
|||
* |
|||
* @param Bytes A byte array with 4 values representing the integer in little-endian format. |
|||
* @return The final integer value or 0 for an invalid array. |
|||
*/ |
|||
|
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Bytes to Int"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast concatenate group bit bitwise bytes to int")) |
|||
static int32 BytesToInt(TArray<uint8> Bytes); |
|||
|
|||
/**
|
|||
* Utility function to get the 4 bytes that make an integer. |
|||
* |
|||
* @param Int The integer value to be converted. |
|||
* @return A byte array containing the 4 bytes that make the integer, starting from the least significant one (little endian). |
|||
*/ |
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Int to Bytes"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast separate bit bitwise int to bytes")) |
|||
static TArray<uint8> IntToBytes(const int32& Int); |
|||
|
|||
/**
|
|||
* Utility function to convert 4 bytes into a float. If the input array's length is not 4, returns 0.0. |
|||
* |
|||
* @param Bytes A byte array with 4 values representing the float in IEEE 754 standard format. |
|||
* @return The final float value or 0.0 for an invalid array. |
|||
*/ |
|||
|
|||
|
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Bytes to Float"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast concatenate group bit bitwise bytes to float")) |
|||
static float BytesToFloat(TArray<uint8> Bytes); |
|||
|
|||
/**
|
|||
* Utility function to get the 4 bytes that make a float. |
|||
* |
|||
* @param Float The float value to be converted. |
|||
* @return A byte array containing the 4 bytes that make the float, in IEEE 754 standard format. |
|||
*/ |
|||
|
|||
|
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Float to Bytes"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast separate bit bitwise flowat to bytes")) |
|||
static TArray<uint8> FloatToBytes(const float& Float); |
|||
|
|||
/**
|
|||
* Open a serial port. Don't forget to close the port before exiting the game. |
|||
* If this Serial instance has already an opened port, |
|||
* return false and doesn't change the opened port number. |
|||
* |
|||
* @param Port The serial port to open. |
|||
* @param BaudRate BaudRate to open the serial port with. |
|||
* @param DTR enable/disable DTR protocol |
|||
* @param RTS enable/disable RTS protocol |
|||
* @return If the serial port was successfully opened. |
|||
*/ |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port With Target and Flow Control"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial start init open port")) |
|||
bool OpenWFC(int32 Port = 2, int32 BaudRate = 9600, bool DTR = true, bool RTS = true); |
|||
/**
|
|||
* Close and end the communication with the serial port. If not open, do nothing. |
|||
*/ |
|||
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Close Serial Port"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial end finish release close port")) |
|||
void Close(); |
|||
|
|||
/**
|
|||
* Will read characters from Serial port until \0 (null char) is found or there are no |
|||
* characters left to read. |
|||
* |
|||
* @param bSuccess If there was anything to read. |
|||
* @return The read string |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read String", keywords = "read string communication com SERIALCOM duino arduino serial get read receive string words text characters"), Category = "Communication Serial") |
|||
FString ReadString(bool& bSuccess); |
|||
/**
|
|||
* Will read characters from Serial port until \r\n (Arduino println line end) is found. |
|||
* |
|||
* @param bSuccess If there was anything to read. |
|||
* @return The read string |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Line", keywords = "communication com SERIALCOM duino arduino serial read line get read receive string words text characters"), Category = "Communication Serial") |
|||
FString Readln(bool& bSuccess); |
|||
/**
|
|||
* Reads the string until a specific char is met. |
|||
* The Terminator char won't be included in the result string. |
|||
*/ |
|||
//UFUNCTION(BlueprintCallable, meta = (DisplayName = "Read String Until", keywords = "get read receive string words text characters"), Category = "Communication Serial")
|
|||
FString ReadStringUntil(bool& bSuccess, uint8 Terminator); |
|||
/**
|
|||
* Reads a float from the serial port (sent as 4 bytes). |
|||
* @param bSuccess True if there were 4 bytes to read. |
|||
* @return The read value |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Float", keywords = "communication com SERIALCOM duino arduino serial read a float get read receive"), Category = "Communication Serial") |
|||
float ReadFloat(bool& bSuccess); |
|||
/**
|
|||
* Reads an integer from the serial port (sent as 4 bytes). |
|||
* @param bSuccess True if there were 4 bytes to read. |
|||
* @return The read value |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Int", keywords = "communication com SERIALCOM duino arduino serial read an int get read receive integer"), Category = "Communication Serial") |
|||
int32 ReadInt(bool& bSuccess); |
|||
/**
|
|||
* Reads a byte from the serial port. |
|||
* @param bSuccess True if there were 4 bytes to read. |
|||
* @return The read value |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Byte", keywords = "communication com SERIALCOM duino arduino serial read a byte get read receive"), Category = "Communication Serial") |
|||
uint8 ReadByte(bool& bSuccess); |
|||
/**
|
|||
* Reads up to Limit bytes from the serial port. If there are less than Limit, |
|||
* reads all of them and return True. |
|||
* @param bSuccess True if there was at least 1 byte to read. |
|||
* @return An array containing the read bytes |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Bytes", keywords = "communication com SERIALCOM duino arduino serial read bytes get read receive"), Category = "Communication Serial") |
|||
TArray<uint8> ReadBytes(int32 Limit = 256); |
|||
|
|||
/**
|
|||
* Writes a string without newline to the serial port. |
|||
* @param String The string to be sent to the serial port. |
|||
* @return True if the string was sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Print", keywords = "communication com SERIALCOM duino arduino serial print send write string words text characters"), Category = "Communication Serial") |
|||
bool Print(FString String); |
|||
/**
|
|||
* Writes a string with newline (\n) appended at the end to the serial port. |
|||
* @param String The string to be sent to the serial port. |
|||
* @return True if the string was sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Print Line", keywords = "communication com SERIALCOM duino arduino serial print line send write string words text characters"), Category = "Communication Serial") |
|||
bool Println(FString String); |
|||
/**
|
|||
* Writes a float value to the serial port as 4 bytes. |
|||
* @param Value The value to be sent to the serial port. |
|||
* @return True if the bytes were sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Float", keywords = "communication com SERIALCOM duino arduino serial write a float send"), Category = "Communication Serial") |
|||
bool WriteFloat(float Value); |
|||
/**
|
|||
* Writes an integer value to the serial port as 4 bytes. |
|||
* @param Value The value to be sent to the serial port. |
|||
* @return True if the bytes were sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Int", keywords = "communication com SERIALCOM duino arduino serial write an int integer send"), Category = "Communication Serial") |
|||
bool WriteInt(int32 Value); |
|||
/**
|
|||
* Writes a byte value to the serial port. |
|||
* @param Value The value to be sent to the serial port. |
|||
* @return True if the byte was sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Byte", keywords = "communication com SERIALCOM duino arduino serial write a byte send"), Category = "Communication Serial") |
|||
bool WriteByte(uint8 Value); |
|||
/**
|
|||
* Writes a byte array as a sequence of bytes to the serial port. |
|||
* @param Buffer The byte array to be sent to the serial port. |
|||
* @return True if the bytes were sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Bytes", keywords = "communication com SERIALCOM duino arduino serial write bytes send"), Category = "Communication Serial") |
|||
bool WriteBytes(TArray<uint8> Buffer); |
|||
|
|||
/** Clean the serial port by reading everything left to be read. */ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Flush Serial Port"), Category = "Communication Serial") |
|||
void Flush(); |
|||
|
|||
/**
|
|||
* Check if the serial port is open. |
|||
* @return True if the serial port is open. |
|||
*/ |
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Serial Port Open?"), Category = "Communication Serial") |
|||
bool IsOpened() { return m_hIDComDev != NULL; } |
|||
|
|||
/**
|
|||
* Read the number of the serial port selected for this Serial instance. |
|||
* @return The number of the serial port. |
|||
*/ |
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Serial Port Number"), Category = "Communication Serial") |
|||
int32 GetPort() { return m_Port; } |
|||
|
|||
/**
|
|||
* Read the selected BaudRate for this Serial instance. |
|||
* @return The baud rate. |
|||
*/ |
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Serial Port Baud Rate"), Category = "Communication Serial") |
|||
int32 GetBaud() { return m_Baud; } |
|||
|
|||
/**
|
|||
* Converts a LineEnd enum value to String. |
|||
* @param LineEnd LineEnd enum value. |
|||
* @return The LineEnd value in string format. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Line End to String", keywords = "communication com SERIALCOM duino arduino serial cast convert line end to string"), Category = "Communication Serial") |
|||
FString LineEndToStr(ELineEnd LineEnd); |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
/////////////////////////////////////////////////////////////////////
|
|||
////////////////////////////////////////////////////////////////////
|
|||
/*
|
|||
|
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "New Baudrate", keywords = "communication com arduino serial cast convert line end to string"), Category = "Communication Serial") |
|||
FString LineEndToStrBD(ELineEnd LineEnd); |
|||
|
|||
*/ |
|||
|
|||
/////////////////////////////////////////////////////////////////////
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
protected: |
|||
void* m_hIDComDev; |
|||
|
|||
// These are pointers to be able to use OVERLAPPED with forward declaration
|
|||
OVERLAPPED* m_OverlappedRead; |
|||
OVERLAPPED* m_OverlappedWrite; |
|||
|
|||
int32 m_Port; |
|||
int32 m_Baud; |
|||
|
|||
}; |
|||
@ -0,0 +1,20 @@ |
|||
using UnrealBuildTool; |
|||
|
|||
public class SERIALCOM : ModuleRules |
|||
{ |
|||
public SERIALCOM(ReadOnlyTargetRules Target) : base(Target) |
|||
{ |
|||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; |
|||
|
|||
PrivateIncludePaths.AddRange(new string[] { "SERIALCOM/Private" }); |
|||
|
|||
PrivateDependencyModuleNames.AddRange( |
|||
new string[] |
|||
{ |
|||
"Engine", |
|||
"Core", |
|||
"CoreUObject" |
|||
} |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
#include "SERIALCOMModule.h" |
|||
|
|||
IMPLEMENT_MODULE(SERIALCOMModule, SERIALCOM); |
|||
|
|||
#define LOCTEXT_NAMESPACE "SERIALCOM" |
|||
|
|||
SERIALCOMModule::SERIALCOMModule() |
|||
{ |
|||
} |
|||
|
|||
void SERIALCOMModule::StartupModule() |
|||
{ |
|||
// Startup LOG MSG
|
|||
UE_LOG(SERIALCOMLog, Warning, TEXT("SERIALCOM: Log Started")); |
|||
} |
|||
|
|||
void SERIALCOMModule::ShutdownModule() |
|||
{ |
|||
// Shutdown LOG MSG
|
|||
UE_LOG(SERIALCOMLog, Warning, TEXT("SERIALCOM: Log Ended")); |
|||
} |
|||
@ -0,0 +1,539 @@ |
|||
//Based off the "Arduino and C++ (for Windows)" code found at: http://playground.arduino.cc/Interfacing/CPPWindows
|
|||
|
|||
#include "SerialCom.h" |
|||
|
|||
#include "Windows/AllowWindowsPlatformTypes.h" |
|||
#include "Windows/MinWindows.h" |
|||
#include "Windows/HideWindowsPlatformTypes.h" |
|||
|
|||
|
|||
#define BOOL2bool(B) B == 0 ? false : true |
|||
|
|||
USerialCom* USerialCom::OpenComPortWithFlowControl(bool& bOpened, int32 Port, int32 BaudRate, bool DTR, bool RTS) |
|||
{ |
|||
USerialCom* Serial = NewObject<USerialCom>(); |
|||
bOpened = Serial->OpenWFC(Port, BaudRate, DTR, RTS); |
|||
return Serial; |
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
USerialCom* USerialCom::OpenComPort(bool& bOpened, int32 Port, int32 BaudRate) |
|||
{ |
|||
USerialCom* Serial = NewObject<USerialCom>(); |
|||
bOpened = Serial->OpenWFC(Port, BaudRate); |
|||
return Serial; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
int32 USerialCom::BytesToInt(TArray<uint8> Bytes) |
|||
{ |
|||
if (Bytes.Num() != 4) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
return *reinterpret_cast<int32*>(Bytes.GetData()); |
|||
} |
|||
|
|||
TArray<uint8> USerialCom::IntToBytes(const int32 &Int) |
|||
{ |
|||
TArray<uint8> Bytes; |
|||
Bytes.Append(reinterpret_cast<const uint8*>(&Int), 4); |
|||
return Bytes; |
|||
} |
|||
|
|||
|
|||
|
|||
float USerialCom::BytesToFloat(TArray<uint8> Bytes) |
|||
{ |
|||
if (Bytes.Num() != 4) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
return *reinterpret_cast<float*>(Bytes.GetData()); |
|||
} |
|||
|
|||
|
|||
|
|||
TArray<uint8> USerialCom::FloatToBytes(const float &Float) |
|||
{ |
|||
TArray<uint8> Bytes; |
|||
Bytes.Append(reinterpret_cast<const uint8*>(&Float), 4); |
|||
return Bytes; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
USerialCom::USerialCom() |
|||
: WriteLineEnd(ELineEnd::n) |
|||
, m_hIDComDev(nullptr) |
|||
, m_OverlappedRead(nullptr) |
|||
, m_OverlappedWrite(nullptr) |
|||
, m_Port(-1) |
|||
, m_Baud(-1) |
|||
{ |
|||
// Allocate the OVERLAPPED structs
|
|||
m_OverlappedRead = new OVERLAPPED(); |
|||
m_OverlappedWrite = new OVERLAPPED(); |
|||
|
|||
FMemory::Memset(m_OverlappedRead, 0, sizeof(OVERLAPPED)); |
|||
FMemory::Memset(m_OverlappedWrite, 0, sizeof(OVERLAPPED)); |
|||
} |
|||
|
|||
USerialCom::~USerialCom() |
|||
{ |
|||
Close(); |
|||
|
|||
// Delete allocated OVERLAPPED structs
|
|||
delete m_OverlappedRead; |
|||
delete m_OverlappedWrite; |
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
bool USerialCom::OpenWFC(int32 nPort, int32 nBaud, bool bDTR, bool bRTS) |
|||
{ |
|||
if (nPort < 0) |
|||
{ |
|||
UE_LOG(LogTemp, Error, TEXT("Invalid port number: %d"), nPort); |
|||
return false; |
|||
} |
|||
if (m_hIDComDev) |
|||
{ |
|||
UE_LOG(LogTemp, Warning, TEXT("Trying to use opened Serial instance to open a new one. " |
|||
"Current open instance port: %d | Port tried: %d"), m_Port, nPort); |
|||
return false; |
|||
} |
|||
|
|||
FString szPort; |
|||
if (nPort < 10) |
|||
szPort = FString::Printf(TEXT("COM%d"), nPort); |
|||
else |
|||
szPort = FString::Printf(TEXT("\\\\.\\COM%d"), nPort); |
|||
DCB dcb; |
|||
|
|||
m_hIDComDev = CreateFile(*szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); |
|||
if (m_hIDComDev == NULL) |
|||
{ |
|||
unsigned long dwError = GetLastError(); |
|||
UE_LOG(LogTemp, Error, TEXT("Failed to open port COM%d (%s). Error: %08X"), nPort, *szPort, dwError); |
|||
return false; |
|||
} |
|||
|
|||
FMemory::Memset(m_OverlappedRead, 0, sizeof(OVERLAPPED)); |
|||
FMemory::Memset(m_OverlappedWrite, 0, sizeof(OVERLAPPED)); |
|||
|
|||
COMMTIMEOUTS CommTimeOuts; |
|||
//CommTimeOuts.ReadIntervalTimeout = 10;
|
|||
CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF; |
|||
CommTimeOuts.ReadTotalTimeoutMultiplier = 0; |
|||
CommTimeOuts.ReadTotalTimeoutConstant = 0; |
|||
CommTimeOuts.WriteTotalTimeoutMultiplier = 0; |
|||
CommTimeOuts.WriteTotalTimeoutConstant = 10; |
|||
SetCommTimeouts(m_hIDComDev, &CommTimeOuts); |
|||
|
|||
m_OverlappedRead->hEvent = CreateEvent(NULL, true, false, NULL); |
|||
m_OverlappedWrite->hEvent = CreateEvent(NULL, true, false, NULL); |
|||
|
|||
dcb.DCBlength = sizeof(DCB); |
|||
GetCommState(m_hIDComDev, &dcb); |
|||
dcb.BaudRate = nBaud; |
|||
dcb.ByteSize = 8; |
|||
if (bDTR == true) |
|||
{ |
|||
dcb.fDtrControl = DTR_CONTROL_ENABLE; |
|||
} |
|||
else |
|||
{ |
|||
dcb.fDtrControl = DTR_CONTROL_DISABLE; |
|||
} |
|||
|
|||
if (bRTS == true) |
|||
{ |
|||
dcb.fRtsControl = RTS_CONTROL_ENABLE; |
|||
} |
|||
else |
|||
{ |
|||
dcb.fRtsControl = RTS_CONTROL_DISABLE; |
|||
} |
|||
|
|||
if (!SetCommState(m_hIDComDev, &dcb) || |
|||
!SetupComm(m_hIDComDev, 10000, 10000) || |
|||
m_OverlappedRead->hEvent == NULL || |
|||
m_OverlappedWrite->hEvent == NULL) |
|||
{ |
|||
unsigned long dwError = GetLastError(); |
|||
if (m_OverlappedRead->hEvent != NULL) CloseHandle(m_OverlappedRead->hEvent); |
|||
if (m_OverlappedWrite->hEvent != NULL) CloseHandle(m_OverlappedWrite->hEvent); |
|||
CloseHandle(m_hIDComDev); |
|||
m_hIDComDev = NULL; |
|||
UE_LOG(LogTemp, Error, TEXT("Failed to setup port COM%d. Error: %08X"), nPort, dwError); |
|||
return false; |
|||
} |
|||
|
|||
//FPlatformProcess::Sleep(0.05f);
|
|||
AddToRoot(); |
|||
m_Port = nPort; |
|||
m_Baud = nBaud; |
|||
return true; |
|||
} |
|||
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
void USerialCom::Close() |
|||
{ |
|||
if (!m_hIDComDev) return; |
|||
|
|||
if (m_OverlappedRead->hEvent != NULL) CloseHandle(m_OverlappedRead->hEvent); |
|||
if (m_OverlappedWrite->hEvent != NULL) CloseHandle(m_OverlappedWrite->hEvent); |
|||
CloseHandle(m_hIDComDev); |
|||
m_hIDComDev = NULL; |
|||
|
|||
RemoveFromRoot(); |
|||
} |
|||
|
|||
FString USerialCom::ReadString(bool &bSuccess) |
|||
{ |
|||
return ReadStringUntil(bSuccess, '\0'); |
|||
} |
|||
|
|||
FString USerialCom::Readln(bool &bSuccess) |
|||
{ |
|||
return ReadStringUntil(bSuccess, '\n'); |
|||
} |
|||
|
|||
FString USerialCom::ReadStringUntil(bool &bSuccess, uint8 Terminator) |
|||
{ |
|||
bSuccess = false; |
|||
if (!m_hIDComDev) return TEXT(""); |
|||
|
|||
TArray<uint8> Chars; |
|||
uint8 Byte = 0x0; |
|||
bool bReadStatus; |
|||
unsigned long dwBytesRead, dwErrorFlags; |
|||
COMSTAT ComStat; |
|||
|
|||
ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); |
|||
if (!ComStat.cbInQue) return TEXT(""); |
|||
|
|||
do { |
|||
bReadStatus = BOOL2bool(ReadFile( |
|||
m_hIDComDev, |
|||
&Byte, |
|||
1, |
|||
&dwBytesRead, |
|||
m_OverlappedRead)); |
|||
|
|||
if (!bReadStatus) |
|||
{ |
|||
if (GetLastError() == ERROR_IO_PENDING) |
|||
{ |
|||
WaitForSingleObject(m_OverlappedRead->hEvent, 2000); |
|||
} |
|||
else |
|||
{ |
|||
Chars.Add(0x0); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (Byte == Terminator || dwBytesRead == 0) |
|||
{ |
|||
// when Terminator is \n, we know we're expecting lines from Arduino. But those
|
|||
// are ended in \r\n. That means that if we found the line Terminator (\n), our previous
|
|||
// character could be \r. If it is, we remove that from the array.
|
|||
if (Chars.Num() > 0 && Terminator == '\n' && Chars.Top() == '\r') Chars.Pop(false); |
|||
|
|||
Chars.Add(0x0); |
|||
break; |
|||
} |
|||
else Chars.Add(Byte); |
|||
|
|||
} while (Byte != 0x0 && Byte != Terminator); |
|||
|
|||
bSuccess = true; |
|||
auto Convert = FUTF8ToTCHAR((ANSICHAR*)Chars.GetData()); |
|||
return FString(Convert.Get()); |
|||
} |
|||
|
|||
float USerialCom::ReadFloat(bool &bSuccess) |
|||
{ |
|||
bSuccess = false; |
|||
|
|||
TArray<uint8> Bytes = ReadBytes(4); |
|||
if (Bytes.Num() == 0) return 0; |
|||
|
|||
bSuccess = true; |
|||
return *(reinterpret_cast<float*>(Bytes.GetData())); |
|||
} |
|||
|
|||
int32 USerialCom::ReadInt(bool &bSuccess) |
|||
{ |
|||
bSuccess = false; |
|||
|
|||
TArray<uint8> Bytes = ReadBytes(4); |
|||
if (Bytes.Num() == 0) return 0; |
|||
|
|||
bSuccess = true; |
|||
return *(reinterpret_cast<int32*>(Bytes.GetData())); |
|||
} |
|||
|
|||
uint8 USerialCom::ReadByte(bool &bSuccess) |
|||
{ |
|||
bSuccess = false; |
|||
if (!m_hIDComDev) return 0x0; |
|||
|
|||
uint8 Byte = 0x0; |
|||
bool bReadStatus; |
|||
unsigned long dwBytesRead, dwErrorFlags; |
|||
COMSTAT ComStat; |
|||
|
|||
ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); |
|||
if (!ComStat.cbInQue) return 0x0; |
|||
|
|||
bReadStatus = BOOL2bool(ReadFile( |
|||
m_hIDComDev, |
|||
&Byte, |
|||
1, |
|||
&dwBytesRead, |
|||
m_OverlappedRead)); |
|||
|
|||
if (!bReadStatus) |
|||
{ |
|||
if (GetLastError() == ERROR_IO_PENDING) |
|||
{ |
|||
WaitForSingleObject(m_OverlappedRead->hEvent, 2000); |
|||
} |
|||
else |
|||
{ |
|||
return 0x0; |
|||
} |
|||
} |
|||
|
|||
bSuccess = dwBytesRead > 0; |
|||
return Byte; |
|||
} |
|||
|
|||
TArray<uint8> USerialCom::ReadBytes(int32 Limit) |
|||
{ |
|||
TArray<uint8> Data; |
|||
|
|||
if (!m_hIDComDev) return Data; |
|||
|
|||
Data.Empty(Limit); |
|||
|
|||
uint8* Buffer = new uint8[Limit]; |
|||
bool bReadStatus; |
|||
unsigned long dwBytesRead, dwErrorFlags; |
|||
COMSTAT ComStat; |
|||
|
|||
ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); |
|||
if (!ComStat.cbInQue) return Data; |
|||
|
|||
bReadStatus = BOOL2bool(ReadFile( |
|||
m_hIDComDev, |
|||
Buffer, |
|||
Limit, |
|||
&dwBytesRead, |
|||
m_OverlappedRead)); |
|||
|
|||
if (!bReadStatus) |
|||
{ |
|||
if (GetLastError() == ERROR_IO_PENDING) |
|||
{ |
|||
WaitForSingleObject(m_OverlappedRead->hEvent, 2000); |
|||
} |
|||
else |
|||
{ |
|||
return Data; |
|||
} |
|||
} |
|||
|
|||
Data.Append(Buffer, dwBytesRead); |
|||
return Data; |
|||
} |
|||
|
|||
bool USerialCom::Print(FString String) |
|||
{ |
|||
auto Convert = FTCHARToUTF8(*String); |
|||
TArray<uint8> Data; |
|||
Data.Append((uint8*)Convert.Get(), Convert.Length()); |
|||
|
|||
return WriteBytes(Data); |
|||
} |
|||
|
|||
bool USerialCom::Println(FString String) |
|||
{ |
|||
return Print(String + LineEndToStr(WriteLineEnd)); |
|||
} |
|||
|
|||
bool USerialCom::WriteFloat(float Value) |
|||
{ |
|||
TArray<uint8> Buffer; |
|||
Buffer.Append(reinterpret_cast<uint8*>(&Value), 4); |
|||
return WriteBytes(Buffer); |
|||
} |
|||
|
|||
bool USerialCom::WriteInt(int32 Value) |
|||
{ |
|||
TArray<uint8> Buffer; |
|||
Buffer.Append(reinterpret_cast<uint8*>(&Value), 4); |
|||
return WriteBytes(Buffer); |
|||
} |
|||
|
|||
bool USerialCom::WriteByte(uint8 Value) |
|||
{ |
|||
TArray<uint8> Buffer({ Value }); |
|||
return WriteBytes(Buffer); |
|||
} |
|||
|
|||
bool USerialCom::WriteBytes(TArray<uint8> Buffer) |
|||
{ |
|||
if (!m_hIDComDev) false; |
|||
|
|||
bool bWriteStat; |
|||
unsigned long dwBytesWritten; |
|||
|
|||
bWriteStat = BOOL2bool(WriteFile(m_hIDComDev, Buffer.GetData(), Buffer.Num(), &dwBytesWritten, m_OverlappedWrite)); |
|||
if (!bWriteStat && (GetLastError() == ERROR_IO_PENDING)) |
|||
{ |
|||
if (WaitForSingleObject(m_OverlappedWrite->hEvent, 1000)) |
|||
{ |
|||
dwBytesWritten = 0; |
|||
return false; |
|||
} |
|||
else |
|||
{ |
|||
GetOverlappedResult(m_hIDComDev, m_OverlappedWrite, &dwBytesWritten, false); |
|||
m_OverlappedWrite->Offset += dwBytesWritten; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
void USerialCom::Flush() |
|||
{ |
|||
if (!m_hIDComDev) return; |
|||
|
|||
TArray<uint8> Data; |
|||
|
|||
do { |
|||
Data = ReadBytes(8192); |
|||
} while (Data.Num() > 0); |
|||
} |
|||
|
|||
FString USerialCom::LineEndToStr(ELineEnd LineEnd) |
|||
{ |
|||
switch (LineEnd) |
|||
{ |
|||
case ELineEnd::rn: |
|||
return TEXT("\r\n"); |
|||
case ELineEnd::n: |
|||
return TEXT("\n"); |
|||
case ELineEnd::r: |
|||
return TEXT("\r"); |
|||
case ELineEnd::nr: |
|||
return TEXT("\n\r"); |
|||
default: |
|||
return TEXT("null"); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
/////////////////////////////////////////////////////////////////////
|
|||
////////////////////////////////////////////////////////////////////
|
|||
/*
|
|||
|
|||
FString USerialCom::LineEndToStrBD(ELineEnd LineEnd) |
|||
{ |
|||
switch (LineEnd) |
|||
{ |
|||
case ELineEnd::A: |
|||
return TEXT("150"); |
|||
case ELineEnd::B: |
|||
return TEXT("200"); |
|||
case ELineEnd::C: |
|||
return TEXT("300"); |
|||
case ELineEnd::D: |
|||
return TEXT("600"); |
|||
case ELineEnd::E: |
|||
return TEXT("1200"); |
|||
/*
|
|||
case ELineEnd::1800: |
|||
return TEXT("1800"); |
|||
case ELineEnd::2400: |
|||
return TEXT("2400"); |
|||
case ELineEnd::4800: |
|||
return TEXT("4800"); |
|||
case ELineEnd::9600: |
|||
return TEXT("9600"); |
|||
case ELineEnd::19200: |
|||
return TEXT("19200"); |
|||
case ELineEnd::28800: |
|||
return TEXT("28800"); |
|||
case ELineEnd::38400: |
|||
return TEXT("38400"); |
|||
case ELineEnd::57600: |
|||
return TEXT("57600"); |
|||
case ELineEnd::76800: |
|||
return TEXT("76800"); |
|||
case ELineEnd::115200: |
|||
return TEXT("115200"); |
|||
case ELineEnd::230400: |
|||
return TEXT("230400"); |
|||
case ELineEnd::460800: |
|||
return TEXT("460800"); |
|||
case ELineEnd::576000: |
|||
return TEXT("576000"); |
|||
case ELineEnd::921600: |
|||
return TEXT("921600"); |
|||
*/ |
|||
|
|||
/*
|
|||
default: |
|||
return TEXT("9600"); |
|||
} |
|||
} |
|||
|
|||
*/ |
|||
|
|||
/////////////////////////////////////////////////////////////////////
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
@ -0,0 +1,16 @@ |
|||
#pragma once |
|||
|
|||
#include "Modules/ModuleManager.h" |
|||
|
|||
DECLARE_LOG_CATEGORY_CLASS(SERIALCOMLog, Log, All); |
|||
|
|||
class SERIALCOMModule : public IModuleInterface |
|||
{ |
|||
private: |
|||
|
|||
public: |
|||
SERIALCOMModule(); |
|||
|
|||
virtual void StartupModule() override; |
|||
virtual void ShutdownModule() override; |
|||
}; |
|||
@ -0,0 +1,321 @@ |
|||
#pragma once |
|||
|
|||
#define FC_DTRDSR 0x01 |
|||
#define FC_RTSCTS 0x02 |
|||
#define FC_XONXOFF 0x04 |
|||
#define ASCII_BEL 0x07 |
|||
#define ASCII_BS 0x08 |
|||
#define ASCII_LF 0x0A |
|||
#define ASCII_CR 0x0D |
|||
#define ASCII_XON 0x11 |
|||
#define ASCII_XOFF 0x13 |
|||
|
|||
#include "CoreTypes.h" |
|||
#include "SerialCom.generated.h" |
|||
|
|||
|
|||
// Forward declaration
|
|||
typedef struct _OVERLAPPED OVERLAPPED; |
|||
|
|||
UENUM(BlueprintType, Category = "Communication Serial") |
|||
enum class ELineEnd : uint8 |
|||
{ |
|||
rn UMETA(DisplayName = "\r\n"), |
|||
n UMETA(DisplayName = "\n"), |
|||
r UMETA(DisplayName = "\r"), |
|||
nr UMETA(DisplayName = "\n\r") |
|||
}; |
|||
|
|||
UCLASS(BlueprintType, Category = "Communication Serial", meta = (Keywords = "com arduino serial arduino duino")) |
|||
class SERIALCOM_API USerialCom : public UObject |
|||
{ |
|||
GENERATED_BODY() |
|||
|
|||
public: |
|||
/** Determines the line ending used when writing lines to serial port with PrintLine. */ |
|||
UPROPERTY(BlueprintReadWrite, Category = "Communication Serial | String") |
|||
ELineEnd WriteLineEnd; |
|||
|
|||
public: |
|||
USerialCom(); |
|||
~USerialCom(); |
|||
|
|||
/**
|
|||
* Open a serial port and return the created Serial instance. |
|||
* Don't forget to close the port before exiting the game. |
|||
* |
|||
* @param bOpened If the serial port was successfully opened. |
|||
* @param Port The serial port to open. |
|||
* @param BaudRate BaudRate to open the serial port with. |
|||
* @param DTR Enable/Disable DTR communication protocol. |
|||
* @param RTS Enable/Disable RTS communication protocol. |
|||
* @return A Serial instance to work with the opened port. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port With Flow Control"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial port start open serial with flow control")) |
|||
static USerialCom* OpenComPortWithFlowControl(bool &bOpened, int32 Port = 1, int32 BaudRate = 9600, bool DTR = true, bool RTS = true); |
|||
|
|||
/**
|
|||
* Utility function to convert 4 bytes into an Integer. If the input array's length is not 4, returns 0. |
|||
* |
|||
* @param Bytes A byte array with 4 values representing the integer in little-endian format. |
|||
* @return The final integer value or 0 for an invalid array. |
|||
*/ |
|||
|
|||
|
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial port start open serial")) |
|||
static USerialCom* OpenComPort(bool &bOpened, int32 Port = 1, int32 BaudRate = 9600); |
|||
|
|||
/**
|
|||
* Utility function to convert 4 bytes into an Integer. If the input array's length is not 4, returns 0. |
|||
* |
|||
* @param Bytes A byte array with 4 values representing the integer in little-endian format. |
|||
* @return The final integer value or 0 for an invalid array. |
|||
*/ |
|||
|
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Bytes to Int"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast concatenate group bit bitwise bytes to int")) |
|||
static int32 BytesToInt(TArray<uint8> Bytes); |
|||
|
|||
/**
|
|||
* Utility function to get the 4 bytes that make an integer. |
|||
* |
|||
* @param Int The integer value to be converted. |
|||
* @return A byte array containing the 4 bytes that make the integer, starting from the least significant one (little endian). |
|||
*/ |
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Int to Bytes"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast separate bit bitwise int to bytes")) |
|||
static TArray<uint8> IntToBytes(const int32& Int); |
|||
|
|||
/**
|
|||
* Utility function to convert 4 bytes into a float. If the input array's length is not 4, returns 0.0. |
|||
* |
|||
* @param Bytes A byte array with 4 values representing the float in IEEE 754 standard format. |
|||
* @return The final float value or 0.0 for an invalid array. |
|||
*/ |
|||
|
|||
|
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Bytes to Float"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast concatenate group bit bitwise bytes to float")) |
|||
static float BytesToFloat(TArray<uint8> Bytes); |
|||
|
|||
/**
|
|||
* Utility function to get the 4 bytes that make a float. |
|||
* |
|||
* @param Float The float value to be converted. |
|||
* @return A byte array containing the 4 bytes that make the float, in IEEE 754 standard format. |
|||
*/ |
|||
|
|||
|
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Float to Bytes"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast separate bit bitwise flowat to bytes")) |
|||
static TArray<uint8> FloatToBytes(const float& Float); |
|||
|
|||
/**
|
|||
* Open a serial port. Don't forget to close the port before exiting the game. |
|||
* If this Serial instance has already an opened port, |
|||
* return false and doesn't change the opened port number. |
|||
* |
|||
* @param Port The serial port to open. |
|||
* @param BaudRate BaudRate to open the serial port with. |
|||
* @param DTR enable/disable DTR protocol |
|||
* @param RTS enable/disable RTS protocol |
|||
* @return If the serial port was successfully opened. |
|||
*/ |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
//////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port With Target and Flow Control"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial start init open port")) |
|||
bool OpenWFC(int32 Port = 2, int32 BaudRate = 9600, bool DTR = true, bool RTS = true); |
|||
/**
|
|||
* Close and end the communication with the serial port. If not open, do nothing. |
|||
*/ |
|||
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Close Serial Port"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial end finish release close port")) |
|||
void Close(); |
|||
|
|||
/**
|
|||
* Will read characters from Serial port until \0 (null char) is found or there are no |
|||
* characters left to read. |
|||
* |
|||
* @param bSuccess If there was anything to read. |
|||
* @return The read string |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read String", keywords = "read string communication com SERIALCOM duino arduino serial get read receive string words text characters"), Category = "Communication Serial") |
|||
FString ReadString(bool& bSuccess); |
|||
/**
|
|||
* Will read characters from Serial port until \r\n (Arduino println line end) is found. |
|||
* |
|||
* @param bSuccess If there was anything to read. |
|||
* @return The read string |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Line", keywords = "communication com SERIALCOM duino arduino serial read line get read receive string words text characters"), Category = "Communication Serial") |
|||
FString Readln(bool& bSuccess); |
|||
/**
|
|||
* Reads the string until a specific char is met. |
|||
* The Terminator char won't be included in the result string. |
|||
*/ |
|||
//UFUNCTION(BlueprintCallable, meta = (DisplayName = "Read String Until", keywords = "get read receive string words text characters"), Category = "Communication Serial")
|
|||
FString ReadStringUntil(bool& bSuccess, uint8 Terminator); |
|||
/**
|
|||
* Reads a float from the serial port (sent as 4 bytes). |
|||
* @param bSuccess True if there were 4 bytes to read. |
|||
* @return The read value |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Float", keywords = "communication com SERIALCOM duino arduino serial read a float get read receive"), Category = "Communication Serial") |
|||
float ReadFloat(bool& bSuccess); |
|||
/**
|
|||
* Reads an integer from the serial port (sent as 4 bytes). |
|||
* @param bSuccess True if there were 4 bytes to read. |
|||
* @return The read value |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Int", keywords = "communication com SERIALCOM duino arduino serial read an int get read receive integer"), Category = "Communication Serial") |
|||
int32 ReadInt(bool& bSuccess); |
|||
/**
|
|||
* Reads a byte from the serial port. |
|||
* @param bSuccess True if there were 4 bytes to read. |
|||
* @return The read value |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Byte", keywords = "communication com SERIALCOM duino arduino serial read a byte get read receive"), Category = "Communication Serial") |
|||
uint8 ReadByte(bool& bSuccess); |
|||
/**
|
|||
* Reads up to Limit bytes from the serial port. If there are less than Limit, |
|||
* reads all of them and return True. |
|||
* @param bSuccess True if there was at least 1 byte to read. |
|||
* @return An array containing the read bytes |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Bytes", keywords = "communication com SERIALCOM duino arduino serial read bytes get read receive"), Category = "Communication Serial") |
|||
TArray<uint8> ReadBytes(int32 Limit = 256); |
|||
|
|||
/**
|
|||
* Writes a string without newline to the serial port. |
|||
* @param String The string to be sent to the serial port. |
|||
* @return True if the string was sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Print", keywords = "communication com SERIALCOM duino arduino serial print send write string words text characters"), Category = "Communication Serial") |
|||
bool Print(FString String); |
|||
/**
|
|||
* Writes a string with newline (\n) appended at the end to the serial port. |
|||
* @param String The string to be sent to the serial port. |
|||
* @return True if the string was sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Print Line", keywords = "communication com SERIALCOM duino arduino serial print line send write string words text characters"), Category = "Communication Serial") |
|||
bool Println(FString String); |
|||
/**
|
|||
* Writes a float value to the serial port as 4 bytes. |
|||
* @param Value The value to be sent to the serial port. |
|||
* @return True if the bytes were sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Float", keywords = "communication com SERIALCOM duino arduino serial write a float send"), Category = "Communication Serial") |
|||
bool WriteFloat(float Value); |
|||
/**
|
|||
* Writes an integer value to the serial port as 4 bytes. |
|||
* @param Value The value to be sent to the serial port. |
|||
* @return True if the bytes were sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Int", keywords = "communication com SERIALCOM duino arduino serial write an int integer send"), Category = "Communication Serial") |
|||
bool WriteInt(int32 Value); |
|||
/**
|
|||
* Writes a byte value to the serial port. |
|||
* @param Value The value to be sent to the serial port. |
|||
* @return True if the byte was sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Byte", keywords = "communication com SERIALCOM duino arduino serial write a byte send"), Category = "Communication Serial") |
|||
bool WriteByte(uint8 Value); |
|||
/**
|
|||
* Writes a byte array as a sequence of bytes to the serial port. |
|||
* @param Buffer The byte array to be sent to the serial port. |
|||
* @return True if the bytes were sent. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Bytes", keywords = "communication com SERIALCOM duino arduino serial write bytes send"), Category = "Communication Serial") |
|||
bool WriteBytes(TArray<uint8> Buffer); |
|||
|
|||
/** Clean the serial port by reading everything left to be read. */ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Flush Serial Port"), Category = "Communication Serial") |
|||
void Flush(); |
|||
|
|||
/**
|
|||
* Check if the serial port is open. |
|||
* @return True if the serial port is open. |
|||
*/ |
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Serial Port Open?"), Category = "Communication Serial") |
|||
bool IsOpened() { return m_hIDComDev != NULL; } |
|||
|
|||
/**
|
|||
* Read the number of the serial port selected for this Serial instance. |
|||
* @return The number of the serial port. |
|||
*/ |
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Serial Port Number"), Category = "Communication Serial") |
|||
int32 GetPort() { return m_Port; } |
|||
|
|||
/**
|
|||
* Read the selected BaudRate for this Serial instance. |
|||
* @return The baud rate. |
|||
*/ |
|||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Serial Port Baud Rate"), Category = "Communication Serial") |
|||
int32 GetBaud() { return m_Baud; } |
|||
|
|||
/**
|
|||
* Converts a LineEnd enum value to String. |
|||
* @param LineEnd LineEnd enum value. |
|||
* @return The LineEnd value in string format. |
|||
*/ |
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Line End to String", keywords = "communication com SERIALCOM duino arduino serial cast convert line end to string"), Category = "Communication Serial") |
|||
FString LineEndToStr(ELineEnd LineEnd); |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
/////////////////////////////////////////////////////////////////////
|
|||
////////////////////////////////////////////////////////////////////
|
|||
/*
|
|||
|
|||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "New Baudrate", keywords = "communication com arduino serial cast convert line end to string"), Category = "Communication Serial") |
|||
FString LineEndToStrBD(ELineEnd LineEnd); |
|||
|
|||
*/ |
|||
|
|||
/////////////////////////////////////////////////////////////////////
|
|||
////////////////////////////////////////////////////////////////////
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
protected: |
|||
void* m_hIDComDev; |
|||
|
|||
// These are pointers to be able to use OVERLAPPED with forward declaration
|
|||
OVERLAPPED* m_OverlappedRead; |
|||
OVERLAPPED* m_OverlappedWrite; |
|||
|
|||
int32 m_Port; |
|||
int32 m_Baud; |
|||
|
|||
}; |
|||
@ -0,0 +1,20 @@ |
|||
using UnrealBuildTool; |
|||
|
|||
public class SERIALCOM : ModuleRules |
|||
{ |
|||
public SERIALCOM(ReadOnlyTargetRules Target) : base(Target) |
|||
{ |
|||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; |
|||
|
|||
PrivateIncludePaths.AddRange(new string[] { "SERIALCOM/Private" }); |
|||
|
|||
PrivateDependencyModuleNames.AddRange( |
|||
new string[] |
|||
{ |
|||
"Engine", |
|||
"Core", |
|||
"CoreUObject" |
|||
} |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
/*
|
|||
* LED + Button Controller with Serial Commands and Smooth Pulsing |
|||
* ================================================================ |
|||
* |
|||
* HARDWARE WIRING NOTES: |
|||
* ---------------------- |
|||
* The switch (TRU Components LAS1-AGQ-11E/R/G) has an integrated LED rated |
|||
* for +12V with a built-in resistor. Since the Arduino cannot: |
|||
* a) Supply 12V (it outputs 5V max) |
|||
* b) Drive enough current for the LED directly |
|||
* c) Do PWM dimming at 12V |
|||
* |
|||
* ...you MUST use an NPN Darlington transistor (TIP122) as a low-side switch. |
|||
* |
|||
* WIRING: |
|||
* +12V PSU (+) --> LED+ on switch |
|||
* LED- on switch --> COLLECTOR (TIP122 middle pin) |
|||
* Arduino Pin 10 --> 1k Ohm resistor --> BASE (TIP122 left pin) |
|||
* EMITTER (TIP122 right pin) --> GND |
|||
* Arduino GND + 12V PSU GND --> tied together (common ground) |
|||
* |
|||
* SWITCH (button) WIRING: |
|||
* COM --> Arduino Pin 9 |
|||
* NO --> GND |
|||
* NC --> leave unconnected |
|||
* |
|||
* Pin 10 must be PWM-capable (Uno/Nano/Mega all support this). |
|||
* |
|||
* SERIAL COMMANDS: |
|||
* LED_ON -> Turn LED fully on (100%, stops pulsing) |
|||
* LED_OFF -> Turn LED fully off (stops pulsing) |
|||
* LED_DIM:xx -> Turn LED on at xx percent (0-100), e.g. LED_DIM:50 |
|||
* LED_PULSE_ON -> Start smooth breathing/pulse animation |
|||
* LED_PULSE_OFF -> Stop pulsing (LED goes off) |
|||
* CLEAR -> Turn LED off (legacy command) |
|||
* |
|||
* BUTTON OUTPUT: |
|||
* "B/true" when pressed (LOW with pull-up) |
|||
* "B/false" when released (HIGH) |
|||
*/ |
|||
|
|||
// Constants for pin assignments
|
|||
const int ledPin = 10; // Must be PWM-capable!
|
|||
const int buttonPin = 9; |
|||
|
|||
// Button state variables
|
|||
int buttonState = 1; |
|||
int lastButtonState = 1; |
|||
|
|||
// Debounce timing
|
|||
unsigned long lastDebounceTime = 0; |
|||
unsigned long debounceDelay = 50; |
|||
|
|||
// LED state machine
|
|||
enum LedMode { |
|||
LED_MODE_OFF, |
|||
LED_MODE_ON, |
|||
LED_MODE_DIM, |
|||
LED_MODE_PULSE |
|||
}; |
|||
|
|||
LedMode ledMode = LED_MODE_OFF; |
|||
|
|||
// Pulse animation variables
|
|||
unsigned long pulseStartTime = 0; |
|||
const unsigned long pulseCycleMs = 3000; // Full breath cycle duration in ms
|
|||
|
|||
void setup() { |
|||
buttonState = 1; |
|||
pinMode(ledPin, OUTPUT); |
|||
pinMode(buttonPin, INPUT_PULLUP); |
|||
analogWrite(ledPin, 0); |
|||
|
|||
Serial.begin(9600); |
|||
} |
|||
|
|||
void loop() { |
|||
// --- Button debouncing ---
|
|||
int reading = digitalRead(buttonPin); |
|||
|
|||
if (reading != lastButtonState) { |
|||
lastDebounceTime = millis(); |
|||
} |
|||
|
|||
if ((millis() - lastDebounceTime) > debounceDelay) { |
|||
if (reading != buttonState) { |
|||
buttonState = reading; |
|||
if (buttonState == 0) { |
|||
Serial.println("B/true"); |
|||
} else { |
|||
Serial.println("B/false"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
lastButtonState = reading; |
|||
|
|||
// --- Serial command handling ---
|
|||
if (Serial.available() > 0) { |
|||
String command = Serial.readStringUntil('\n'); |
|||
command.trim(); |
|||
|
|||
if (command == "LED_ON") { |
|||
ledMode = LED_MODE_ON; |
|||
analogWrite(ledPin, 255); |
|||
} |
|||
else if (command == "LED_OFF") { |
|||
ledMode = LED_MODE_OFF; |
|||
analogWrite(ledPin, 0); |
|||
} |
|||
else if (command.startsWith("LED_DIM:")) { |
|||
int percent = command.substring(8).toInt(); |
|||
percent = constrain(percent, 0, 100); |
|||
int pwmValue = map(percent, 0, 100, 0, 255); |
|||
ledMode = LED_MODE_DIM; |
|||
analogWrite(ledPin, pwmValue); |
|||
} |
|||
else if (command == "LED_PULSE_ON") { |
|||
ledMode = LED_MODE_PULSE; |
|||
pulseStartTime = millis(); |
|||
} |
|||
else if (command == "LED_PULSE_OFF") { |
|||
ledMode = LED_MODE_OFF; |
|||
analogWrite(ledPin, 0); |
|||
} |
|||
else if (command.startsWith("CLEAR")) { |
|||
ledMode = LED_MODE_OFF; |
|||
analogWrite(ledPin, 0); |
|||
} |
|||
} |
|||
|
|||
// --- Pulse animation (non-blocking) ---
|
|||
if (ledMode == LED_MODE_PULSE) { |
|||
unsigned long elapsed = millis() - pulseStartTime; |
|||
unsigned long pos = elapsed % pulseCycleMs; |
|||
|
|||
// Sine wave: 0 -> 1 -> 0 over one full "breath" cycle
|
|||
float phase = (float)pos / (float)pulseCycleMs; |
|||
float brightness = sin(phase * PI); |
|||
brightness = brightness * brightness; // Squared for organic ease-in-out
|
|||
|
|||
// Min ~8 so LED never fully goes dark (subtle "alive" glow at trough)
|
|||
int pwmValue = 2 + (int)(brightness * 247.0); |
|||
analogWrite(ledPin, pwmValue); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue