The following code snippet works if the Bluetooth LE device is NOT paired. After pairing the same device, the call to WriteClientCharacteristicConfigurationDescriptorAsync() fails with UNREACHABLE. No PIN code required for pairing.
Can anyone please help? The code will become part of a library with Java JNI bindings, so needs to be in C++. That's why I'm using the WinRT stuff. I'm using VS 2017 target SDK 10.0.18362.0 running on Windows 10 1903.
#include "pch.h" #include <combaseapi.h> using namespace winrt; using namespace Windows::Foundation; using namespace winrt::Windows::Foundation; using namespace Windows::Storage::Streams; using namespace Windows::Devices::Bluetooth; using namespace Windows::Foundation::Collections; using namespace Windows::Devices::Bluetooth::Advertisement; using namespace Windows::Devices::Bluetooth::GenericAttributeProfile; #define NU_SERVICE "{6E400001-B5A3-F393-E0A9-E50E24DCCA9E}" #define TX_CHARACTERISTIC "{6E400002-B5A3-F393-E0A9-E50E24DCCA9E}" #define RX_CHARACTERISTIC "{6E400003-B5A3-F393-E0A9-E50E24DCCA9E}" std::wstring guidToString(GUID uuid) { std::wstring guid; WCHAR* wszUuid = NULL; if(::UuidToString(&uuid, (RPC_WSTR*) &wszUuid) == RPC_S_OK) { guid = wszUuid; ::RpcStringFree((RPC_WSTR*) &wszUuid); } return guid; } void str2ba(const char *straddr, unsigned long long *btaddr) { int i; unsigned int aaddr[6]; unsigned long long tmpaddr = 0; if (sscanf_s(straddr, "%02x:%02x:%02x:%02x:%02x:%02x", &aaddr[0], &aaddr[1], &aaddr[2], &aaddr[3], &aaddr[4], &aaddr[5]) != 6) { return; } *btaddr = 0; for (i = 0; i < 6; i++) { tmpaddr = (unsigned long long) (aaddr[i] & 0xff); *btaddr = ((*btaddr) << 8) + tmpaddr; } } IAsyncAction OpenDevice(unsigned long long deviceAddress) { auto device = co_await BluetoothLEDevice::FromBluetoothAddressAsync(deviceAddress); std::wcout << std::hex << "Device Information: " << std::endl << "\tName :" << device.Name().c_str() << std::endl << "\tAddress :" << device.BluetoothAddress() << std::endl << "\tStatus :" << (device.ConnectionStatus() == BluetoothConnectionStatus::Connected ? "Connected" : "Disconnected") << std::endl << "\tDeviceId :" << device.DeviceId().c_str() << std::endl << std::endl; GUID nusGUID, txcGUID, rxcGUID; CLSIDFromString(TEXT(NU_SERVICE), &nusGUID); CLSIDFromString(TEXT(TX_CHARACTERISTIC), &txcGUID); CLSIDFromString(TEXT(RX_CHARACTERISTIC), &rxcGUID); auto services = co_await device.GetGattServicesAsync();//BluetoothCacheMode::Cached); for(GenericAttributeProfile::GattDeviceService const & s : services.Services()) { std::wcout << std::hex << "\t\tService - Guid: [" << guidToString(s.Uuid()) << "]" << std::endl; auto characteristics = co_await s.GetCharacteristicsAsync(); for(GenericAttributeProfile::GattCharacteristic const & c : characteristics.Characteristics()) { std::wcout << std::hex << "\t\tCharacteristic - Guid: [" << guidToString(c.Uuid()) << "]" << std::endl; if(c.CharacteristicProperties() == GattCharacteristicProperties::Notify) { printf("Notify supported\n"); GattCommunicationStatus status = co_await c.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue::Notify); switch (status) { case GattCommunicationStatus::AccessDenied: printf("access denied\n"); break; case GattCommunicationStatus::ProtocolError: printf("protocol error\n"); break; case GattCommunicationStatus::Unreachable: printf("unreachable\n"); break; case GattCommunicationStatus::Success: c.ValueChanged([](GattCharacteristic const& charateristic, GattValueChangedEventArgs const& args) { std::wcout << std::hex << "\t\tNotified GattCharacteristic - Guid: [" << guidToString(charateristic.Uuid()) << "]" << std::endl; }); // // Code to write to BT device ommitted.... Sleep 5 secs then exit. // Sleep(5000); } } } } device.Close(); } int main() { init_apartment(); // Connect to a specific Bluetooth device. unsigned long long deviceAddress; str2ba("db:e7:df:00:52:32", &deviceAddress); printf("device = %lld\n", deviceAddress); try { OpenDevice(deviceAddress).get(); } catch (const std::exception& e) { std::cout << e.what() << std::endl; return FALSE; } return TRUE; }