Hello,
i am currently writing a plugin that manages to connect to a paired Heart Rate Monitor (Polar H10) via bluetooth. I have a functional C++ Code that manages to do everything i want as a command line tool.
Now i try to get it running in Unreal Engine, especially as a plugin, so i can include this same plugin in future programs too.
As far as i know it’s not that easy to include windows.h, SetupAPI oder bluetoothleapi.h.
The following command line tool manages to save the current heart rate into a variable heart_rate.
#include "stdafx.h"
#include <windows.h>
#include <tchar.h>
#include <setupapi.h>
#include <combaseapi.h>
#include <bluetoothleapis.h>
#define CHARACTERISTIC "{0000180D-0000-1000-8000-00805F9B34FB}"
int heart_rate = 0;
void Notify(BTH_LE_GATT_EVENT_TYPE EventType, PVOID EventOutParameter, PVOID Context)
{
PBLUETOOTH_GATT_VALUE_CHANGED_EVENT ValueChangedEventParameters = (PBLUETOOTH_GATT_VALUE_CHANGED_EVENT)EventOutParameter;
HRESULT hr;
if (0 == ValueChangedEventParameters->CharacteristicValue->DataSize) {
hr = E_FAIL;
}
else {
if (0x01 == (ValueChangedEventParameters->CharacteristicValue->Data[0] & 0x01)) {
heart_rate = ValueChangedEventParameters->CharacteristicValue->Data[1] * 256 + ValueChangedEventParameters->CharacteristicValue->Data[2];
}
else {
heart_rate = ValueChangedEventParameters->CharacteristicValue->Data[1];
}
}
}
// This function works to get a handle for a BLE device based on its GUID
HANDLE GetHandle(__in GUID AGuid)
{
HDEVINFO hDI;
SP_DEVICE_INTERFACE_DATA did;
SP_DEVINFO_DATA dd;
GUID BluetoothInterfaceGUID = AGuid;
HANDLE hComm = NULL;
hDI = SetupDiGetClassDevs(&BluetoothInterfaceGUID, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if (hDI == INVALID_HANDLE_VALUE) return NULL;
did.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
dd.cbSize = sizeof(SP_DEVINFO_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(hDI, NULL, &BluetoothInterfaceGUID, i, &did); i++)
{
SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData;
DeviceInterfaceDetailData.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
DWORD size = 0;
if (!SetupDiGetDeviceInterfaceDetail(hDI, &did, NULL, 0, &size, 0))
{
int err = GetLastError();
if (err == ERROR_NO_MORE_ITEMS) break;
PSP_DEVICE_INTERFACE_DETAIL_DATA pInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)GlobalAlloc(GPTR, size);
pInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(hDI, &did, pInterfaceDetailData, size, &size, &dd))
break;
hComm = CreateFile(
pInterfaceDetailData->DevicePath,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
GlobalFree(pInterfaceDetailData);
}
}
SetupDiDestroyDeviceInfoList(hDI);
return hComm;
}
void ConnectToHRM()
{
// Step 1: find the BLE device handle from its GUID
GUID AGuid;
// GUID can be constructed from "{xxx....}" string using CLSID
CLSIDFromString(TEXT(CHARACTERISTIC), &AGuid);
// Get the handle
HANDLE hLEDevice = GetHandle(AGuid);
// Step 2: Get a list of services that the device advertises
// first send 0, NULL as the parameters to BluetoothGATTServices inorder to get the number of
// services in serviceBufferCount
USHORT serviceBufferCount;
////////////////////////////////////////////////////////////////////////////
// Determine Services Buffer Size
////////////////////////////////////////////////////////////////////////////
HRESULT hr = BluetoothGATTGetServices(
hLEDevice,
0,
NULL,
&serviceBufferCount,
BLUETOOTH_GATT_FLAG_NONE);
if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
}
PBTH_LE_GATT_SERVICE pServiceBuffer = (PBTH_LE_GATT_SERVICE)
malloc(sizeof(BTH_LE_GATT_SERVICE) * serviceBufferCount);
if (NULL == pServiceBuffer) {
}
else {
RtlZeroMemory(pServiceBuffer,
sizeof(BTH_LE_GATT_SERVICE) * serviceBufferCount);
}
////////////////////////////////////////////////////////////////////////////
// Retrieve Services
////////////////////////////////////////////////////////////////////////////
USHORT numServices;
hr = BluetoothGATTGetServices(
hLEDevice,
serviceBufferCount,
pServiceBuffer,
&numServices,
BLUETOOTH_GATT_FLAG_NONE);
if (S_OK != hr) {
}
// Step 3: now get the list of charactersitics. note how the pServiceBuffer is required from step 2
////////////////////////////////////////////////////////////////////////////
// Determine Characteristic Buffer Size
////////////////////////////////////////////////////////////////////////////
USHORT charBufferSize;
hr = BluetoothGATTGetCharacteristics(
hLEDevice,
pServiceBuffer,
0,
NULL,
&charBufferSize,
BLUETOOTH_GATT_FLAG_NONE);
if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
}
PBTH_LE_GATT_CHARACTERISTIC pCharBuffer = NULL;
if (charBufferSize > 0) {
pCharBuffer = (PBTH_LE_GATT_CHARACTERISTIC)
malloc(charBufferSize * sizeof(BTH_LE_GATT_CHARACTERISTIC));
if (NULL == pCharBuffer) {
}
else {
RtlZeroMemory(pCharBuffer,
charBufferSize * sizeof(BTH_LE_GATT_CHARACTERISTIC));
}
////////////////////////////////////////////////////////////////////////////
// Retrieve Characteristics
////////////////////////////////////////////////////////////////////////////
USHORT numChars;
hr = BluetoothGATTGetCharacteristics(
hLEDevice,
pServiceBuffer,
charBufferSize,
pCharBuffer,
&numChars,
BLUETOOTH_GATT_FLAG_NONE);
if (S_OK != hr) {
}
if (numChars != charBufferSize) {
}
}
// Step 4: now get the list of descriptors. note how the pCharBuffer is required from step 3
// descriptors are required as we descriptors that are notification based will have to be written
// once IsSubcribeToNotification set to true, we set the appropriate callback function
// need for setting descriptors for notification according to
//http://social.msdn.microsoft.com/Forums/en-US/11d3a7ce-182b-4190-bf9d-64fefc3328d9/windows-bluetooth-le-apis-event-callbacks?forum=wdk
PBTH_LE_GATT_CHARACTERISTIC currGattChar;
for (int ii = 0; ii <charBufferSize; ii++) {
currGattChar = &pCharBuffer[ii];
USHORT charValueDataSize;
PBTH_LE_GATT_CHARACTERISTIC_VALUE pCharValueBuffer;
///////////////////////////////////////////////////////////////////////////
// Determine Descriptor Buffer Size
////////////////////////////////////////////////////////////////////////////
USHORT descriptorBufferSize;
hr = BluetoothGATTGetDescriptors(
hLEDevice,
currGattChar,
0,
NULL,
&descriptorBufferSize,
BLUETOOTH_GATT_FLAG_NONE);
if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
}
PBTH_LE_GATT_DESCRIPTOR pDescriptorBuffer;
if (descriptorBufferSize > 0) {
pDescriptorBuffer = (PBTH_LE_GATT_DESCRIPTOR)
malloc(descriptorBufferSize
* sizeof(BTH_LE_GATT_DESCRIPTOR));
if (NULL == pDescriptorBuffer) {
}
else {
RtlZeroMemory(pDescriptorBuffer, descriptorBufferSize);
}
////////////////////////////////////////////////////////////////////////////
// Retrieve Descriptors
////////////////////////////////////////////////////////////////////////////
USHORT numDescriptors;
hr = BluetoothGATTGetDescriptors(
hLEDevice,
currGattChar,
descriptorBufferSize,
pDescriptorBuffer,
&numDescriptors,
BLUETOOTH_GATT_FLAG_NONE);
if (S_OK != hr) {
}
if (numDescriptors != descriptorBufferSize) {
}
for (int kk = 0; kk<numDescriptors; kk++) {
PBTH_LE_GATT_DESCRIPTOR currGattDescriptor = &pDescriptorBuffer[kk];
////////////////////////////////////////////////////////////////////////////
// Determine Descriptor Value Buffer Size
////////////////////////////////////////////////////////////////////////////
USHORT descValueDataSize;
hr = BluetoothGATTGetDescriptorValue(
hLEDevice,
currGattDescriptor,
0,
NULL,
&descValueDataSize,
BLUETOOTH_GATT_FLAG_NONE);
if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
}
PBTH_LE_GATT_DESCRIPTOR_VALUE pDescValueBuffer = (PBTH_LE_GATT_DESCRIPTOR_VALUE)malloc(descValueDataSize);
if (NULL == pDescValueBuffer) {
}
else {
RtlZeroMemory(pDescValueBuffer, descValueDataSize);
}
////////////////////////////////////////////////////////////////////////////
// Retrieve the Descriptor Value
////////////////////////////////////////////////////////////////////////////
hr = BluetoothGATTGetDescriptorValue(
hLEDevice,
currGattDescriptor,
(ULONG)descValueDataSize,
pDescValueBuffer,
NULL,
BLUETOOTH_GATT_FLAG_NONE);
if (S_OK != hr) {
}
// you may also get a descriptor that is read (and not notify) andi am guessing the attribute handle is out of limits
// we set all descriptors that are notifiable to notify us via IsSubstcibeToNotification
if (currGattDescriptor->AttributeHandle < 255) {
BTH_LE_GATT_DESCRIPTOR_VALUE newValue;
RtlZeroMemory(&newValue, sizeof(newValue));
newValue.DescriptorType = ClientCharacteristicConfiguration;
newValue.ClientCharacteristicConfiguration.IsSubscribeToNotification = TRUE;
hr = BluetoothGATTSetDescriptorValue(
hLEDevice,
currGattDescriptor,
&newValue,
BLUETOOTH_GATT_FLAG_NONE);
if (S_OK != hr) {
}
}
}
}
// set the appropriate callback function when the descriptor change value
BLUETOOTH_GATT_EVENT_HANDLE EventHandle;
if (currGattChar->IsNotifiable) {
BTH_LE_GATT_EVENT_TYPE EventType = CharacteristicValueChangedEvent;
BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION EventParameterIn;
EventParameterIn.Characteristics[0] = *currGattChar;
EventParameterIn.NumCharacteristics = 1;
hr = BluetoothGATTRegisterEvent(
hLEDevice,
EventType,
&EventParameterIn,
(PFNBLUETOOTH_GATT_EVENT_CALLBACK) Notify,
NULL,
&EventHandle,
BLUETOOTH_GATT_FLAG_NONE);
if (S_OK != hr) {
}
}
else if (currGattChar->IsReadable) {
////////////////////////////////////////////////////////////////////////////
// Determine Characteristic Value Buffer Size
////////////////////////////////////////////////////////////////////////////
hr = BluetoothGATTGetCharacteristicValue(
hLEDevice,
currGattChar,
0,
NULL,
&charValueDataSize,
BLUETOOTH_GATT_FLAG_NONE);
if (HRESULT_FROM_WIN32(ERROR_MORE_DATA) != hr) {
}
pCharValueBuffer = (PBTH_LE_GATT_CHARACTERISTIC_VALUE)malloc(charValueDataSize);
if (NULL == pCharValueBuffer) {
}
else {
RtlZeroMemory(pCharValueBuffer, charValueDataSize);
}
////////////////////////////////////////////////////////////////////////////
// Retrieve the Characteristic Value
////////////////////////////////////////////////////////////////////////////
hr = BluetoothGATTGetCharacteristicValue(
hLEDevice,
currGattChar,
(ULONG)charValueDataSize,
pCharValueBuffer,
NULL,
BLUETOOTH_GATT_FLAG_NONE);
if (S_OK != hr) {
}
// Free before going to next iteration, or memory leak.
free(pCharValueBuffer);
pCharValueBuffer = NULL;
}
}
// go into an inf loop that sleeps. you will ideally see notifications from the HR device
while (1) {
Sleep(1000);
}
CloseHandle(hLEDevice);
}
So i started to create a plugin (function library) which creates an instance of a HeartRateConnector (.h/.cpp). For now this Connector has two empty functions which i can call in Unreal Engine.
HeartRateConnector.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "HeartRateConnector.generated.h"
/**
*
*/
UCLASS(Blueprintable)
class PLUGINHEARTRATE_API UHeartRateConnector : public UObject
{
GENERATED_BODY()
UFUNCTION(BlueprintCallable, Category = "PluginHeartRate")
static void ConnectHeartRateMonitor();
UFUNCTION(BlueprintCallable, Category = "PluginHeartRate")
static void DisconnectHeartRateMonitor();
};
HeartRateConnector.cpp
#include "HeartRateConnector.h"
void UHeartRateConnector::ConnectHeartRateMonitor()
{
}
void UHeartRateConnector::DisconnectHeartRateMonitor()
{
}
First i tried to just copy my code into HeartRateConnector.cpp, used
#include "AllowWindowsPlatformTypes.h"
....
#include "HideWindowsPlatformTypes.h"
to avoid lots of errors, but i still have problems with SetupAPI and bluetoothleapi.h.
My next idea was, to make a static library of my original command line tool and tried to integrade it into my plugin, but i still dont know how to manage it and if it solves the problems with SetupAPI and bluetoothleapi.h.
Right now i just need a hint in the right direction, can anyone help me?
greetings
chris