2024-09-20 12:29:13 +00:00
|
|
|
// A lot of this code is made because of the work of https://github.com/capull0/SkyDumper and https://github.com/silicontrip/SkyReader
|
2024-09-19 15:44:34 +00:00
|
|
|
|
2024-09-18 13:30:30 +00:00
|
|
|
#include "SkyPortalSubsystem.h"
|
|
|
|
#include "Engine/Engine.h"
|
2024-09-19 12:03:16 +00:00
|
|
|
|
2024-09-23 13:48:29 +00:00
|
|
|
#include "HAL/RunnableThread.h"
|
|
|
|
#include "Misc/ScopeLock.h"
|
2024-09-24 16:24:05 +00:00
|
|
|
#include "SkyPortalIO.h"
|
2024-09-23 13:48:29 +00:00
|
|
|
|
2024-09-18 13:30:30 +00:00
|
|
|
|
|
|
|
void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
|
|
|
{
|
2024-09-19 12:03:16 +00:00
|
|
|
|
2024-09-18 13:30:30 +00:00
|
|
|
Super::Initialize(Collection);
|
2024-09-23 13:48:29 +00:00
|
|
|
// Start the status checker thread
|
|
|
|
StatusChecker = new FPortalStatusChecker(this, 0.1f); // Check every 5 seconds
|
|
|
|
StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);
|
|
|
|
|
2024-09-20 12:29:13 +00:00
|
|
|
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
|
2024-09-18 14:54:36 +00:00
|
|
|
|
2024-09-18 13:30:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void USkyPortalSubsystem::Deinitialize()
|
|
|
|
{
|
2024-09-23 13:48:29 +00:00
|
|
|
// Stop the thread when the subsystem is deinitialized
|
|
|
|
if (StatusChecker)
|
|
|
|
{
|
|
|
|
StatusChecker->Stop();
|
|
|
|
StatusCheckerThread->WaitForCompletion();
|
|
|
|
|
|
|
|
delete StatusCheckerThread;
|
|
|
|
delete StatusChecker;
|
|
|
|
StatusChecker = nullptr;
|
|
|
|
StatusCheckerThread = nullptr;
|
|
|
|
}
|
|
|
|
|
2024-09-19 12:03:16 +00:00
|
|
|
// Disconnect portal
|
|
|
|
if (PortalDevice) {
|
|
|
|
hid_close(PortalDevice);
|
|
|
|
}
|
|
|
|
hid_exit();
|
|
|
|
|
2024-09-18 16:10:50 +00:00
|
|
|
|
2024-09-20 12:29:13 +00:00
|
|
|
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Deinitialized"));
|
2024-09-18 13:30:30 +00:00
|
|
|
Super::Deinitialize();
|
2024-09-19 12:03:16 +00:00
|
|
|
}
|
|
|
|
|
2024-09-20 12:29:13 +00:00
|
|
|
// Antenna up / activate
|
|
|
|
void USkyPortalSubsystem::ActivatePortal(int active)
|
|
|
|
{
|
|
|
|
RWBlock req, res;
|
|
|
|
|
|
|
|
memset(req.buf, 0, rw_buf_size);
|
|
|
|
req.buf[1] = 'A';
|
|
|
|
req.buf[2] = active;
|
|
|
|
do { Write(&req); } while (CheckResponse(&res, 'A'));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start portal
|
|
|
|
void USkyPortalSubsystem::RestartPortal()
|
|
|
|
{
|
|
|
|
RWBlock req, res;
|
|
|
|
|
|
|
|
memset(req.buf, 0, rw_buf_size);
|
|
|
|
req.buf[1] = 'R';
|
|
|
|
|
|
|
|
do { Write(&req); } while (CheckResponse(&res, 'R'));
|
|
|
|
|
|
|
|
}
|
2024-09-19 12:03:16 +00:00
|
|
|
|
|
|
|
|
2024-09-19 15:44:34 +00:00
|
|
|
/*
|
|
|
|
Number of endpoints : 2
|
|
|
|
found an IN End Point 0 with attributes interrupt and address 0x1
|
|
|
|
found an OUT End Point 1 with attributes interrupt and address 0x1
|
|
|
|
*/
|
2024-09-20 12:29:13 +00:00
|
|
|
bool USkyPortalSubsystem::OpenPortalHandle() {
|
|
|
|
|
|
|
|
//reset
|
|
|
|
if (PortalDevice) {
|
|
|
|
hid_close(PortalDevice);
|
|
|
|
}
|
|
|
|
|
2024-09-19 15:44:34 +00:00
|
|
|
/*
|
|
|
|
Declare two pointers to hold information about HID devices.
|
|
|
|
"list" will point to the head of the linked list of devices,
|
|
|
|
"attributes" will be used to iterate through the list.
|
|
|
|
*/
|
|
|
|
struct hid_device_info* list, * attributes;
|
|
|
|
|
|
|
|
list = hid_enumerate(0x0, 0x0);
|
|
|
|
// If `list` is NULL, that means no devices were found or there was an error.
|
|
|
|
// In this case, print an error message and terminate the program.
|
|
|
|
if (!list) {
|
|
|
|
UE_LOG(LogHIDApi, Error, TEXT("No devices found"));
|
2024-09-19 12:03:16 +00:00
|
|
|
// Get the error message from the HIDAPI
|
|
|
|
HidError = hid_error(NULL);
|
2024-09-19 15:44:34 +00:00
|
|
|
UE_LOG(LogHIDApi, Error, TEXT("%s"), *HidError);
|
2024-09-19 12:03:16 +00:00
|
|
|
return false;
|
|
|
|
}
|
2024-09-19 15:44:34 +00:00
|
|
|
attributes = list;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Iterate through the linked list of devices
|
|
|
|
int vendorCount = sizeof(VendorIds) / sizeof(VendorIds[0]);
|
|
|
|
int productCount = sizeof(ProductIds) / sizeof(ProductIds[0]);
|
|
|
|
|
|
|
|
while (attributes) {
|
|
|
|
// Check if the devices match any of vendor_id and product_id
|
|
|
|
for (int i = 0; i < vendorCount; i++) {
|
|
|
|
for (int j = 0; j < productCount; j++) {
|
|
|
|
if (attributes->vendor_id == VendorIds[i] && attributes->product_id == ProductIds[j]) {
|
|
|
|
UE_LOG(LogHIDApi, Display, TEXT("Portal found"));
|
|
|
|
UE_LOG(LogHIDApi, Log, TEXT("Vendor ID: 0x%x, Product ID: 0x%x"), attributes->vendor_id, attributes->product_id);
|
|
|
|
|
|
|
|
PortalDevice = hid_open(attributes->vendor_id, attributes->product_id, NULL);
|
|
|
|
if (PortalDevice) {
|
|
|
|
UE_LOG(LogHIDApi, Display, TEXT("Successful connection to Portal."));
|
|
|
|
|
|
|
|
// Free the device list
|
|
|
|
hid_free_enumeration(list);
|
2024-09-20 12:29:13 +00:00
|
|
|
//bPortalConnected = true;
|
2024-09-19 15:44:34 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move to the next device in the list
|
|
|
|
attributes = attributes->next;
|
|
|
|
}
|
2024-09-19 12:03:16 +00:00
|
|
|
|
2024-09-19 15:44:34 +00:00
|
|
|
// Free the device list
|
|
|
|
hid_free_enumeration(list);
|
2024-09-19 20:17:18 +00:00
|
|
|
UE_LOG(LogHIDApi, Error, TEXT("No Portals found"));
|
2024-09-19 15:44:34 +00:00
|
|
|
return false;
|
2024-09-19 12:03:16 +00:00
|
|
|
}
|
2024-09-21 22:01:15 +00:00
|
|
|
//{ Region Color & light functions
|
2024-09-20 12:29:13 +00:00
|
|
|
void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color)
|
|
|
|
{
|
2024-09-19 15:44:34 +00:00
|
|
|
|
2024-09-23 16:06:55 +00:00
|
|
|
unsigned char r = FMath::Clamp(Color.R * 100, 0.0f, 255.0f);
|
|
|
|
unsigned char g = FMath::Clamp(Color.G * 100, 0.0f, 255.0f);
|
|
|
|
unsigned char b = FMath::Clamp(Color.B * 100, 0.0f, 255.0f);
|
2024-09-19 15:44:34 +00:00
|
|
|
|
2024-09-20 12:29:13 +00:00
|
|
|
RWBlock req;
|
|
|
|
|
|
|
|
memset(req.buf, 0, rw_buf_size);
|
2024-09-21 22:01:15 +00:00
|
|
|
|
2024-09-20 12:29:13 +00:00
|
|
|
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);
|
|
|
|
}
|
2024-09-22 21:58:01 +00:00
|
|
|
void USkyPortalSubsystem::ChangePortalColorside(const FLinearColor& Color, const EPortalSide PortalSide, const float BlendTime)
|
2024-09-21 22:01:15 +00:00
|
|
|
{
|
|
|
|
|
2024-09-22 21:58:01 +00:00
|
|
|
unsigned char r = FMath::Clamp(Color.R * 100, 0, 255);
|
|
|
|
unsigned char g = FMath::Clamp(Color.G * 100, 0, 255);
|
|
|
|
unsigned char b = FMath::Clamp(Color.B * 100, 0, 255);
|
|
|
|
EPortalSide _portalside;
|
2024-09-21 22:01:15 +00:00
|
|
|
RWBlock req, res;
|
|
|
|
|
|
|
|
memset(req.buf, 0, rw_buf_size);
|
|
|
|
|
2024-09-22 21:58:01 +00:00
|
|
|
if (PortalSide == EPortalSide::BOTH) {
|
|
|
|
_portalside = EPortalSide::LEFT;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (_portalside) {
|
2024-09-21 22:01:15 +00:00
|
|
|
case EPortalSide::LEFT:
|
2024-09-22 21:58:01 +00:00
|
|
|
req.buf[1] = 'J';
|
2024-09-21 22:01:15 +00:00
|
|
|
req.buf[2] = 0x00;
|
|
|
|
case EPortalSide::RIGHT:
|
2024-09-22 21:58:01 +00:00
|
|
|
req.buf[1] = 'J';
|
2024-09-21 22:01:15 +00:00
|
|
|
req.buf[2] = 0x02;
|
2024-09-22 21:58:01 +00:00
|
|
|
case EPortalSide::TRAP:
|
|
|
|
req.buf[1] = 'L';
|
|
|
|
req.buf[2] = 0x01;
|
|
|
|
req.buf[3] = FMath::Max3(r, g, b); // calculate brightness
|
|
|
|
Write(&req); //since it's a color command for trap, only 3 bytes are needed, no response.
|
|
|
|
return;
|
2024-09-21 22:01:15 +00:00
|
|
|
}
|
2024-09-22 21:58:01 +00:00
|
|
|
|
2024-09-21 22:01:15 +00:00
|
|
|
req.buf[3] = r; // R
|
|
|
|
req.buf[4] = g; // G
|
|
|
|
req.buf[5] = b; // B
|
|
|
|
|
|
|
|
//Convert the time in millisecond into two bytes
|
|
|
|
uint16_t _time = BlendTime;
|
|
|
|
uint8_t _time_low = _time & 0xFF; // Get the low byte by masking the least significant 8 bits
|
|
|
|
uint8_t _time_high = (_time >> 8) & 0xFF; // Get the high byte extracted by shifting the bits 8 positions to the right and masking the result
|
|
|
|
req.buf[6] = _time_low;
|
|
|
|
req.buf[7] = _time_high;
|
|
|
|
|
|
|
|
do { Write(&req); } while (CheckResponse(&res, 'J'));
|
|
|
|
|
2024-09-22 21:58:01 +00:00
|
|
|
if (PortalSide == EPortalSide::BOTH) {
|
|
|
|
ChangePortalColorside(Color, EPortalSide::RIGHT, BlendTime); // send a second command for the right side
|
|
|
|
}
|
2024-09-21 22:01:15 +00:00
|
|
|
}
|
|
|
|
//}
|
|
|
|
|
2024-09-20 12:29:13 +00:00
|
|
|
|
2024-09-21 22:01:15 +00:00
|
|
|
// write function need to be different on windows, as hid_write doesn't work with the way windows handle I/O
|
2024-09-21 15:12:02 +00:00
|
|
|
#if PLATFORM_WINDOWS
|
|
|
|
|
|
|
|
#include <Windows.h>
|
|
|
|
|
|
|
|
#define HID_CTL_CODE(id) \
|
|
|
|
CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_NEITHER, FILE_ANY_ACCESS)
|
|
|
|
#define HID_IN_CTL_CODE(id) \
|
|
|
|
CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_IN_DIRECT, FILE_ANY_ACCESS)
|
|
|
|
#define IOCTL_HID_SET_OUTPUT_REPORT HID_IN_CTL_CODE(101)
|
|
|
|
|
|
|
|
struct hid_device_ {
|
|
|
|
HANDLE device_handle;
|
|
|
|
BOOL blocking;
|
|
|
|
USHORT output_report_length;
|
|
|
|
unsigned char* write_buf;
|
|
|
|
size_t input_report_length;
|
|
|
|
USHORT feature_report_length;
|
|
|
|
unsigned char* feature_buf;
|
|
|
|
wchar_t* last_error_str;
|
|
|
|
BOOL read_pending;
|
|
|
|
char* read_buf;
|
|
|
|
OVERLAPPED ol;
|
|
|
|
OVERLAPPED write_ol;
|
|
|
|
struct hid_device_info* device_info;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void USkyPortalSubsystem::Write(RWBlock* pb)
|
|
|
|
{
|
2024-09-21 22:01:15 +00:00
|
|
|
if (!ensure(PortalDevice)) {
|
|
|
|
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
|
|
|
return;
|
|
|
|
}
|
2024-09-21 15:12:02 +00:00
|
|
|
BOOL res;
|
|
|
|
|
|
|
|
OVERLAPPED ol;
|
|
|
|
memset(&ol, 0, sizeof(ol));
|
|
|
|
|
|
|
|
DWORD bytes_returned;
|
|
|
|
|
|
|
|
res = DeviceIoControl(PortalDevice->device_handle,
|
|
|
|
IOCTL_HID_SET_OUTPUT_REPORT,
|
|
|
|
(unsigned char*)pb->buf, 0x21,
|
|
|
|
(unsigned char*)pb->buf, 0x21,
|
|
|
|
&bytes_returned, &ol);
|
2024-09-21 22:01:15 +00:00
|
|
|
ensureMsgf(res, TEXT("Unable to write to Portal"));
|
2024-09-21 15:12:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
2024-09-22 21:58:01 +00:00
|
|
|
void USkyPortalSubsystem::Write(RWBlock* pb) {
|
2024-09-20 12:29:13 +00:00
|
|
|
|
2024-09-22 21:58:01 +00:00
|
|
|
if (!ensure(PortalDevice)) {
|
|
|
|
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
|
|
|
return;
|
|
|
|
}
|
2024-09-20 12:29:13 +00:00
|
|
|
|
2024-09-22 21:58:01 +00:00
|
|
|
pb->buf[0] = 0; // Use report 0
|
2024-09-20 12:29:13 +00:00
|
|
|
|
|
|
|
|
2024-09-22 21:58:01 +00:00
|
|
|
ensureMsgf(hid_write(PortalDevice, pb->buf, 0x21) != -1, TEXT("Unable to write to Portal, %s"), hid_error(PortalDevice));
|
|
|
|
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to write to Portal. error:\n %s"), hid_error(PortalDevice));
|
2024-09-20 12:29:13 +00:00
|
|
|
}
|
2024-09-21 15:12:02 +00:00
|
|
|
#endif
|
2024-09-20 12:29:13 +00:00
|
|
|
|
2024-09-19 15:44:34 +00:00
|
|
|
|
2024-09-23 16:06:55 +00:00
|
|
|
FPortalStatusData USkyPortalSubsystem::ParsePortalStatus(const RWBlock& ResponseBlock)
|
|
|
|
{
|
|
|
|
FPortalStatusData PortalStatusData;
|
|
|
|
|
|
|
|
// Parse the figure status array (little-endian 32-bit integer)
|
|
|
|
uint32 FigureStatusArray = 0;
|
|
|
|
// Reading the 32-bit integer (character status array) from the buffer
|
|
|
|
FigureStatusArray |= ResponseBlock.buf[1]; // 1st byte
|
|
|
|
FigureStatusArray |= (ResponseBlock.buf[2] << 8); // 2nd byte
|
|
|
|
FigureStatusArray |= (ResponseBlock.buf[3] << 16); // 3rd byte
|
|
|
|
FigureStatusArray |= (ResponseBlock.buf[4] << 24); // 4th byte
|
|
|
|
|
2024-09-24 16:24:05 +00:00
|
|
|
TStaticArray<EFigureStatus, 16> tempArray;
|
2024-09-23 16:06:55 +00:00
|
|
|
// For each of the 16 entries, extract the 2-bit status and map it to EFigureStatus
|
|
|
|
for (int32 i = 0; i < 16; ++i)
|
|
|
|
{
|
|
|
|
uint8 StatusBits = (FigureStatusArray >> (i * 2)) & 0b11; // Extract 2 bits
|
|
|
|
EFigureStatus FigureStatus;
|
|
|
|
|
|
|
|
switch (StatusBits)
|
|
|
|
{
|
|
|
|
case 0b00:
|
|
|
|
FigureStatus = EFigureStatus::NOT_PRESENT;
|
|
|
|
break;
|
|
|
|
case 0b01:
|
|
|
|
FigureStatus = EFigureStatus::PRESENT;
|
|
|
|
break;
|
|
|
|
case 0b11:
|
|
|
|
FigureStatus = EFigureStatus::ADDED;
|
|
|
|
break;
|
|
|
|
case 0b10:
|
|
|
|
FigureStatus = EFigureStatus::REMOVED;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
FigureStatus = EFigureStatus::NOT_PRESENT; // Default case
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add to the array of figure statuses
|
2024-09-24 16:24:05 +00:00
|
|
|
//PortalStatusData.StatusArray.Insert(FigureStatus, i);
|
|
|
|
tempArray[i] = FigureStatus;
|
2024-09-23 16:06:55 +00:00
|
|
|
}
|
2024-09-24 16:24:05 +00:00
|
|
|
PortalStatusData.StatusArray.SetNum(0);
|
|
|
|
PortalStatusData.StatusArray.Append(tempArray);
|
2024-09-23 16:06:55 +00:00
|
|
|
// The next byte is the response counter
|
|
|
|
PortalStatusData.Counter = ResponseBlock.buf[5];
|
|
|
|
|
|
|
|
// The last byte is the boolean indicating whether the portal is ready
|
|
|
|
PortalStatusData.bIsReady = ResponseBlock.buf[6] != 0; // 0 means not ready, non-zero means ready
|
|
|
|
|
|
|
|
return PortalStatusData;
|
|
|
|
}
|
|
|
|
|
2024-09-20 12:29:13 +00:00
|
|
|
void USkyPortalSubsystem::Sleep(int sleepMs) {
|
|
|
|
FPlatformProcess::Sleep(sleepMs * 0.0001);
|
2024-09-19 12:03:16 +00:00
|
|
|
}
|
|
|
|
|
2024-09-23 13:48:29 +00:00
|
|
|
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;
|
|
|
|
}
|
2024-09-23 16:06:55 +00:00
|
|
|
EPortalCommand CommandResponse = GetPortalCommandFromChar(res.buf[0]);
|
2024-09-23 13:48:29 +00:00
|
|
|
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:
|
2024-09-23 16:06:55 +00:00
|
|
|
CurrentStatusData = ParsePortalStatus(res);
|
2024-09-23 21:55:43 +00:00
|
|
|
|
2024-09-23 16:06:55 +00:00
|
|
|
//Send delegate when new informations are received
|
2024-09-23 21:55:43 +00:00
|
|
|
if (OldStatusData != CurrentStatusData) {
|
2024-09-24 16:24:05 +00:00
|
|
|
|
2024-09-23 21:55:43 +00:00
|
|
|
for (int i = 0; i < CurrentStatusData.StatusArray.Num(); i++) {
|
|
|
|
if (CurrentStatusData.StatusArray[i] != OldStatusData.StatusArray[i]) {
|
2024-09-24 16:24:05 +00:00
|
|
|
if (
|
|
|
|
//!FalsePositive() //filter conflicting infos
|
|
|
|
true) {
|
|
|
|
FigureDataBlock FigureData;
|
2024-09-23 21:55:43 +00:00
|
|
|
switch (CurrentStatusData.StatusArray[i])
|
|
|
|
{
|
2024-09-24 16:24:05 +00:00
|
|
|
case EFigureStatus::NOT_PRESENT:
|
|
|
|
OnSkylanderRemoved.Broadcast(00, i);
|
|
|
|
break;
|
|
|
|
case EFigureStatus::PRESENT:
|
|
|
|
FigureData = ReadFigureBlocks(i);
|
|
|
|
OnSkylanderAdded.Broadcast(GetFigureID(FigureData), i);
|
|
|
|
break;
|
|
|
|
case EFigureStatus::ADDED:
|
|
|
|
FigureData = ReadFigureBlocks(i);
|
|
|
|
OnSkylanderAdded.Broadcast(GetFigureID(FigureData), i);
|
|
|
|
case EFigureStatus::REMOVED:
|
|
|
|
OnSkylanderRemoved.Broadcast(00, i);
|
2024-09-23 21:55:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
OldStatusData = CurrentStatusData;
|
|
|
|
}
|
2024-09-24 16:24:05 +00:00
|
|
|
|
2024-09-23 21:55:43 +00:00
|
|
|
|
2024-09-23 13:48:29 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-09-24 16:24:05 +00:00
|
|
|
TArray<uint8> USkyPortalSubsystem::QueryBlock(uint8 characterIndex, uint8 block)
|
|
|
|
{
|
|
|
|
TArray<uint8> QueryCommand;
|
|
|
|
QueryCommand.SetNum(0x21);
|
|
|
|
QueryCommand[1] = 'Q';
|
|
|
|
QueryCommand[2] = characterIndex;
|
|
|
|
QueryCommand[3] = block;
|
|
|
|
|
|
|
|
TArray<uint8> Output;
|
|
|
|
|
|
|
|
do {
|
|
|
|
portalConnection->Write(QueryCommand);
|
|
|
|
output = portalConnection->Read();
|
|
|
|
} while (output[0] != 'Q' || (output[1] % 0x10 != characterIndex && output[1] != 0x01) || output[2] != block);
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool USkyPortalSubsystem::FalsePositive() const
|
2024-09-23 21:55:43 +00:00
|
|
|
{
|
|
|
|
int dif = FMath::Abs(CurrentStatusData.Counter - OldStatusData.Counter);
|
2024-09-24 16:24:05 +00:00
|
|
|
return (dif <= 2 || dif >= 254);
|
2024-09-23 21:55:43 +00:00
|
|
|
}
|
|
|
|
|
2024-09-23 13:48:29 +00:00
|
|
|
|
|
|
|
|
2024-09-22 21:58:01 +00:00
|
|
|
/* 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
|
|
|
|
*/
|
|
|
|
bool USkyPortalSubsystem::CheckResponse(RWBlock* res, char expect)
|
2024-09-20 12:29:13 +00:00
|
|
|
{
|
|
|
|
if (!PortalDevice) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int b = hid_read_timeout(PortalDevice, res->buf, rw_buf_size, TIMEOUT);
|
|
|
|
|
|
|
|
if (b < 0) {
|
2024-09-23 13:48:29 +00:00
|
|
|
UE_LOG(LogSkyportalIO, Error, TEXT("Error.\n %s"), hid_error(PortalDevice));
|
2024-09-20 12:29:13 +00:00
|
|
|
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"));
|
|
|
|
}
|
|
|
|
|
2024-09-21 22:01:15 +00:00
|
|
|
if (res->buf[0] == 'R' && res->buf[1] == 0) {
|
|
|
|
UE_LOG(LogSkyportalIO, Warning, TEXT("Status says no skylander on portal"));
|
|
|
|
}
|
|
|
|
|
2024-09-20 12:29:13 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-09-23 13:48:29 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-09-23 21:55:43 +00:00
|
|
|
bool USkyPortalSubsystem::ReadBlock(unsigned int block, unsigned char data[0x10], int skylanderIndex) {
|
2024-09-20 12:29:13 +00:00
|
|
|
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';
|
2024-09-23 21:55:43 +00:00
|
|
|
followup = 0x10 + skylanderIndex;
|
2024-09-20 12:29:13 +00:00
|
|
|
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;
|
2024-09-20 12:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool USkyPortalSubsystem::ConnectPortal()
|
|
|
|
{
|
|
|
|
|
|
|
|
bPortalConnected = OpenPortalHandle();
|
|
|
|
if (bPortalConnected) {
|
|
|
|
RestartPortal();
|
|
|
|
ActivatePortal(1);
|
2024-09-21 15:12:02 +00:00
|
|
|
|
|
|
|
Sleep(500);
|
2024-09-23 16:06:55 +00:00
|
|
|
ChangePortalColor(FLinearColor(0.5, 0.5, 0.5));
|
2024-09-20 12:29:13 +00:00
|
|
|
|
2024-09-23 13:48:29 +00:00
|
|
|
UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: "));
|
2024-09-20 12:29:13 +00:00
|
|
|
}
|
|
|
|
return bPortalConnected;
|
|
|
|
}
|
|
|
|
|
2024-09-22 21:58:01 +00:00
|
|
|
bool USkyPortalSubsystem::bIsPortalReady()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void USkyPortalSubsystem::SendPortalCommand(EPortalCommand Command)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void USkyPortalSubsystem::SendPortalSound(USoundWave* Sound)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-09-20 12:29:13 +00:00
|
|
|
|
|
|
|
|
2024-09-23 13:48:29 +00:00
|
|
|
FPortalStatusChecker::FPortalStatusChecker(USkyPortalSubsystem* InSubsystem, float InCheckInterval)
|
|
|
|
: SkyPortalSubsystem(InSubsystem), CheckInterval(InCheckInterval), bShouldRun(true)
|
2024-09-20 12:29:13 +00:00
|
|
|
{
|
2024-09-23 13:48:29 +00:00
|
|
|
}
|
2024-09-20 12:29:13 +00:00
|
|
|
|
2024-09-23 13:48:29 +00:00
|
|
|
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:"));
|
|
|
|
}
|
|
|
|
}
|
2024-09-20 12:29:13 +00:00
|
|
|
|
2024-09-23 21:55:43 +00:00
|
|
|
FigureDataBlock USkyPortalSubsystem::ReadFigureBlocks(uint8 FigureIndex)
|
|
|
|
{
|
|
|
|
FigureDataBlock FigureData;
|
|
|
|
FigureData.error = false; // Initialize error flag
|
|
|
|
|
|
|
|
RWBlock req, res;
|
|
|
|
|
|
|
|
// Loop over all 64 blocks
|
|
|
|
for (uint8 BlockIndex = 0; BlockIndex < FIGURE_TOTAL_BLOCKS; ++BlockIndex)
|
|
|
|
{
|
|
|
|
// Prepare the request buffer
|
|
|
|
memset(req.buf, 0, rw_buf_size);
|
|
|
|
req.buf[1] = 'Q'; // Command character
|
|
|
|
req.buf[2] = FigureIndex; // Figure index (0x00 to 0x0F)
|
|
|
|
req.buf[3] = BlockIndex; // Block index (0x00 to 0x3F)
|
|
|
|
|
|
|
|
// Send the request
|
|
|
|
Write(&req);
|
|
|
|
|
|
|
|
// Read the response
|
|
|
|
int BuffResponse = hid_read_timeout(PortalDevice, res.buf, rw_buf_size, TIMEOUT);
|
|
|
|
|
|
|
|
// Check if the response was received successfully
|
|
|
|
if (BuffResponse < rw_buf_size)
|
|
|
|
{
|
|
|
|
FigureData.error = true; // Mark error flag
|
|
|
|
UE_LOG(LogSkyportalIO, Warning, TEXT("Error receiving data for block %d of figure %d"), BlockIndex, FigureIndex);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the response status
|
|
|
|
uint8 StatusByte = res.buf[2];
|
|
|
|
uint8 ReturnedBlockIndex = res.buf[3];
|
|
|
|
|
|
|
|
// Check for errors in the status byte
|
|
|
|
if (StatusByte == 0x01) // Error in response
|
|
|
|
{
|
|
|
|
FigureData.error = true;
|
|
|
|
UE_LOG(LogSkyportalIO, Warning, TEXT("Error reading block %d for figure %d"), BlockIndex, FigureIndex);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (StatusByte != (0x10 + 'Q')) // Unexpected status byte
|
|
|
|
{
|
|
|
|
FigureData.error = true;
|
|
|
|
UE_LOG(LogSkyportalIO, Warning, TEXT("Unexpected status byte 0x%02X for block %d"), StatusByte, BlockIndex);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify that the block index matches the requested block
|
|
|
|
if (ReturnedBlockIndex != BlockIndex)
|
|
|
|
{
|
|
|
|
FigureData.error = true;
|
|
|
|
UE_LOG(LogSkyportalIO, Warning, TEXT("Mismatched block index. Expected %d, got %d"), BlockIndex, ReturnedBlockIndex);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy block data into FigureData
|
|
|
|
memcpy(FigureData.blockdata[BlockIndex], &res.buf[4], FIGURE_BLOCK_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
return FigureData; // Return the complete figure data
|
|
|
|
}
|