Browse Source

Removed FaceDetector and nDisplay Plugin, Added resaved State and project manager after Data Assets, added serialcom plugin

master
Tillman Staffen 3 weeks ago
parent
commit
e9bdd566e5
  1. 1
      Unreal/Config/DefaultGame.ini
  2. BIN
      Unreal/Content/Project/BP/BP_Project_Manager.uasset
  3. BIN
      Unreal/Content/Project/BP/Modes/States/DT_ProjectStates.uasset
  4. BIN
      Unreal/Content/Project/BP/Modes/States/DT_ProjectStates_Demo.uasset
  5. 24
      Unreal/Plugins/AvatarCore_FaceDetector/AvatarCore_FaceDetector.uplugin
  6. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Content/BP_FaceDetector.uasset
  7. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_Face.uasset
  8. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_FaceArea.uasset
  9. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/BP_TargetActor.uasset
  10. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/Target.uasset
  11. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Resources/Icon128.png
  12. 60
      Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/AvatarCore_FaceDetector.Build.cs
  13. 20
      Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarCore_FaceDetector.cpp
  14. 307
      Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarFaceDetectorSubsystem.cpp
  15. 14
      Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarCore_FaceDetector.h
  16. 56
      Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarFaceDetectorSubsystem.h
  17. 45
      Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/FaceDetectorTypes.h
  18. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/FaceDetector.exe
  19. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/blaze_face_short_range.tflite
  20. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/face_landmarker.task
  21. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffmpeg.exe
  22. BIN
      Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffplay.exe
  23. BIN
      Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/BP_StateManager.uasset
  24. BIN
      Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/StructsAndEnums/S_StateProcedure.uasset
  25. BIN
      Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugProjectStates.uasset
  26. 34
      Unreal/Plugins/AvatarCore_nDisplay/AvatarCore_nDisplay.uplugin
  27. BIN
      Unreal/Plugins/AvatarCore_nDisplay/Content/AvatarBaseReplicated.uasset
  28. BIN
      Unreal/Plugins/AvatarCore_nDisplay/Content/BodyReplicated_AnimBP.uasset
  29. BIN
      Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicated_AvatarCore_AnimBP.uasset
  30. BIN
      Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicating_AvatarCore_AnimInst_Face_Child.uasset
  31. BIN
      Unreal/Plugins/AvatarCore_nDisplay/Resources/Icon128.png
  32. 54
      Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/AvatarCore_nDisplay.Build.cs
  33. 22
      Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplay.cpp
  34. 161
      Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplayBPLibrary.cpp
  35. 14
      Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/ReplicatedFaceCPPAnimInstance.cpp
  36. 14
      Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplay.h
  37. 59
      Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplayBPLibrary.h
  38. 30
      Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/ReplicatedFaceCPPAnimInstance.h
  39. BIN
      Unreal/Plugins/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset
  40. 15
      Unreal/Plugins/SerialCOM/Content/Arduino_Example/Instructions.txt
  41. 18
      Unreal/Plugins/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino
  42. BIN
      Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset
  43. 39
      Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt
  44. 29
      Unreal/Plugins/SerialCOM/INSTRUCTIONS-PLUGIN.txt
  45. 23
      Unreal/Plugins/SerialCOM/LICENSE.txt
  46. 32
      Unreal/Plugins/SerialCOM/RELEASE_v5.5.4.2.1.txt
  47. BIN
      Unreal/Plugins/SerialCOM/Resources/Icon128.png
  48. 28
      Unreal/Plugins/SerialCOM/SERIALCOM.uplugin
  49. BIN
      Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor-SERIALCOM.pdb
  50. 7
      Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor.modules
  51. BIN
      Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset
  52. 15
      Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/Instructions.txt
  53. 18
      Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino
  54. BIN
      Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset
  55. 39
      Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt
  56. 29
      Unreal/Plugins/SerialCOM/SerialCOM/INSTRUCTIONS-PLUGIN.txt
  57. 23
      Unreal/Plugins/SerialCOM/SerialCOM/LICENSE.txt
  58. 32
      Unreal/Plugins/SerialCOM/SerialCOM/RELEASE_v5.5.4.2.1.txt
  59. BIN
      Unreal/Plugins/SerialCOM/SerialCOM/Resources/Icon128.png
  60. 28
      Unreal/Plugins/SerialCOM/SerialCOM/SERIALCOM.uplugin
  61. 21
      Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp
  62. 539
      Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp
  63. 16
      Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h
  64. 321
      Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SerialCom.h
  65. 20
      Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs
  66. 21
      Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp
  67. 539
      Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp
  68. 16
      Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h
  69. 321
      Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SerialCom.h
  70. 20
      Unreal/Plugins/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs
  71. 146
      Unreal/Plugins/SerialCOM/arduino_ptt_sketch/spie_arduino_ptt_sketch.ino
  72. 24
      Unreal/SPIE_Avatar.uproject

1
Unreal/Config/DefaultGame.ini

@ -114,6 +114,7 @@ bSkipMovies=False
+DirectoriesToAlwaysCook=(Path="/NNEDenoiser") +DirectoriesToAlwaysCook=(Path="/NNEDenoiser")
+DirectoriesToAlwaysCook=(Path="/RuntimeMetaHumanLipSync/RealisticModelData") +DirectoriesToAlwaysCook=(Path="/RuntimeMetaHumanLipSync/RealisticModelData")
+DirectoriesToAlwaysCook=(Path="/RuntimeMetaHumanLipSync/ModelData") +DirectoriesToAlwaysCook=(Path="/RuntimeMetaHumanLipSync/ModelData")
+DirectoriesToAlwaysCook=(Path="/MetaHumanCoreTech")
+DirectoriesToAlwaysStageAsUFS=(Path="Schema") +DirectoriesToAlwaysStageAsUFS=(Path="Schema")
+DirectoriesToAlwaysStageAsNonUFS=(Path="Schema") +DirectoriesToAlwaysStageAsNonUFS=(Path="Schema")
+DirectoriesToAlwaysStageAsNonUFS=(Path="Certificates") +DirectoriesToAlwaysStageAsNonUFS=(Path="Certificates")

BIN
Unreal/Content/Project/BP/BP_Project_Manager.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Content/Project/BP/Modes/States/DT_ProjectStates.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Content/Project/BP/Modes/States/DT_ProjectStates_Demo.uasset (Stored with Git LFS)

Binary file not shown.

24
Unreal/Plugins/AvatarCore_FaceDetector/AvatarCore_FaceDetector.uplugin

@ -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"
}
]
}

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Content/BP_FaceDetector.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_Face.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_FaceArea.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/BP_TargetActor.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/Target.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Resources/Icon128.png (Stored with Git LFS)

Binary file not shown.

60
Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/AvatarCore_FaceDetector.Build.cs

@ -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"));
}
}

20
Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarCore_FaceDetector.cpp

@ -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)

307
Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarFaceDetectorSubsystem.cpp

@ -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);
});
}

14
Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarCore_FaceDetector.h

@ -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;
};

56
Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarFaceDetectorSubsystem.h

@ -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;
};

45
Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/FaceDetectorTypes.h

@ -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;
};

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/FaceDetector.exe

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/blaze_face_short_range.tflite

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/face_landmarker.task

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffmpeg.exe

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffplay.exe

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/BP_StateManager.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/StructsAndEnums/S_StateProcedure.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugProjectStates.uasset (Stored with Git LFS)

Binary file not shown.

34
Unreal/Plugins/AvatarCore_nDisplay/AvatarCore_nDisplay.uplugin

@ -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
}
]
}

BIN
Unreal/Plugins/AvatarCore_nDisplay/Content/AvatarBaseReplicated.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_nDisplay/Content/BodyReplicated_AnimBP.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicated_AvatarCore_AnimBP.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicating_AvatarCore_AnimInst_Face_Child.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Unreal/Plugins/AvatarCore_nDisplay/Resources/Icon128.png (Stored with Git LFS)

Binary file not shown.

54
Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/AvatarCore_nDisplay.Build.cs

@ -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 ...
}
);
}
}

22
Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplay.cpp

@ -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)

161
Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplayBPLibrary.cpp

@ -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));
}
}

14
Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/ReplicatedFaceCPPAnimInstance.cpp

@ -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();
}

14
Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplay.h

@ -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;
};

59
Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplayBPLibrary.h

@ -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
);
};

30
Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/ReplicatedFaceCPPAnimInstance.h

@ -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;
};

BIN
Unreal/Plugins/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset (Stored with Git LFS)

Binary file not shown.

15
Unreal/Plugins/SerialCOM/Content/Arduino_Example/Instructions.txt

@ -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.

18
Unreal/Plugins/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino

@ -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);
}

BIN
Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset (Stored with Git LFS)

Binary file not shown.

39
Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt

@ -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

29
Unreal/Plugins/SerialCOM/INSTRUCTIONS-PLUGIN.txt

@ -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/

23
Unreal/Plugins/SerialCOM/LICENSE.txt

@ -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.

32
Unreal/Plugins/SerialCOM/RELEASE_v5.5.4.2.1.txt

@ -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)

BIN
Unreal/Plugins/SerialCOM/Resources/Icon128.png (Stored with Git LFS)

Binary file not shown.

28
Unreal/Plugins/SerialCOM/SERIALCOM.uplugin

@ -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
}

BIN
Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffprobe.exe → Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor-SERIALCOM.pdb

Binary file not shown.

7
Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor.modules

@ -0,0 +1,7 @@
{
"BuildId": "33043543",
"Modules":
{
"SERIALCOM": "UnrealEditor-SERIALCOM.dll"
}
}

BIN
Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset (Stored with Git LFS)

Binary file not shown.

15
Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/Instructions.txt

@ -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.

18
Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino

@ -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);
}

BIN
Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset (Stored with Git LFS)

Binary file not shown.

39
Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt

@ -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

29
Unreal/Plugins/SerialCOM/SerialCOM/INSTRUCTIONS-PLUGIN.txt

@ -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/

23
Unreal/Plugins/SerialCOM/SerialCOM/LICENSE.txt

@ -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.

32
Unreal/Plugins/SerialCOM/SerialCOM/RELEASE_v5.5.4.2.1.txt

@ -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)

BIN
Unreal/Plugins/SerialCOM/SerialCOM/Resources/Icon128.png (Stored with Git LFS)

Binary file not shown.

28
Unreal/Plugins/SerialCOM/SerialCOM/SERIALCOM.uplugin

@ -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
}

21
Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp

@ -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"));
}

539
Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp

@ -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");
}
}
*/
/////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////

16
Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h

@ -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;
};

321
Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SerialCom.h

@ -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;
};

20
Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs

@ -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"
}
);
}
}

21
Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp

@ -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"));
}

539
Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp

@ -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");
}
}
*/
/////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////

16
Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h

@ -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;
};

321
Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SerialCom.h

@ -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;
};

20
Unreal/Plugins/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs

@ -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"
}
);
}
}

146
Unreal/Plugins/SerialCOM/arduino_ptt_sketch/spie_arduino_ptt_sketch.ino

@ -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);
}
}

24
Unreal/SPIE_Avatar.uproject

@ -41,6 +41,30 @@
{ {
"Name": "HDRIBackdrop", "Name": "HDRIBackdrop",
"Enabled": true "Enabled": true
},
{
"Name": "MetaHuman",
"Enabled": true,
"SupportedTargetPlatforms": [
"Win64",
"Linux"
]
},
{
"Name": "MetaHumanCalibrationProcessing",
"Enabled": true,
"SupportedTargetPlatforms": [
"Win64",
"Linux"
]
},
{
"Name": "MetaHumanLiveLink",
"Enabled": true
},
{
"Name": "RigLogic",
"Enabled": true
} }
] ]
} }
Loading…
Cancel
Save