diff --git a/Unreal/PS_BLE_Tracker.uproject b/Unreal/PS_BLE_Tracker.uproject index b23b0d4..d680761 100644 --- a/Unreal/PS_BLE_Tracker.uproject +++ b/Unreal/PS_BLE_Tracker.uproject @@ -17,6 +17,10 @@ "TargetAllowList": [ "Editor" ] + }, + { + "Name": "PS_Win_BLE", + "Enabled": true } ] } \ No newline at end of file diff --git a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/PS_Win_BLE.Build.cs b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/PS_Win_BLE.Build.cs index 4be0dbb..a140456 100644 --- a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/PS_Win_BLE.Build.cs +++ b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/PS_Win_BLE.Build.cs @@ -49,6 +49,13 @@ public class PS_Win_BLE : ModuleRules "runtimeobject.lib" // RoInitialize / WinRT activation }); + // C++/WinRT headers (winrt/Windows.Devices.Bluetooth.h, etc.) + // Located in the Windows SDK — we pick the version used by UE5.5 + string WinSDKDir = "C:/Program Files (x86)/Windows Kits/10"; + string WinSDKVersion = "10.0.22621.0"; + PrivateIncludePaths.Add(Path.Combine(WinSDKDir, "Include", WinSDKVersion, "cppwinrt")); + PrivateIncludePaths.Add(Path.Combine(WinSDKDir, "Include", WinSDKVersion, "winrt")); + // Allow WinRT headers in C++ code bEnableExceptions = true; } diff --git a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Private/PS_BLEDevice.cpp b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Private/PS_BLEDevice.cpp index 2c8b9aa..3e872f0 100644 --- a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Private/PS_BLEDevice.cpp +++ b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Private/PS_BLEDevice.cpp @@ -4,18 +4,6 @@ #include "PS_BLEModule.h" #include "PS_BLEManager.h" -#if PLATFORM_WINDOWS -#include "Windows/AllowWindowsPlatformTypes.h" -#include "Windows/AllowWindowsPlatformAtomics.h" -#pragma warning(push) -#pragma warning(disable: 4668 4946 5204 5220) -#include -#pragma warning(pop) -#include "Windows/HideWindowsPlatformAtomics.h" -#include "Windows/HideWindowsPlatformTypes.h" -using namespace winrt; -#endif - // ───────────────────────────────────────────────────────────────────────────── UPS_BLE_Device::UPS_BLE_Device() diff --git a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Private/PS_BLEModule.cpp b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Private/PS_BLEModule.cpp index 18fa643..c5bab2b 100644 --- a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Private/PS_BLEModule.cpp +++ b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Private/PS_BLEModule.cpp @@ -8,15 +8,19 @@ #include "Modules/ModuleManager.h" // ─── WinRT includes (Windows only) ─────────────────────────────────────────── +// NOTE: AllowWindowsPlatformTypes must wrap the WinRT headers. +// We do NOT place any "using namespace" at file scope because Unreal also +// defines a ::Windows namespace (via AllowWindowsPlatformTypes) and that would +// cause "ambiguous symbol" errors. All WinRT types are fully qualified with +// winrt:: inside each function body. + #if PLATFORM_WINDOWS -// Unreal defines WIN32_LEAN_AND_MEAN which can strip some COM headers — we -// add the specific ones we need without touching the Unreal macros. #include "Windows/AllowWindowsPlatformTypes.h" #include "Windows/AllowWindowsPlatformAtomics.h" #pragma warning(push) -#pragma warning(disable: 4668 4946 5204 5220) +#pragma warning(disable: 4265 4668 4946 5204 5220) // 4265: WinRT internal non-virtual dtor (harmless) #include #include @@ -30,20 +34,36 @@ #include "Windows/HideWindowsPlatformAtomics.h" #include "Windows/HideWindowsPlatformTypes.h" -using namespace winrt; -using namespace Windows::Devices::Bluetooth; -using namespace Windows::Devices::Bluetooth::Advertisement; -using namespace Windows::Devices::Bluetooth::GenericAttributeProfile; -using namespace Windows::Foundation; -using namespace Windows::Foundation::Collections; -using namespace Windows::Storage::Streams; +// ─── Convenient namespace aliases used ONLY inside function bodies ──────────── +// (Defined as macros so we can paste them at the top of each #if PLATFORM_WINDOWS block) +#define PS_BLE_WINRT_NS \ + namespace WinBT = winrt::Windows::Devices::Bluetooth; \ + namespace WinAdv = winrt::Windows::Devices::Bluetooth::Advertisement; \ + namespace WinGAP = winrt::Windows::Devices::Bluetooth::GenericAttributeProfile; \ + namespace WinFnd = winrt::Windows::Foundation; \ + namespace WinStr = winrt::Windows::Storage::Streams; -// ─── Scanner state (allocated on heap so WinRT types don't leak into header) ─ +// ─── Scanner state (heap-allocated so WinRT types don't appear in the header) ─ struct FPS_ScannerState { - BluetoothLEAdvertisementWatcher Watcher{ nullptr }; - winrt::event_token ReceivedToken; - winrt::event_token StoppedToken; + winrt::Windows::Devices::Bluetooth::Advertisement::BluetoothLEAdvertisementWatcher Watcher{ nullptr }; + winrt::event_token ReceivedToken; + winrt::event_token StoppedToken; +}; + +// ─── WinRT object wrappers ──────────────────────────────────────────────────── +// WinRT types delete operator new — we wrap them in plain structs so they can +// live on the heap via regular new/delete (stored as void* in UObject headers). + +struct FPS_BLEDeviceHandle +{ + winrt::Windows::Devices::Bluetooth::BluetoothLEDevice Device{ nullptr }; + winrt::event_token ConnectionStatusToken; +}; + +struct FPS_GattServiceHandle +{ + winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::GattDeviceService Service{ nullptr }; }; #endif // PLATFORM_WINDOWS @@ -112,12 +132,14 @@ UPS_BLE_Manager* UPS_BLE_Module::GetBLEManager(const UPS_BLE_Module* Mod) void UPS_BLE_Module::ScannerCleanup() { #if PLATFORM_WINDOWS + PS_BLE_WINRT_NS if (ScannerHandle) { FPS_ScannerState* State = static_cast(ScannerHandle); try { - if (State->Watcher && State->Watcher.Status() == BluetoothLEAdvertisementWatcherStatus::Started) + if (State->Watcher && + State->Watcher.Status() == WinAdv::BluetoothLEAdvertisementWatcherStatus::Started) { State->Watcher.Stop(); } @@ -132,24 +154,24 @@ void UPS_BLE_Module::ScannerCleanup() bool UPS_BLE_Module::StartDiscoveryLive(UPS_BLE_Manager* Ref, int32 DurationMs, const FString& Filter) { #if PLATFORM_WINDOWS + PS_BLE_WINRT_NS if (!Ref) return false; ScannerCleanup(); FPS_ScannerState* State = new FPS_ScannerState(); - State->Watcher = BluetoothLEAdvertisementWatcher(); - State->Watcher.ScanningMode(BluetoothLEScanningMode::Active); + State->Watcher = WinAdv::BluetoothLEAdvertisementWatcher(); + State->Watcher.ScanningMode(WinAdv::BluetoothLEScanningMode::Active); ScannerHandle = State; - // Capture filter and manager ref FString FilterCopy = Filter; UPS_BLE_Manager* MgrRef = Ref; State->ReceivedToken = State->Watcher.Received( - [MgrRef, FilterCopy](BluetoothLEAdvertisementWatcher const&, BluetoothLEAdvertisementReceivedEventArgs const& Args) + [MgrRef, FilterCopy](WinAdv::BluetoothLEAdvertisementWatcher const&, + WinAdv::BluetoothLEAdvertisementReceivedEventArgs const& Args) { FString Name = Args.Advertisement().LocalName().c_str(); - // Apply name filter (comma-separated substrings) if (!FilterCopy.IsEmpty()) { TArray Parts; @@ -157,11 +179,7 @@ bool UPS_BLE_Module::StartDiscoveryLive(UPS_BLE_Manager* Ref, int32 DurationMs, bool bMatch = false; for (const FString& Part : Parts) { - if (Name.Contains(Part.TrimStartAndEnd())) - { - bMatch = true; - break; - } + if (Name.Contains(Part.TrimStartAndEnd())) { bMatch = true; break; } } if (!bMatch) return; } @@ -170,12 +188,12 @@ bool UPS_BLE_Module::StartDiscoveryLive(UPS_BLE_Manager* Ref, int32 DurationMs, Rec.ID = Args.BluetoothAddress(); Rec.RSSI = Args.RawSignalStrengthInDBm(); Rec.Name = Name; - DispatchDeviceDiscovered(MgrRef, Rec); }); State->StoppedToken = State->Watcher.Stopped( - [MgrRef](BluetoothLEAdvertisementWatcher const&, BluetoothLEAdvertisementWatcherStoppedEventArgs const&) + [MgrRef](WinAdv::BluetoothLEAdvertisementWatcher const&, + WinAdv::BluetoothLEAdvertisementWatcherStoppedEventArgs const&) { TArray Empty; DispatchDiscoveryEnd(MgrRef, Empty); @@ -183,7 +201,6 @@ bool UPS_BLE_Module::StartDiscoveryLive(UPS_BLE_Manager* Ref, int32 DurationMs, State->Watcher.Start(); - // Auto-stop after DurationMs if (DurationMs > 0) { int32 Ms = DurationMs; @@ -203,28 +220,28 @@ bool UPS_BLE_Module::StartDiscoveryLive(UPS_BLE_Manager* Ref, int32 DurationMs, bool UPS_BLE_Module::StartDiscoveryInBackground(UPS_BLE_Manager* Ref, int32 DurationMs, const FString& Filter) { #if PLATFORM_WINDOWS + PS_BLE_WINRT_NS if (!Ref) return false; ScannerCleanup(); - // Collect all devices during scan, fire DiscoveryEnd at the end TSharedPtr, ESPMode::ThreadSafe> Collected = MakeShared, ESPMode::ThreadSafe>(); FPS_ScannerState* State = new FPS_ScannerState(); - State->Watcher = BluetoothLEAdvertisementWatcher(); - State->Watcher.ScanningMode(BluetoothLEScanningMode::Active); + State->Watcher = WinAdv::BluetoothLEAdvertisementWatcher(); + State->Watcher.ScanningMode(WinAdv::BluetoothLEScanningMode::Active); ScannerHandle = State; FString FilterCopy = Filter; UPS_BLE_Manager* MgrRef = Ref; State->ReceivedToken = State->Watcher.Received( - [MgrRef, FilterCopy, Collected](BluetoothLEAdvertisementWatcher const&, BluetoothLEAdvertisementReceivedEventArgs const& Args) + [MgrRef, FilterCopy, Collected](WinAdv::BluetoothLEAdvertisementWatcher const&, + WinAdv::BluetoothLEAdvertisementReceivedEventArgs const& Args) { FString Name = Args.Advertisement().LocalName().c_str(); + uint64 Addr = Args.BluetoothAddress(); - // Dedup by address - uint64 Addr = Args.BluetoothAddress(); for (const FPS_DeviceRecord& R : *Collected) { if (R.ID == Addr) return; @@ -250,7 +267,8 @@ bool UPS_BLE_Module::StartDiscoveryInBackground(UPS_BLE_Manager* Ref, int32 Dura }); State->StoppedToken = State->Watcher.Stopped( - [MgrRef, Collected](BluetoothLEAdvertisementWatcher const&, BluetoothLEAdvertisementWatcherStoppedEventArgs const&) + [MgrRef, Collected](WinAdv::BluetoothLEAdvertisementWatcher const&, + WinAdv::BluetoothLEAdvertisementWatcherStoppedEventArgs const&) { DispatchDiscoveryEnd(MgrRef, *Collected); }); @@ -276,12 +294,14 @@ bool UPS_BLE_Module::StartDiscoveryInBackground(UPS_BLE_Manager* Ref, int32 Dura bool UPS_BLE_Module::StopDiscovery() { #if PLATFORM_WINDOWS + PS_BLE_WINRT_NS if (ScannerHandle) { FPS_ScannerState* State = static_cast(ScannerHandle); try { - if (State->Watcher && State->Watcher.Status() == BluetoothLEAdvertisementWatcherStatus::Started) + if (State->Watcher && + State->Watcher.Status() == WinAdv::BluetoothLEAdvertisementWatcherStatus::Started) { State->Watcher.Stop(); } @@ -303,39 +323,33 @@ bool UPS_BLE_Module::ConnectDevice(UPS_BLE_Device* Device) if (!Device) return false; uint64 Addr = Device->DeviceID; - UPS_BLE_Module* ModRef = this; - // WinRT async connect + service discovery on background thread - AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [ModRef, Device, Addr]() + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [Device, Addr]() { + PS_BLE_WINRT_NS try { - // FromBluetoothAddressAsync is the standard WinRT connect path - auto Op = BluetoothLEDevice::FromBluetoothAddressAsync(Addr); - BluetoothLEDevice BLEDev = Op.get(); - + auto BLEDev = WinBT::BluetoothLEDevice::FromBluetoothAddressAsync(Addr).get(); if (!BLEDev) { DispatchDeviceDisconnected(Device); return; } - // Store native handle (AddRef via IUnknown kept alive by winrt wrapper) - Device->NativeDeviceHandle = new winrt::Windows::Devices::Bluetooth::BluetoothLEDevice(BLEDev); - - // Subscribe to connection-status change - BLEDev.ConnectionStatusChanged( - [Device](BluetoothLEDevice const& Dev, IInspectable const&) + FPS_BLEDeviceHandle* Handle = new FPS_BLEDeviceHandle(); + Handle->Device = BLEDev; + Handle->ConnectionStatusToken = BLEDev.ConnectionStatusChanged( + [Device](WinBT::BluetoothLEDevice const& Dev, winrt::Windows::Foundation::IInspectable const&) { - if (Dev.ConnectionStatus() == BluetoothConnectionStatus::Disconnected) + if (Dev.ConnectionStatus() == WinBT::BluetoothConnectionStatus::Disconnected) { DispatchDeviceDisconnected(Device); } }); + Device->NativeDeviceHandle = Handle; - // Discover GATT services - auto SvcResult = BLEDev.GetGattServicesAsync(BluetoothCacheMode::Uncached).get(); - if (SvcResult.Status() != GattCommunicationStatus::Success) + auto SvcResult = BLEDev.GetGattServicesAsync(WinBT::BluetoothCacheMode::Uncached).get(); + if (SvcResult.Status() != WinGAP::GattCommunicationStatus::Success) { DispatchDeviceDisconnected(Device); return; @@ -348,16 +362,17 @@ bool UPS_BLE_Module::ConnectDevice(UPS_BLE_Device* Device) for (uint32_t si = 0; si < Services.Size(); si++) { auto Svc = Services.GetAt(si); - Device->NativeGattServices.Add(new GattDeviceService(Svc)); + FPS_GattServiceHandle* SvcHandle = new FPS_GattServiceHandle(); + SvcHandle->Service = Svc; + Device->NativeGattServices.Add(SvcHandle); FPS_ServiceItem SvcItem; GUID g = Svc.Uuid(); SvcItem.ServiceUUID = UPS_BLE_Device::GUIDToString(&g); - SvcItem.ServiceName = SvcItem.ServiceUUID; // WinRT doesn't give a friendly name + SvcItem.ServiceName = SvcItem.ServiceUUID; - // Discover characteristics - auto CharResult = Svc.GetCharacteristicsAsync(BluetoothCacheMode::Uncached).get(); - if (CharResult.Status() == GattCommunicationStatus::Success) + auto CharResult = SvcHandle->Service.GetCharacteristicsAsync(WinBT::BluetoothCacheMode::Uncached).get(); + if (CharResult.Status() == WinGAP::GattCommunicationStatus::Success) { auto Chars = CharResult.Characteristics(); for (uint32_t ci = 0; ci < Chars.Size(); ci++) @@ -368,17 +383,17 @@ bool UPS_BLE_Module::ConnectDevice(UPS_BLE_Device* Device) ChItem.CharacteristicUUID = UPS_BLE_Device::GUIDToString(&cg); ChItem.CharacteristicName = ChItem.CharacteristicUUID; - // Map WinRT properties to our descriptor bits auto Props = Ch.CharacteristicProperties(); uint8 Desc = 0; - if ((Props & GattCharacteristicProperties::Broadcast) != GattCharacteristicProperties::None) Desc |= 0x01; - if ((Props & GattCharacteristicProperties::ExtendedProperties) != GattCharacteristicProperties::None) Desc |= 0x02; - if ((Props & GattCharacteristicProperties::Notify) != GattCharacteristicProperties::None) Desc |= 0x04; - if ((Props & GattCharacteristicProperties::Indicate) != GattCharacteristicProperties::None) Desc |= 0x08; - if ((Props & GattCharacteristicProperties::Read) != GattCharacteristicProperties::None) Desc |= 0x10; - if ((Props & GattCharacteristicProperties::Write) != GattCharacteristicProperties::None) Desc |= 0x20; - if ((Props & GattCharacteristicProperties::WriteWithoutResponse)!= GattCharacteristicProperties::None) Desc |= 0x40; - if ((Props & GattCharacteristicProperties::AuthenticatedSignedWrites)!= GattCharacteristicProperties::None) Desc |= 0x80; + using GP = WinGAP::GattCharacteristicProperties; + if ((Props & GP::Broadcast) != GP::None) Desc |= 0x01; + if ((Props & GP::ExtendedProperties) != GP::None) Desc |= 0x02; + if ((Props & GP::Notify) != GP::None) Desc |= 0x04; + if ((Props & GP::Indicate) != GP::None) Desc |= 0x08; + if ((Props & GP::Read) != GP::None) Desc |= 0x10; + if ((Props & GP::Write) != GP::None) Desc |= 0x20; + if ((Props & GP::WriteWithoutResponse) != GP::None) Desc |= 0x40; + if ((Props & GP::AuthenticatedSignedWrites) != GP::None) Desc |= 0x80; ChItem.Descriptor = Desc; SvcItem.Characteristics.Add(ChItem); @@ -405,22 +420,18 @@ bool UPS_BLE_Module::ConnectDevice(UPS_BLE_Device* Device) bool UPS_BLE_Module::DisconnectDevice(UPS_BLE_Device* Device) { #if PLATFORM_WINDOWS + PS_BLE_WINRT_NS if (!Device) return false; - // Close native GATT service handles for (void* SvcPtr : Device->NativeGattServices) { - if (SvcPtr) - { - delete static_cast(SvcPtr); - } + if (SvcPtr) delete static_cast(SvcPtr); } Device->NativeGattServices.Empty(); - // Close device handle if (Device->NativeDeviceHandle) { - delete static_cast(Device->NativeDeviceHandle); + delete static_cast(Device->NativeDeviceHandle); Device->NativeDeviceHandle = nullptr; } return true; @@ -432,11 +443,12 @@ bool UPS_BLE_Module::DisconnectDevice(UPS_BLE_Device* Device) bool UPS_BLE_Module::IsDeviceConnected(UPS_BLE_Device* Device) { #if PLATFORM_WINDOWS + PS_BLE_WINRT_NS if (!Device || !Device->NativeDeviceHandle) return false; try { - BluetoothLEDevice* BLEDev = static_cast(Device->NativeDeviceHandle); - return BLEDev->ConnectionStatus() == BluetoothConnectionStatus::Connected; + auto* Handle = static_cast(Device->NativeDeviceHandle); + return Handle->Device.ConnectionStatus() == WinBT::BluetoothConnectionStatus::Connected; } catch (...) {} #endif @@ -450,26 +462,27 @@ bool UPS_BLE_Module::IsDeviceConnected(UPS_BLE_Device* Device) bool UPS_BLE_Module::ReadCharacteristic(UPS_BLE_Device* Device, uint8 SI, uint8 CI) { #if PLATFORM_WINDOWS - if (!Device || SI >= Device->NativeGattServices.Num()) return false; + if (!Device || SI >= (uint8)Device->NativeGattServices.Num()) return false; AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [Device, SI, CI]() { + PS_BLE_WINRT_NS try { - GattDeviceService* Svc = static_cast(Device->NativeGattServices[SI]); - auto Chars = Svc->GetCharacteristicsAsync(BluetoothCacheMode::Cached).get(); - if (Chars.Status() != GattCommunicationStatus::Success) return; + auto* SvcH = static_cast(Device->NativeGattServices[SI]); + auto Chars = SvcH->Service.GetCharacteristicsAsync(WinBT::BluetoothCacheMode::Cached).get(); + if (Chars.Status() != WinGAP::GattCommunicationStatus::Success) return; - auto Ch = Chars.Characteristics().GetAt(CI); - auto Result = Ch.ReadValueAsync(BluetoothCacheMode::Uncached).get(); + auto Ch = Chars.Characteristics().GetAt(CI); + auto Result = Ch.ReadValueAsync(WinBT::BluetoothCacheMode::Uncached).get(); - EPS_GATTStatus Status = (Result.Status() == GattCommunicationStatus::Success) + EPS_GATTStatus Status = (Result.Status() == WinGAP::GattCommunicationStatus::Success) ? EPS_GATTStatus::Success : EPS_GATTStatus::Failure; TArray Data; - if (Result.Status() == GattCommunicationStatus::Success) + if (Result.Status() == WinGAP::GattCommunicationStatus::Success) { - auto Reader = DataReader::FromBuffer(Result.Value()); + auto Reader = WinStr::DataReader::FromBuffer(Result.Value()); Data.SetNumUninitialized(Reader.UnconsumedBufferLength()); for (uint8& B : Data) B = Reader.ReadByte(); } @@ -487,26 +500,26 @@ bool UPS_BLE_Module::ReadCharacteristic(UPS_BLE_Device* Device, uint8 SI, uint8 bool UPS_BLE_Module::WriteCharacteristic(UPS_BLE_Device* Device, uint8 SI, uint8 CI, const TArray& Data) { #if PLATFORM_WINDOWS - if (!Device || SI >= Device->NativeGattServices.Num()) return false; + if (!Device || SI >= (uint8)Device->NativeGattServices.Num()) return false; TArray DataCopy = Data; AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [Device, SI, CI, DataCopy]() { + PS_BLE_WINRT_NS try { - GattDeviceService* Svc = static_cast(Device->NativeGattServices[SI]); - auto Chars = Svc->GetCharacteristicsAsync(BluetoothCacheMode::Cached).get(); - if (Chars.Status() != GattCommunicationStatus::Success) return; + auto* SvcH = static_cast(Device->NativeGattServices[SI]); + auto Chars = SvcH->Service.GetCharacteristicsAsync(WinBT::BluetoothCacheMode::Cached).get(); + if (Chars.Status() != WinGAP::GattCommunicationStatus::Success) return; - auto Ch = Chars.Characteristics().GetAt(CI); - - auto Writer = DataWriter(); + auto Ch = Chars.Characteristics().GetAt(CI); + auto Writer = WinStr::DataWriter(); for (uint8 B : DataCopy) Writer.WriteByte(B); - auto Status = Ch.WriteValueAsync(Writer.DetachBuffer(), - GattWriteOption::WriteWithResponse).get(); + auto WriteStatus = Ch.WriteValueAsync(Writer.DetachBuffer(), + WinGAP::GattWriteOption::WriteWithResponse).get(); - EPS_GATTStatus GattStatus = (Status == GattCommunicationStatus::Success) + EPS_GATTStatus GattStatus = (WriteStatus == WinGAP::GattCommunicationStatus::Success) ? EPS_GATTStatus::Success : EPS_GATTStatus::Failure; DispatchWrite(Device, SI, CI, GattStatus); @@ -522,39 +535,39 @@ bool UPS_BLE_Module::WriteCharacteristic(UPS_BLE_Device* Device, uint8 SI, uint8 bool UPS_BLE_Module::SubscribeCharacteristic(UPS_BLE_Device* Device, uint8 SI, uint8 CI) { #if PLATFORM_WINDOWS - if (!Device || SI >= Device->NativeGattServices.Num()) return false; + if (!Device || SI >= (uint8)Device->NativeGattServices.Num()) return false; AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [Device, SI, CI]() { + PS_BLE_WINRT_NS try { - GattDeviceService* Svc = static_cast(Device->NativeGattServices[SI]); - auto Chars = Svc->GetCharacteristicsAsync(BluetoothCacheMode::Cached).get(); - if (Chars.Status() != GattCommunicationStatus::Success) return; + auto* SvcH = static_cast(Device->NativeGattServices[SI]); + auto Chars = SvcH->Service.GetCharacteristicsAsync(WinBT::BluetoothCacheMode::Cached).get(); + if (Chars.Status() != WinGAP::GattCommunicationStatus::Success) return; auto Ch = Chars.Characteristics().GetAt(CI); - // Write CCCD to enable notifications auto WriteStatus = Ch.WriteClientCharacteristicConfigurationDescriptorAsync( - GattClientCharacteristicConfigurationDescriptorValue::Notify).get(); + WinGAP::GattClientCharacteristicConfigurationDescriptorValue::Notify).get(); - EPS_GATTStatus GattStatus = (WriteStatus == GattCommunicationStatus::Success) + EPS_GATTStatus GattStatus = (WriteStatus == WinGAP::GattCommunicationStatus::Success) ? EPS_GATTStatus::Success : EPS_GATTStatus::Failure; - if (WriteStatus == GattCommunicationStatus::Success) + if (WriteStatus == WinGAP::GattCommunicationStatus::Success) { - // Register value-changed callback auto Token = Ch.ValueChanged( - [Device, SI, CI](GattCharacteristic const&, GattValueChangedEventArgs const& Args) + [Device, SI, CI](WinGAP::GattCharacteristic const&, + WinGAP::GattValueChangedEventArgs const& Args) { - auto Reader = DataReader::FromBuffer(Args.CharacteristicValue()); + PS_BLE_WINRT_NS + auto Reader = WinStr::DataReader::FromBuffer(Args.CharacteristicValue()); TArray Data; Data.SetNumUninitialized(Reader.UnconsumedBufferLength()); for (uint8& B : Data) B = Reader.ReadByte(); DispatchNotify(Device, SI, CI, EPS_GATTStatus::Success, MoveTemp(Data)); }); - // Store token (key = packed SI<<8|CI) uint64 Key = ((uint64)SI << 8) | CI; Device->NotifyTokens.Add(Key, new winrt::event_token(Token)); } @@ -572,19 +585,19 @@ bool UPS_BLE_Module::SubscribeCharacteristic(UPS_BLE_Device* Device, uint8 SI, u bool UPS_BLE_Module::UnsubscribeCharacteristic(UPS_BLE_Device* Device, uint8 SI, uint8 CI) { #if PLATFORM_WINDOWS - if (!Device || SI >= Device->NativeGattServices.Num()) return false; + if (!Device || SI >= (uint8)Device->NativeGattServices.Num()) return false; AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [Device, SI, CI]() { + PS_BLE_WINRT_NS try { - GattDeviceService* Svc = static_cast(Device->NativeGattServices[SI]); - auto Chars = Svc->GetCharacteristicsAsync(BluetoothCacheMode::Cached).get(); - if (Chars.Status() != GattCommunicationStatus::Success) return; + auto* SvcH = static_cast(Device->NativeGattServices[SI]); + auto Chars = SvcH->Service.GetCharacteristicsAsync(WinBT::BluetoothCacheMode::Cached).get(); + if (Chars.Status() != WinGAP::GattCommunicationStatus::Success) return; auto Ch = Chars.Characteristics().GetAt(CI); - // Remove event token uint64 Key = ((uint64)SI << 8) | CI; if (winrt::event_token** TokenPtr = reinterpret_cast(Device->NotifyTokens.Find(Key))) { @@ -593,11 +606,10 @@ bool UPS_BLE_Module::UnsubscribeCharacteristic(UPS_BLE_Device* Device, uint8 SI, Device->NotifyTokens.Remove(Key); } - // Write CCCD to disable notifications auto WriteStatus = Ch.WriteClientCharacteristicConfigurationDescriptorAsync( - GattClientCharacteristicConfigurationDescriptorValue::None).get(); + WinGAP::GattClientCharacteristicConfigurationDescriptorValue::None).get(); - EPS_GATTStatus GattStatus = (WriteStatus == GattCommunicationStatus::Success) + EPS_GATTStatus GattStatus = (WriteStatus == WinGAP::GattCommunicationStatus::Success) ? EPS_GATTStatus::Success : EPS_GATTStatus::Failure; DispatchUnsubscribe(Device, SI, CI, GattStatus); @@ -633,22 +645,55 @@ void UPS_BLE_Module::DispatchDiscoveryEnd(UPS_BLE_Manager* Mgr, const TArrayRefToManager->JustConnectedDevice(Dev); Dev->OnConnect.Broadcast(Dev, Dev->ActiveServices); } - else { AsyncTask(ENamedThreads::GameThread, [Dev]() { Dev->RefToManager->JustConnectedDevice(Dev); Dev->OnConnect.Broadcast(Dev, Dev->ActiveServices); }); } + if (IsInGameThread()) + { + Dev->RefToManager->JustConnectedDevice(Dev); + Dev->OnConnect.Broadcast(Dev, Dev->ActiveServices); + } + else + { + AsyncTask(ENamedThreads::GameThread, [Dev]() + { + Dev->RefToManager->JustConnectedDevice(Dev); + Dev->OnConnect.Broadcast(Dev, Dev->ActiveServices); + }); + } } void UPS_BLE_Module::DispatchDeviceDisconnected(UPS_BLE_Device* Dev) { if (!Dev) return; - if (IsInGameThread()) { Dev->RefToManager->JustDisconnectedDevice(Dev); Dev->OnDisconnect.Broadcast(Dev); } - else { AsyncTask(ENamedThreads::GameThread, [Dev]() { Dev->RefToManager->JustDisconnectedDevice(Dev); Dev->OnDisconnect.Broadcast(Dev); }); } + if (IsInGameThread()) + { + Dev->RefToManager->JustDisconnectedDevice(Dev); + Dev->OnDisconnect.Broadcast(Dev); + } + else + { + AsyncTask(ENamedThreads::GameThread, [Dev]() + { + Dev->RefToManager->JustDisconnectedDevice(Dev); + Dev->OnDisconnect.Broadcast(Dev); + }); + } } void UPS_BLE_Module::DispatchServicesDiscovered(UPS_BLE_Device* Dev) { if (!Dev) return; - if (IsInGameThread()) { Dev->RefToManager->JustDiscoveredServices(Dev); Dev->OnServicesDiscovered.Broadcast(Dev, Dev->ActiveServices); } - else { AsyncTask(ENamedThreads::GameThread, [Dev]() { Dev->RefToManager->JustDiscoveredServices(Dev); Dev->OnServicesDiscovered.Broadcast(Dev, Dev->ActiveServices); }); } + if (IsInGameThread()) + { + Dev->RefToManager->JustDiscoveredServices(Dev); + Dev->OnServicesDiscovered.Broadcast(Dev, Dev->ActiveServices); + } + else + { + AsyncTask(ENamedThreads::GameThread, [Dev]() + { + Dev->RefToManager->JustDiscoveredServices(Dev); + Dev->OnServicesDiscovered.Broadcast(Dev, Dev->ActiveServices); + }); + } } void UPS_BLE_Module::DispatchRead(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS_GATTStatus Status, TArray Data) @@ -657,14 +702,16 @@ void UPS_BLE_Module::DispatchRead(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS_G if (IsInGameThread()) { if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num()) - Dev->OnRead.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data); + Dev->OnRead.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, + Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data); } else { AsyncTask(ENamedThreads::GameThread, [Dev, SI, CI, Status, Data]() { if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num()) - Dev->OnRead.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data); + Dev->OnRead.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, + Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data); }); } } @@ -675,14 +722,16 @@ void UPS_BLE_Module::DispatchNotify(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS if (IsInGameThread()) { if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num()) - Dev->OnNotify.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data); + Dev->OnNotify.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, + Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data); } else { AsyncTask(ENamedThreads::GameThread, [Dev, SI, CI, Status, Data]() { if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num()) - Dev->OnNotify.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data); + Dev->OnNotify.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, + Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID, Data); }); } } @@ -693,7 +742,8 @@ void UPS_BLE_Module::DispatchWrite(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, EPS_ auto Fire = [Dev, SI, CI, Status]() { if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num()) - Dev->OnWrite.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID); + Dev->OnWrite.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, + Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID); }; if (IsInGameThread()) Fire(); else AsyncTask(ENamedThreads::GameThread, Fire); @@ -707,7 +757,8 @@ void UPS_BLE_Module::DispatchSubscribe(UPS_BLE_Device* Dev, uint8 SI, uint8 CI, if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num()) { Dev->ActiveServices[SI].Characteristics[CI].subscribed = true; - Dev->OnSubscribe.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID); + Dev->OnSubscribe.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, + Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID); } }; if (IsInGameThread()) Fire(); @@ -722,7 +773,8 @@ void UPS_BLE_Module::DispatchUnsubscribe(UPS_BLE_Device* Dev, uint8 SI, uint8 CI if (SI < Dev->ActiveServices.Num() && CI < Dev->ActiveServices[SI].Characteristics.Num()) { Dev->ActiveServices[SI].Characteristics[CI].subscribed = false; - Dev->OnUnsubscribe.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID); + Dev->OnUnsubscribe.Broadcast(Status, Dev, Dev->ActiveServices[SI].ServiceUUID, + Dev->ActiveServices[SI].Characteristics[CI].CharacteristicUUID); } }; if (IsInGameThread()) Fire(); diff --git a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Public/PS_BLEModule.h b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Public/PS_BLEModule.h index df21de1..bb8140f 100644 --- a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Public/PS_BLEModule.h +++ b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Public/PS_BLEModule.h @@ -9,15 +9,6 @@ class UPS_BLE_Device; class UPS_BLE_Manager; -// ─── Internal device record (equivalent to TUE_dev_rec from old plugin) ────── -struct FPS_DeviceRecord -{ - uint64 ID = 0; - int64 RSSI = 0; - FString Name; - void* NativeDeviceRef = nullptr; // WinRT IBluetoothLEDevice* (opaque ptr for forward compat) -}; - // ─── Module ─────────────────────────────────────────────────────────────────── class UPS_BLE_Module : public IModuleInterface @@ -48,9 +39,9 @@ public: bool UnsubscribeCharacteristic(UPS_BLE_Device* Device, uint8 ServiceIndex, uint8 CharIndex); UPS_BLE_Manager* LocalBLEManager = nullptr; + bool bInitialized = false; private: - bool bInitialized = false; // WinRT scanner (opaque handle — implementation in .cpp using WinRT types) void* ScannerHandle = nullptr; diff --git a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Public/PS_BLETypes.h b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Public/PS_BLETypes.h index e1d4d95..9697909 100644 --- a/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Public/PS_BLETypes.h +++ b/Unreal/Plugins/PS_Win_BLE/Source/PS_Win_BLE/Public/PS_BLETypes.h @@ -97,6 +97,18 @@ public: UPROPERTY(BlueprintReadOnly, Category = "PS BLE") TArray Characteristics; }; +// ─── INTERNAL DEVICE RECORD (forward-declared here so Manager can use it) ──── +// This is a plain C++ struct (no UCLASS/USTRUCT) used internally between +// the module and the manager — not exposed to Blueprint. + +struct FPS_DeviceRecord +{ + uint64 ID = 0; + int64 RSSI = 0; + FString Name; + void* NativeDeviceRef = nullptr; +}; + // ─── FORWARD DECLARATIONS ──────────────────────────────────────────────────── class UPS_BLE_Device;