From 5f126e2b55fc0efe1906fa479daff624680651ec Mon Sep 17 00:00:00 2001 From: Lucas Peter Date: Mon, 23 Sep 2024 15:48:29 +0200 Subject: [PATCH] Add Status subsystem --- .../SkyPortal/Private/SkyPortalSubsystem.cpp | 155 ++++++++++++++++-- Source/SkyPortal/Public/SkyPortalSubsystem.h | 90 ++++++++-- 2 files changed, 225 insertions(+), 20 deletions(-) diff --git a/Source/SkyPortal/Private/SkyPortalSubsystem.cpp b/Source/SkyPortal/Private/SkyPortalSubsystem.cpp index e0754a8..dee295b 100644 --- a/Source/SkyPortal/Private/SkyPortalSubsystem.cpp +++ b/Source/SkyPortal/Private/SkyPortalSubsystem.cpp @@ -3,6 +3,9 @@ #include "SkyPortalSubsystem.h" #include "Engine/Engine.h" +#include "HAL/RunnableThread.h" +#include "Misc/ScopeLock.h" + DEFINE_LOG_CATEGORY(LogHIDApi); DEFINE_LOG_CATEGORY(LogSkyportalIO); @@ -10,13 +13,28 @@ void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); - // Custom initialization logic + // Start the status checker thread + StatusChecker = new FPortalStatusChecker(this, 0.1f); // Check every 5 seconds + StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal); + UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized")); } void USkyPortalSubsystem::Deinitialize() { + // Stop the thread when the subsystem is deinitialized + if (StatusChecker) + { + StatusChecker->Stop(); + StatusCheckerThread->WaitForCompletion(); + + delete StatusCheckerThread; + delete StatusChecker; + StatusChecker = nullptr; + StatusCheckerThread = nullptr; + } + // Disconnect portal if (PortalDevice) { hid_close(PortalDevice); @@ -263,6 +281,51 @@ void USkyPortalSubsystem::Sleep(int sleepMs) { FPlatformProcess::Sleep(sleepMs * 0.0001); } +void USkyPortalSubsystem::CheckComplexResponse() { + + if (!PortalDevice) { + return; + } + + RWBlock req, res; + + memset(req.buf, 0, rw_buf_size); + req.buf[1] = 'S'; + Write(&req); + + int BuffResponse = hid_read_timeout(PortalDevice, res.buf, rw_buf_size, TIMEOUT); + if (BuffResponse < 0) { + UE_LOG(LogSkyportalIO, Error, TEXT("Error.\n %s"), hid_error(PortalDevice)); + return; + } + EPortalCommand CommandResponse = GetPortalCommandFromChar(res.buf[1]); + switch (CommandResponse) + { + case A: + break; + case C: + break; + case J: + break; + case L: + break; + case M: + break; + case Q: + break; + case R: + break; + case S: + + break; + default: + break; + } + +} + + + /* Verify the command response, when only a character is sended by the portal. * *TODO: Refacto this function to handle better the response/output from the portal @@ -276,7 +339,7 @@ bool USkyPortalSubsystem::CheckResponse(RWBlock* res, char expect) int b = hid_read_timeout(PortalDevice, res->buf, rw_buf_size, TIMEOUT); if (b < 0) { - UE_LOG(LogSkyportalIO, Error, TEXT("Unable to read Skylander from Portal.\n")); + UE_LOG(LogSkyportalIO, Error, TEXT("Error.\n %s"), hid_error(PortalDevice)); return false; } @@ -348,6 +411,35 @@ bool USkyPortalSubsystem::WriteBlock(unsigned int block, unsigned char data[0x10 } +EPortalCommand USkyPortalSubsystem::GetPortalCommandFromChar(unsigned char Char) +{ + switch (Char) + { + case 'A': + return EPortalCommand::A; + case 'C': + return EPortalCommand::C; + case 'J': + return EPortalCommand::J; + case 'L': + return EPortalCommand::L; + case 'M': + return EPortalCommand::M; + case 'Q': + return EPortalCommand::Q; + case 'R': + return EPortalCommand::R; + case 'S': + return EPortalCommand::S; + default: + // Handle the case when the character doesn't match any enum + // Return a default or invalid value, or handle the error + UE_LOG(LogSkyportalIO, Warning, TEXT("Invalid character for Portal Command: %c"), TCHAR(Char)); + return EPortalCommand::S; // 'S' for Status as a default + } + +} + bool USkyPortalSubsystem::ReadBlock(unsigned int block, unsigned char data[0x10], int skylander) { RWBlock req, res; //request and response buffers unsigned char followup; @@ -411,7 +503,7 @@ bool USkyPortalSubsystem::ConnectPortal() Sleep(500); ChangePortalColor(FLinearColor(0xC8, 0xC8, 0xC8)); - UE_LOG(LogSkyportalIO, Log, TEXT("Portal Status: "), PortalStatus()); + UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: ")); } return bPortalConnected; } @@ -433,13 +525,56 @@ void USkyPortalSubsystem::SendPortalSound(USoundWave* Sound) -unsigned char USkyPortalSubsystem::PortalStatus() +FPortalStatusChecker::FPortalStatusChecker(USkyPortalSubsystem* InSubsystem, float InCheckInterval) + : SkyPortalSubsystem(InSubsystem), CheckInterval(InCheckInterval), bShouldRun(true) { - RWBlock req, res; +} - memset(req.buf, 0, rw_buf_size); - req.buf[1] = 'S'; - do { Write(&req); } while (CheckResponse(&res, 'S')); +bool FPortalStatusChecker::Init() +{ + // Initialization logic, if necessary (e.g., logging) + return true; +} + +uint32 FPortalStatusChecker::Run() +{ + // Main loop of the thread, runs until Stop() is called + while (bShouldRun) + { + // Check the portal status + CheckPortalStatus(); + + // Sleep for the specified interval + FPlatformProcess::Sleep(CheckInterval); + } + + return 0; // Exit code for the thread +} + +void FPortalStatusChecker::Stop() +{ + // Signal the thread to stop running + bShouldRun = false; +} + +void FPortalStatusChecker::Exit() +{ + // Cleanup after the thread has stopped +} + +void FPortalStatusChecker::CheckPortalStatus() +{ + // Ensure the subsystem is valid + if (SkyPortalSubsystem && SkyPortalSubsystem->bPortalConnected) + { + UE_LOG(LogSkyportalIO, Verbose, TEXT("Check portal")); + + // Call the subsystem function to get portal status + + SkyPortalSubsystem->CheckComplexResponse(); + + // Do something with the status (log, notify, etc.) + UE_LOG(LogSkyportalIO, Verbose, TEXT("Portal Status:")); + } +} - return res.buf[1]; -} \ No newline at end of file diff --git a/Source/SkyPortal/Public/SkyPortalSubsystem.h b/Source/SkyPortal/Public/SkyPortalSubsystem.h index e6f93fd..e84686f 100644 --- a/Source/SkyPortal/Public/SkyPortalSubsystem.h +++ b/Source/SkyPortal/Public/SkyPortalSubsystem.h @@ -3,6 +3,7 @@ #include "CoreMinimal.h" #include "Subsystems/EngineSubsystem.h" #include "hidapi.h" +#include "HAL/Runnable.h" #include "SkyPortalSubsystem.generated.h" @@ -29,13 +30,44 @@ enum EPortalCommand { S UMETA(DisplayName = "Status") }; +UENUM(BlueprintType) +enum class EFigureStatus : uint8 +{ + NOT_PRESENT = 0b00 UMETA(DisplayName = "Not Present"), + PRESENT = 0b01 UMETA(DisplayName = "Present"), + ADDED = 0b11 UMETA(DisplayName = "Added"), + REMOVED = 0b10 UMETA(DisplayName = "Removed") +}; +USTRUCT(BlueprintType) +struct FPortalStatusResponse +{ + GENERATED_BODY() + + // Array of statuses + UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure") + TArray StatusArray; + + // timestamp. + //only one byte long. This means that after the value 0xFF, it overflows back to 0x00. + // Since these are so far apart, it can be assumed that 0x00 is newer than anything in the range 0xF0 - 0xFF. + UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure") + uint8 Counter; + + // Should always be true + UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure") + bool bIsReady; +}; /* Macro constants Definitions */ #define rw_buf_size 0x21 #define TIMEOUT 30000 #define DEBUG true +typedef struct { + unsigned char buf[rw_buf_size]; int BytesTransferred; +} RWBlock; + DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All); DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All); @@ -46,6 +78,38 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderRemovedDelegate, int32, #pragma endregion +class FPortalStatusChecker : public FRunnable { +public: + // Constructor: pass the subsystem and desired check interval (in seconds) + FPortalStatusChecker(USkyPortalSubsystem* InSubsystem, float InCheckInterval); + + // FRunnable interface + virtual bool Init() override; + virtual uint32 Run() override; + virtual void Stop() override; + virtual void Exit() override; + +private: + // Pointer to the subsystem that contains PortalStatus() + USkyPortalSubsystem* SkyPortalSubsystem; + + // Time interval (in seconds) for status checking + float CheckInterval; + + // Thread control variables + FThreadSafeBool bShouldRun; + + // Helper function to check portal status + void CheckPortalStatus(); +}; + + + + + + + + /* Handle all the portal I/O * * @@ -107,28 +171,31 @@ public: FOnSkylanderRemovedDelegate OnSkylanderRemoved; + EPortalCommand GetPortalCommandFromChar(unsigned char Char); + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly) bool bPortalConnected = false; FString HidError; - - - unsigned char PortalStatus(); - + bool ReadBlock(unsigned int block, unsigned char data[0x10], int skylander); + bool WriteBlock(unsigned int, unsigned char[0x10], int); + bool CheckResponse(RWBlock*, char); + void CheckComplexResponse(); private: - typedef struct { - unsigned char buf[rw_buf_size]; int BytesTransferred; - } RWBlock; + static void Sleep(int sleepMs); + + void ActivatePortal(int active); void RestartPortal(); @@ -136,12 +203,14 @@ private: // Block/byte related data write/read functions bool OpenPortalHandle(); - bool ReadBlock(unsigned int block, unsigned char data[0x10], int skylander); - bool WriteBlock(unsigned int, unsigned char[0x10], int); - bool CheckResponse(RWBlock*, char); void Write(RWBlock*); + // Pointer to the status checker thread + FPortalStatusChecker* StatusChecker; + FRunnableThread* StatusCheckerThread; + + protected: //Constants @@ -155,3 +224,4 @@ protected: //const int ProductId = 336; }; +