PS_Win_BLE : fix erreurs de compilation WinRT

- Suppression des 'using namespace Windows::*' au scope global
  (conflit avec le namespace Windows d'Unreal via AllowWindowsPlatformTypes)
- Remplacement par un macro PS_BLE_WINRT_NS avec aliases locaux
  (WinBT, WinAdv, WinGAP, WinStr) utilisés dans chaque fonction
- Ajout de FPS_BLEDeviceHandle et FPS_GattServiceHandle : wrappers
  heap-alloués pour les types WinRT qui suppriment operator new
- Suppression warning C4265 (dtor non-virtual interne aux headers WinRT)
- Plugin charge sans erreur dans UE 5.5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
j.foucher 2026-02-18 16:46:01 +01:00
parent d593bbd9fd
commit 113eddef46
6 changed files with 204 additions and 150 deletions

View File

@ -17,6 +17,10 @@
"TargetAllowList": [
"Editor"
]
},
{
"Name": "PS_Win_BLE",
"Enabled": true
}
]
}

View File

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

View File

@ -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 <winrt/Windows.Foundation.h>
#pragma warning(pop)
#include "Windows/HideWindowsPlatformAtomics.h"
#include "Windows/HideWindowsPlatformTypes.h"
using namespace winrt;
#endif
// ─────────────────────────────────────────────────────────────────────────────
UPS_BLE_Device::UPS_BLE_Device()

View File

@ -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 <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
@ -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<FPS_ScannerState*>(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<FString> 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<FPS_DeviceRecord> 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<TArray<FPS_DeviceRecord>, ESPMode::ThreadSafe> Collected =
MakeShared<TArray<FPS_DeviceRecord>, 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<FPS_ScannerState*>(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<GattDeviceService*>(SvcPtr);
}
if (SvcPtr) delete static_cast<FPS_GattServiceHandle*>(SvcPtr);
}
Device->NativeGattServices.Empty();
// Close device handle
if (Device->NativeDeviceHandle)
{
delete static_cast<BluetoothLEDevice*>(Device->NativeDeviceHandle);
delete static_cast<FPS_BLEDeviceHandle*>(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<BluetoothLEDevice*>(Device->NativeDeviceHandle);
return BLEDev->ConnectionStatus() == BluetoothConnectionStatus::Connected;
auto* Handle = static_cast<FPS_BLEDeviceHandle*>(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<GattDeviceService*>(Device->NativeGattServices[SI]);
auto Chars = Svc->GetCharacteristicsAsync(BluetoothCacheMode::Cached).get();
if (Chars.Status() != GattCommunicationStatus::Success) return;
auto* SvcH = static_cast<FPS_GattServiceHandle*>(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<uint8> 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<uint8>& Data)
{
#if PLATFORM_WINDOWS
if (!Device || SI >= Device->NativeGattServices.Num()) return false;
if (!Device || SI >= (uint8)Device->NativeGattServices.Num()) return false;
TArray<uint8> DataCopy = Data;
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [Device, SI, CI, DataCopy]()
{
PS_BLE_WINRT_NS
try
{
GattDeviceService* Svc = static_cast<GattDeviceService*>(Device->NativeGattServices[SI]);
auto Chars = Svc->GetCharacteristicsAsync(BluetoothCacheMode::Cached).get();
if (Chars.Status() != GattCommunicationStatus::Success) return;
auto* SvcH = static_cast<FPS_GattServiceHandle*>(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<GattDeviceService*>(Device->NativeGattServices[SI]);
auto Chars = Svc->GetCharacteristicsAsync(BluetoothCacheMode::Cached).get();
if (Chars.Status() != GattCommunicationStatus::Success) return;
auto* SvcH = static_cast<FPS_GattServiceHandle*>(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<uint8> 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<GattDeviceService*>(Device->NativeGattServices[SI]);
auto Chars = Svc->GetCharacteristicsAsync(BluetoothCacheMode::Cached).get();
if (Chars.Status() != GattCommunicationStatus::Success) return;
auto* SvcH = static_cast<FPS_GattServiceHandle*>(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<winrt::event_token**>(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 TArray<FPS
void UPS_BLE_Module::DispatchDeviceConnected(UPS_BLE_Device* Dev)
{
if (!Dev) return;
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); }); }
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<uint8> 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();

View File

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

View File

@ -97,6 +97,18 @@ public:
UPROPERTY(BlueprintReadOnly, Category = "PS BLE") TArray<FPS_CharacteristicItem> 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;