From 2360897978a2cbbfae8d5cd8af8475ed501ae079 Mon Sep 17 00:00:00 2001 From: Lucas Peter Date: Fri, 20 Sep 2024 14:29:13 +0200 Subject: [PATCH] wip: implement portalIO TODO : Fix the write IO timing issue --- SkyPortal.uplugin | 1 - .../SkyPortal/Private/SkyPortalSubsystem.cpp | 185 +++++++++++++++++- Source/SkyPortal/Public/SkyPortalSubsystem.h | 42 +++- 3 files changed, 212 insertions(+), 16 deletions(-) diff --git a/SkyPortal.uplugin b/SkyPortal.uplugin index 10a316e..1722beb 100644 --- a/SkyPortal.uplugin +++ b/SkyPortal.uplugin @@ -21,6 +21,5 @@ "LoadingPhase": "PostDefault" } ], - "IsExperimentalVersion": false, "Sealed": true } \ No newline at end of file diff --git a/Source/SkyPortal/Private/SkyPortalSubsystem.cpp b/Source/SkyPortal/Private/SkyPortalSubsystem.cpp index 99b9498..9373016 100644 --- a/Source/SkyPortal/Private/SkyPortalSubsystem.cpp +++ b/Source/SkyPortal/Private/SkyPortalSubsystem.cpp @@ -1,17 +1,17 @@ -// A lot of this code is made because of the work of https://github.com/capull0/SkyDumper +// A lot of this code is made because of the work of https://github.com/capull0/SkyDumper and https://github.com/silicontrip/SkyReader #include "SkyPortalSubsystem.h" #include "Engine/Engine.h" DEFINE_LOG_CATEGORY(LogHIDApi); - +DEFINE_LOG_CATEGORY(LogSkyportalIO); void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); // Custom initialization logic - UE_LOG(LogTemp, Warning, TEXT("SkyPortalSubsystem Initialized")); + UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized")); // Initialize HIDAPI int res = hid_init(); @@ -38,7 +38,7 @@ void USkyPortalSubsystem::Deinitialize() hid_exit(); - UE_LOG(LogTemp, Warning, TEXT("SkyPortalSubsystem Deinitialized")); + UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Deinitialized")); Super::Deinitialize(); } @@ -90,6 +90,7 @@ bool USkyPortalSubsystem::ConnectPortal() { // Free the device list hid_free_enumeration(list); + bPortalConnected = true; return true; } @@ -108,13 +109,179 @@ bool USkyPortalSubsystem::ConnectPortal() { return false; } +void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color) +{ + unsigned char r = Color.R; + unsigned char g = Color.G; + unsigned char b = Color.B; -void USkyPortalSubsystem::ReadPortal() { - if (PortalDevice) { - hid_read(PortalDevice, PortalRawData, 16); - //RawDataArray.Append(PortalRawData, 16); - } + RWBlock req; + memset(req.buf, 0, rw_buf_size); + req.buf[1] = 'C'; + req.buf[2] = r; // R + req.buf[3] = g; // G + req.buf[4] = b; // B + + // no response for this one. + Write(&req); } +void USkyPortalSubsystem::Write(RWBlock* pb) { + // TODO: Need to make this function async + if (!ensure(PortalDevice)) { + UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found")); + return; + } + + pb->buf[0] = 0; // Use report 0 + + /* + for (int attempt = 0; attempt < 10; attempt++) { + if (hid_write(PortalDevice, pb->buf, 0x21) == -1) { + Sleep(100); + } + else { + return; + } + } + */ + ensureMsgf(hid_write(PortalDevice, pb->buf, 0x21) != -1, TEXT("Unable to write to Portal, %s"), hid_error(PortalDevice)); + UE_LOG(LogSkyportalIO, Verbose, TEXT("Writed")); +} + + +void USkyPortalSubsystem::Sleep(int sleepMs) { + FPlatformProcess::Sleep(sleepMs * 0.0001); +} + + +bool USkyPortalSubsystem::CheckResponse(RWBlock* res, char expect) +{ + if (!PortalDevice) { + return false; + } + + 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")); + return false; + } + + res->BytesTransferred = b; + + /* this is here to debug the different responses from the portal. + #if DEBUG + SkylanderIO* skio; + skio = new SkylanderIO(); + printf("<<<\n"); + skio->fprinthex(stdout, res->buf, 0x21); + delete skio; + #endif + */ + + // found wireless USB but portal is not connected + if (res->buf[0] == 'Z') + { + UE_LOG(LogSkyportalIO, Error, TEXT("found wireless USB but portal is not connected")); + return false; + } + + // Status says no skylander on portal + if (res->buf[0] == 'Q' && res->buf[1] == 0) { + UE_LOG(LogSkyportalIO, Warning, TEXT("Status says no skylander on portal")); + } + + return (res->buf[0] != expect); +} + + + +bool USkyPortalSubsystem::WriteBlock(unsigned int block, unsigned char data[0x10], int skylander) { + RWBlock req, res; //request and response buffer + unsigned char verify[0x10]; // A 16-byte array used to verify the data that was written to the portal. + + UE_LOG(LogSkyportalIO, Verbose, TEXT("Trying to write the current block :%X\n"), block); + + // Trying to write 3 times + for (int retries = 0; retries < 3; retries++) { + // Write request + // W 57 10 <0x10 bytes of data> + memset(req.buf, 0, rw_buf_size);//Reset request buffer + req.buf[1] = 'W'; + req.buf[2] = 0x10 + skylander; + req.buf[3] = (unsigned char)block; + memcpy(req.buf + 4, data, 0x10); + + do { Write(&req); } while (CheckResponse(&res, 'W')); + + Sleep(100); //Wait 0.1 seconds for write to take effect + + memset(verify, 0xCD, sizeof(verify)); // 0xCD is a placeholder value + ReadBlock(block, verify, skylander); + + if (memcmp(data, verify, sizeof(verify))) { + UE_LOG(LogSkyportalIO, Error, TEXT("verification of the written block failed")); + continue; //retry + } + UE_LOG(LogSkyportalIO, Verbose, TEXT("block successfully written")); + return true; + } + UE_LOG(LogSkyportalIO, Fatal, TEXT("failed to write block")); + return false; +} + + +bool USkyPortalSubsystem::ReadBlock(unsigned int block, unsigned char data[0x10], int skylander) { + RWBlock req, res; //request and response buffers + unsigned char followup; + + UE_LOG(LogSkyportalIO, Verbose, TEXT("PortalIO:ReadBlock :%X"), block); + + + // Checking if the block is not out of range + if (!ensure(block < 0x40)) { + UE_LOG(LogSkyportalIO, Error, TEXT("PortalIO:ReadBlock failed, block out of range")); + return false; // Early return instead of throwing an exception + } + + // Send query request + + // Trying to read data 15x + for (int attempt = 0; attempt < 15; attempt++) + { + int i = 0; + + memset(req.buf, 0, rw_buf_size); // Clear the request buffer (initialize all bytes to zero) + req.buf[1] = 'Q'; + followup = 0x10 + skylander; + req.buf[2] = followup; + if (block == 0) { + req.buf[2] = followup + 0x10; // may not be needed + } + req.buf[3] = (unsigned char)block; + + memset(&(res.buf), 0, rw_buf_size); // Clear the response buffer to prepare for incoming data + + + do { Write(&req); } while (CheckResponse(&res, 'Q')); + + if (res.buf[0] == 'Q' && res.buf[2] == (unsigned char)block) { + // Got our query back + if (res.buf[1] == followup) { + // got the query back with no error + memcpy(data, res.buf + 3, 0x10); + UE_LOG(LogSkyportalIO, Verbose, TEXT("PortalIO:ReadBlock success")); + return true; + } + } + + + } // retries + + UE_LOG(LogSkyportalIO, Fatal, TEXT("PortalIO:ReadBlock failed after retries")); + ensureMsgf(false, TEXT("PortalIO: Failed to read block after multiple retries")); + return false; +} \ No newline at end of file diff --git a/Source/SkyPortal/Public/SkyPortalSubsystem.h b/Source/SkyPortal/Public/SkyPortalSubsystem.h index 289c668..75ca7ae 100644 --- a/Source/SkyPortal/Public/SkyPortalSubsystem.h +++ b/Source/SkyPortal/Public/SkyPortalSubsystem.h @@ -9,8 +9,12 @@ /* Macro Definitions */ +#define rw_buf_size 0x21 #define TIMEOUT 30000 +#define DEBUG true + DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All); /* Subsystem */ UCLASS() @@ -24,19 +28,43 @@ public: virtual void Deinitialize() override; // Connect to Portal, return false if portal is not found - UFUNCTION(BlueprintCallable, CallInEditor) + UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal")) bool ConnectPortal(); + UPROPERTY(VisibleAnywhere, BlueprintReadOnly) + bool bPortalConnected = false; + FString HidError; + + UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "Color", Category = "SkyPortal")) + void ChangePortalColor(const FLinearColor& Color = FLinearColor::Green); + + + + + + + + private: //Portal ref used in the subsystem hid_device* PortalDevice; - unsigned char* PortalRawData; + typedef struct { + unsigned char buf[rw_buf_size]; int BytesTransferred; + } RWBlock; + + + static void Sleep(int sleepMs); + + + // Block/byte related data write/read functions + bool ReadBlock(unsigned int block, unsigned char data[0x10], int skylander); + bool WriteBlock(unsigned int, unsigned char[0x10], int); + bool CheckResponse(RWBlock* res, char expect); + void Write(RWBlock* pb); - FString RawData(); - void ReadPortal(); protected: @@ -44,8 +72,10 @@ protected: const int VendorIds[4] = { 0x12ba, 0x54c, 0x1430, 0x1430 }; const int ProductIds[4] = { 0x150, 0x967, 0x1f17 }; + + /////Defaults values, should not be used - const int VendorId = 5168; - const int ProductId = 336; + //const int VendorId = 5168; + //const int ProductId = 336; };