-1

I have build a set C++ containing classes on top of the BluetoothAPIs apis.

I can enumerate open handles to services, characteristics and descriptors. I can read characteristic values. The issue that I have is that I cannot write to a characteristic value.

Below is the code use to write the characteristic value

void BleGattCharacteristic::setValue(UCHAR * data, ULONG size){
if (pGattCharacteristic->IsSignedWritable || pGattCharacteristic->IsWritable || pGattCharacteristic->IsWritableWithoutResponse)
{
    size_t required_size = sizeof(BTH_LE_GATT_CHARACTERISTIC_VALUE) + size;

    PBTH_LE_GATT_CHARACTERISTIC_VALUE gatt_value = (PBTH_LE_GATT_CHARACTERISTIC_VALUE)malloc(required_size);

    ZeroMemory(gatt_value, required_size);

    gatt_value->DataSize = (ULONG)size;
    memcpy(gatt_value->Data, data, size);

    HRESULT hr = BluetoothGATTSetCharacteristicValue(bleDeviceContext.getBleServiceHandle(), pGattCharacteristic, gatt_value, NULL, BLUETOOTH_GATT_FLAG_NONE);

    free(gatt_value);

    if (HRESULT_FROM_WIN32(S_OK) != hr)
    {
        stringstream msg;
        msg << "Unable to write the characeristic value. Reason: ["
            << Util.getLastError(hr) << "]";

        throw BleException(msg.str());
    }
}
else
{
    throw BleException("characteristic is not writable");
}}

The call to bleDeviceContext.getBleServiceHandle() returns the open handle to the device info service.

pGattCharacteristics is the pointer to the characteristic to write too. It was opened with a call to BluetoothGATTGetCharacteristics.

I have tried different combinations of the flags with no difference in the return code.

I have also tried using the handle to the device not to the service. In that case I get an ERROR_INVALID_FUNCTION return error code.

I would appreciate any pointers as to what I am doing wrong or what other possible options I could try.

DerekGn
  • 614
  • 4
  • 3
  • The driver is unhappy about your code. Nobody could run this code so you'll have to figure it out yourself. A crucial first step is to fix the bugs in the error reporting code . Calling GetLastError() is not correct, the error code is already embedded in the hr value. Use if (FAILED(hr)) to decide to display it. – Hans Passant Sep 27 '17 at 15:09
  • Util.getLastError() generates a readable error from the HR int value. So i don't see the bug that you see. – DerekGn Sep 27 '17 at 15:17
  • @DerekGn except that [`HRESULT` has a way of getting readable errors](https://stackoverflow.com/a/7008111/332733) – Mgetz Sep 28 '17 at 12:22
  • @Mgetz I did not want to mix com api calls in with winapi for no other reason than i thought it was bad practice. This may be a misguided view. My Util.getLastError() basically does the whole FormatMessage routine. If using HRESULT is ok then less LOC would be better. Thanks for the pointer. – DerekGn Sep 28 '17 at 20:02
  • side note: it's undefined behavior to `delete` `malloc`d memory, you need to use `free` or use `new`. But honestly you don't need to do either as you have a pointer to data... and shouldn't need to make a copy. – Mgetz Sep 29 '17 at 11:14
  • @megtz delete my bad, thanks I will fix The BTH_LE_GATT_CHARACTERISTIC_VALUE is defined as typedef struct _BTH_LE_GATT_CHARACTERISTIC_VALUE { ULONG DataSize; #ifdef MIDL_PASS [size_is(DataSize)] UCHAR Data[*]; #else _Field_size_bytes_(DataSize) UCHAR Data[1]; #endif } BTH_LE_GATT_CHARACTERISTIC_VALUE, *PBTH_LE_GATT_CHARACTERISTIC_VALUE; You cant assign the payload data to the Data array field, not an lvalue error C3863: array type 'UCHAR [1]' is not assignable It looks like the example in the ms docs are incorrect with respect to assigning the Data field. – DerekGn Sep 29 '17 at 15:34

2 Answers2

0

1- You have to use the Service Handle, right.
2- I don't know how you designed your class, and then how you allocate some memory for the Characteristic's Value itself.
What I do (to be sure to have enough and proper memory for Value's data):
a) at init of the Value object, call ::BluetoothGATTGetCharacteristicValue twice, to get the needed size and then actually allocate some internal memory for it.
b) when using it, set the inner memory to what it may , then call ::BluetoothGATTSetCharacteristicValue

hr=::BluetoothGATTSetCharacteristicValue(
handle,
(PBTH_LE_GATT_CHARACTERISTIC)Characteristic,
value,//actually a (PBTH_LE_GATT_CHARACTERISTIC_VALUE) to allocated memory
0,//BTH_LE_GATT_RELIABLE_WRITE_CONTEXT ReliableWriteContext,
BLUETOOTH_GATT_FLAG_NONE)

adanteny
  • 181
  • 1
  • 5
  • yes the code is definitely using service handle. The enumeration of the characteristic uses the standard mechanism as per the [docs](https://msdn.microsoft.com/en-us/library/windows/hardware/hh450795(v=vs.85).aspx). I am doing the same thing with different error handling. – DerekGn Sep 28 '17 at 20:19
  • @DerekGn did you find your solution? About getting the required size, I'm using the snippet from msdn https://social.msdn.microsoft.com/Forums/en-US/bad452cb-4fc2-4a86-9b60-070b43577cc9/is-there-a-simple-example-desktop-programming-c-for-bluetooth-low-energy-devices?forum=wdk The memory buffer is then allocated and **still alive** before and after every call to ::BluetoothGATTSetCharacteristicValue... which is a bit different than your (local) allocation method... – adanteny Sep 29 '17 at 06:42
  • Ok I miss understood your original point, I incorrectly thought you where referring to the characteristic itself not the value. If I understand you are saying read the characteristic value first, using the double read method, and use that instance as the value to write to the characteristic, modifying the instance to contain the data I wish to send to the device. Is this what you meant? – DerekGn Sep 29 '17 at 15:36
  • No problem, after reading my answer, I can see that I didn't explain myself clearly. There's nothing like getting the needed size of the Value : just compute and allocate the proper size according to the data expected :( – adanteny Sep 29 '17 at 15:42
0

So a few things:

typedef struct _BTH_LE_GATT_CHARACTERISTIC_VALUE {
   ULONG DataSize;
   UCHAR Data[];
} BTH_LE_GATT_CHARACTERISTIC_VALUE, *PBTH_LE_GATT_CHARACTERISTIC_VALUE;

is how the data structure used in the parameter CharacteristicValue is defined. Please note that Data is NOT an allocated array, but rather a pointer. So accessing Data[0] is undefined behavior and could be accessing anywhere in memory. Rather you need to do gatt_value.Data = &data; setting the pointer to the address of the input parameter.

Secondly the documentation is quite clear as to why you might get ERROR_INVALID_FUNCTION; if another reliable write is already pending then this write will fail. You should consider retry logic in that case.

As for E_INVALIDARG I'd assume it's related to the undefined behavior but I'd check after fixing the other issues previously mentioned.

Mgetz
  • 5,108
  • 2
  • 33
  • 51
  • in my haste I pasted in the one hack to far version of the snippet. I have updated to the latest version. – DerekGn Sep 28 '17 at 20:07
  • I simplified the call to write one byte to the device not multiple bytes as in the original version. I am sure that there are no other writes on going when attempting this write. The test harness basically opens the device, enumerates services etc. Reads the manufactures name characteristic, then attempts to write to a proprietary characteristic value. I have tried creating a reliable write context and using in call without any additional success. I will give the retry a shot – DerekGn Sep 28 '17 at 20:18