wip: implement portalIO

TODO : Fix the write IO timing issue
This commit is contained in:
Lucas Peter 2024-09-20 14:29:13 +02:00
parent ea75a1d07e
commit 2360897978
No known key found for this signature in database
3 changed files with 212 additions and 16 deletions

View file

@ -21,6 +21,5 @@
"LoadingPhase": "PostDefault"
}
],
"IsExperimentalVersion": false,
"Sealed": true
}

View file

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

View file

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