The GattCharacteristic.ValueChanged
Event never seems to fire in a very bare bones C++ / WinRT console application.
For device with config
TestDevice
FFFF
EEEE
The following program will simply
TestDevice
TestDevice
with ShortID FFFF
EEEE
GattClientCharacteristicConfigurationDescriptorValue
to EEEE
, which has property Read, Write and NotifyTypedEventHandler<GattCharacteristic,GattValueChangedEventArgs>
to ValueChanged
which should print New Value
on value updateUpdating the characteristic value does not trigger the ValueChanged
event. However, the same device setup has been tested with othe bluetooth stack with success.
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Devices.Bluetooth.h>
#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
#include <winrt/Windows.Devices.Bluetooth.Advertisement.h>
using winrt::Windows::Devices::Bluetooth::BluetoothConnectionStatus;
using winrt::Windows::Devices::Bluetooth::BluetoothLEDevice;
using winrt::Windows::Devices::Bluetooth::BluetoothUuidHelper;
using winrt::Windows::Devices::Bluetooth::Advertisement::BluetoothLEAdvertisementReceivedEventArgs;
using winrt::Windows::Devices::Bluetooth::Advertisement::BluetoothLEAdvertisementWatcher;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile;
using namespace winrt;
class WinBleCentral
{
public:
WinBleCentral()
{
bleWatcher.Received(
[this](BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
hstring testHstring{ std::wstring_view(L"TestDevice") };
if (testHstring == eventArgs.Advertisement().LocalName())
{
this->bleWatcher.Stop();
std::cout << "Matched\n";
BluetoothLEDevice::FromBluetoothAddressAsync(eventArgs.BluetoothAddress()).Completed(
[this](IAsyncOperation<BluetoothLEDevice> sender, AsyncStatus status)
{
if (auto device = sender.GetResults(); device)
{
std::cout << "Connected\n";
winrt::guid serviceGUID = BluetoothUuidHelper::FromShortId(0xFFFF);
device.GetGattServicesForUuidAsync(serviceGUID).Completed(
[this](IAsyncOperation<GattDeviceServicesResult> sender, AsyncStatus status)
{
GattDeviceServicesResult result = sender.get();
if (result && status == winrt::Windows::Foundation::AsyncStatus::Completed)
{
winrt::guid charGUID = BluetoothUuidHelper::FromShortId(0xEEEE);
std::cout << "Num Services: " << result.Services().Size() << '\n';
for (auto&& service : result.Services())
{
service.GetCharacteristicsForUuidAsync(charGUID).Completed(
[this](IAsyncOperation<GattCharacteristicsResult>sender, AsyncStatus status)
{
std::cout << "Get Characteristics\n";
if (auto result = sender.GetResults(); result)
{
std::cout << "Num Characteristics: " << result.Characteristics().Size() << '\n';
for (auto character : result.Characteristics())
{
character.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue::Notify).Completed(
[this, character](IAsyncOperation<GattCommunicationStatus>sender, AsyncStatus status)
{
character.ValueChanged([this](GattCharacteristic characteristic, GattValueChangedEventArgs const& args)
{
std::cout << "New Value!\n";
});
});
}
}
});
}
}
}
);
}
});
}
});
bleWatcher.Start();
};
BluetoothLEAdvertisementWatcher bleWatcher;
};
int main()
{
WinBleCentral bleCentral;
while (getchar() != '\n');
}
This appears to be down to the GattCharacteristic
object falling out of scope. By simply keeping a reference of tha variable the above works as expected.
Below is the same program as above with the Async
side of things stripped out to improve readability. I've tested and the exact same behaviour is displayed in both cases.
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Devices.Bluetooth.h>
#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
#include <winrt/Windows.Devices.Bluetooth.Advertisement.h>
using winrt::Windows::Devices::Bluetooth::BluetoothConnectionStatus;
using winrt::Windows::Devices::Bluetooth::BluetoothLEDevice;
using winrt::Windows::Devices::Bluetooth::BluetoothUuidHelper;
using winrt::Windows::Devices::Bluetooth::Advertisement::BluetoothLEAdvertisementReceivedEventArgs;
using winrt::Windows::Devices::Bluetooth::Advertisement::BluetoothLEAdvertisementWatcher;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile;
using namespace winrt;
class WinBleCentral
{
public:
WinBleCentral()
{
std::cout << "Start\n";
bleWatcher.Received(
[this](BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
hstring testHstring{ std::wstring_view(L"TestDevice") };
if (testHstring == eventArgs.Advertisement().LocalName())
{
this->bleWatcher.Stop();
std::cout << "Matched\n";
auto device = BluetoothLEDevice::FromBluetoothAddressAsync(eventArgs.BluetoothAddress()).get();
std::cout << "Device\n";
winrt::guid serviceGUID = BluetoothUuidHelper::FromShortId(0xFFFF);
auto serviceResults = device.GetGattServicesForUuidAsync(serviceGUID).get();
std::cout << "Service Results\n";
winrt::guid charGUID = BluetoothUuidHelper::FromShortId(0xEEEE);
auto service = serviceResults.Services().GetAt(0);
std::cout << "Services\n";
auto characteristicResults = service.GetCharacteristicsForUuidAsync(charGUID).get();
std::cout << "Characteristic Results\n";
auto characteristic = characteristicResults.Characteristics().GetAt(0);
characteristics.push_back(characteristic);
std::cout << "Characterstic\n";
auto gattCommunicationStatus = characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue::Notify).get();
std::cout << "WriteClientCharacteristicConfiguration\n";
characteristic.ValueChanged([this](GattCharacteristic const& notifyingCharacteristic, GattValueChangedEventArgs args)
{
std::cout << "New Value!\n";
});
}
});
bleWatcher.Start();
}
std::vector<GattCharacteristic> characteristics;
BluetoothLEAdvertisementWatcher bleWatcher;
};
int main()
{
WinBleCentral bleCentral;
while (getchar() != '\n');
}
even though std::vector<GattCharacteristic> characteristics
is never used, the simple act of characteristics.push_back(characteristic)
is enough to keep a reference alive. This behaviour was definitely not expected.
To keep with the whole UWP style, I initially used the IVector
and the Append
method, but the entire thing collapses at check_hresult(WINRT_IMPL_SHIM(winrt::Windows::Foundation::Collections::IVector<T>)->Append(impl::bind_in(value)));
for what appears to be a null pointer.
For the above, all error checking has been eschewed for readability. Actual implementation should have healthy checking of all Async operations to make sure the data is actually there.