// 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" #include "SkyPortal.h" #include "HAL/RunnableThread.h" #include "Misc/ScopeLock.h" void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); // Start the status checker thread StatusChecker = new FPortalStatusChecker(this, RunnerInterval); 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 (PortalHandle) { PortalHandle->Close(); } hid_exit(); UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Deinitialized")); Super::Deinitialize(); } void USkyPortalSubsystem::PortalActivate(const bool bShouldActivate) { FWriteBlock command; memset(command.data, 0, write_buf_size); command.data[1] = 'A'; command.data[2] = bShouldActivate; uint8* output; do { PortalHandle->Write(&command); output = PortalHandle->Read(); } while (output[0] != 'A'); } void USkyPortalSubsystem::PortalReady() { FWriteBlock command; memset(command.data, 0, write_buf_size); //maybe not needed here command.data[1] = 'R'; unsigned char* output; do { PortalHandle->Write(&command); output = PortalHandle->Read(); } while (output[0] != 'R'); unsigned char _PortalId[2] = { output[1],output[2] }; uint16_t BE_PortalId = (_PortalId[0] << 8) | _PortalId[1]; // Big-endian format uint32 BEU_PortalId = (_PortalId[0] << 8) | _PortalId[1]; // Big-endian format unsigned uint16_t LE_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian //uint32 LEU_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian unsigned PortalId = BEU_PortalId; //Need a conversion as uint16 is not supported in BP } FPortalStatusData USkyPortalSubsystem::PortalStatus() { FWriteBlock command; memset(command.data, 0, write_buf_size); //maybe not needed here command.data[1] = 'S'; uint8* output; do { PortalHandle->Write(&command); output = PortalHandle->Read(); } while (output[0] != 'S'); return ParsePortalStatus(output); } SKYPORTAL_API bool USkyPortalSubsystem::RestartRunner() { if (StatusChecker) { StatusChecker->Stop(); StatusCheckerThread->WaitForCompletion(); delete StatusCheckerThread; delete StatusChecker; StatusChecker = nullptr; StatusCheckerThread = nullptr; } StatusChecker = new FPortalStatusChecker(this, RunnerInterval); // Check every 10 milliseconds StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal); UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized")); } #pragma region Color functions void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color) { 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); FWriteBlock command; memset(command.data, 0, write_buf_size); command.data[1] = 'C'; command.data[2] = r; // R command.data[3] = g; // G command.data[4] = b; // B // no response for this one. PortalHandle->Write(&command); } void USkyPortalSubsystem::ChangePortalColorSide(const FLinearColor& Color, const EPortalSide PortalSide, const float BlendTime) { 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; FWriteBlock command; memset(command.data, 0, write_buf_size); if (PortalSide == EPortalSide::BOTH) { _portalside = EPortalSide::LEFT; } else { _portalside = PortalSide; } switch (_portalside) { case EPortalSide::LEFT: command.data[1] = 'J'; command.data[2] = 0x00; case EPortalSide::RIGHT: command.data[1] = 'J'; command.data[2] = 0x02; case EPortalSide::TRAP: command.data[1] = 'L'; command.data[2] = 0x01; command.data[3] = FMath::Max3(r, g, b); // calculate brightness PortalHandle->Write(&command); //since it's a color command for trap, only 3 bytes are needed, no response. return; } command.data[3] = r; // R command.data[4] = g; // G command.data[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 command.data[6] = _time_low; command.data[7] = _time_high; uint8* output; do { PortalHandle->Write(&command); output = PortalHandle->Read(); } while (output[0] != 'J'); if (PortalSide == EPortalSide::BOTH) { ChangePortalColorSide(Color, EPortalSide::RIGHT, BlendTime); // send a second command for the right side } } #pragma endregion void USkyPortalSubsystem::Sleep(int sleepMs) { FPlatformProcess::Sleep(sleepMs * 0.0001); } bool USkyPortalSubsystem::PortalConnect() { PortalHandle = NewObject(); if (IsValid(PortalHandle)) { UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: ")); PortalReady(); PortalActivate(1); Sleep(500); ChangePortalColor(FLinearColor(0.5, 0.5, 0.5)); return true; } return false; } bool USkyPortalSubsystem::IsPortalReady() { if (PortalHandle) { return PortalHandle->bPortalReady; } //maybe should send a status request ? return false; } SKYPORTAL_API void USkyPortalSubsystem::PortalMusic(const USoundWave* Sound) { return; }