This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

S132: Is it possible to send an empty notification

My previous product uses an empty notification to indicate that a bulk transfer is finished. I attempted to send an empty notification but got error code 0x10 (NRF_ERROR_INVALID_FLAGS).

Is there a way to send an empty notification?

ble_gatts_hvx_params_t hvx_param =
{
    .type   = BLE_GATT_HVX_NOTIFICATION,
    .handle = p_ctx->bulkdata_handle,
    .p_data = data,
    .p_len  = &len,
};
hvx_param.p_len = 0;
err_code = sd_ble_gatts_hvx(p_ctx->conn_handle, &hvx_param);

[Edit]

No empty packet sent when p_data = NULL and p_len = 0.

image description

Example of an empty notification packet being sent by TI CC2540.

image description

[Edit 2]

Alright. Added print on TX_COMPLETE.

Heres the log at the end of the transfer. Its hard to tell if one of these completes correlates to the empty notification.

BULKDATA:INFO:on_tx_complete: 503
BULKDATA:INFO:on_tx_complete: 504
APP:INFO:Sent 10 KBytes
BULKDATA:INFO:on_tx_complete: 505
BULKDATA:INFO:Sent empty packet, error code: 0x0
APP:INFO:Finished.
BULKDATA:INFO:on_tx_complete: 506
BULKDATA:INFO:on_tx_complete: 507
BULKDATA:INFO:on_tx_complete: 508
BULKDATA:INFO:on_tx_complete: 509
BULKDATA:INFO:on_tx_complete: 510
BULKDATA:INFO:on_tx_complete: 511
BULKDATA:INFO:on_tx_complete: 512
BULKDATA:INFO:on_tx_complete: 513

I rebuilt without sending the empty packet and there does appear to be one less "on_tx_complete" after the Finished printout.

BULKDATA:INFO:on_tx_complete: 503
BULKDATA:INFO:on_tx_complete: 504
APP:INFO:Sent 10 KBytes
BULKDATA:INFO:on_tx_complete: 505
APP:INFO:Finished.
BULKDATA:INFO:on_tx_complete: 506
BULKDATA:INFO:on_tx_complete: 507
BULKDATA:INFO:on_tx_complete: 508
BULKDATA:INFO:on_tx_complete: 509
BULKDATA:INFO:on_tx_complete: 510
BULKDATA:INFO:on_tx_complete: 511
BULKDATA:INFO:on_tx_complete: 512

Next I modified the code to send only empty notifications. This is where things get weird. It appears that each notification is 9 bytes of 0xFF. 9 bytes happens to be the initial length of the characteristic however the characteristic is initialized to all zeros.

Char init code, generated by BDS:

ble_app_app_generic_data_rcv_t app_generic_data_rcv_initial_value = p_app_init->ble_app_app_generic_data_rcv_initial_value; 

ble_add_char_params_t add_app_generic_data_rcv_params;
memset(&add_app_generic_data_rcv_params, 0, sizeof(add_app_generic_data_rcv_params));

add_app_generic_data_rcv_params.uuid                = 0xFF25;
add_app_generic_data_rcv_params.uuid_type           = ble_uuid.type; 
add_app_generic_data_rcv_params.max_len             = NRF_BLE_GATT_MAX_MTU_SIZE;
add_app_generic_data_rcv_params.init_len            = app_generic_data_rcv_encode(&app_generic_data_rcv_initial_value, app_generic_data_rcv_encoded_value);
add_app_generic_data_rcv_params.p_init_value        = app_generic_data_rcv_encoded_value; 
add_app_generic_data_rcv_params.is_value_user       = 1; 
add_app_generic_data_rcv_params.char_props.notify   = 1; 
add_app_generic_data_rcv_params.char_props.write    = 1; 
add_app_generic_data_rcv_params.write_access        = SEC_OPEN; 
add_app_generic_data_rcv_params.cccd_write_access   = SEC_OPEN;
// 1 for variable length and 0 for fixed length.
add_app_generic_data_rcv_params.is_var_len          = 1;

err_code = characteristic_add(p_app->service_handle, &add_app_generic_data_rcv_params, &(p_app->app_generic_data_rcv_handles));

How did you setup your characteristic?

  • It was "empty sd_ble_gatts_hvx" aka sd_ble_gatts_hvx with parameters containing p_data = NULL and p_len = 0. If you get NRF_SUCCESS then it means that stack accepted the packet and it should be sent out as soon as possible (typically on very next interval). After that really happens you get BLE_EVT_TX_COMPLETE event call which would indicate it went out over radio. I guess you have better experience with TI sniffer then I do (I've used it 3 years ago but Nordic was more reliable for me and then I even switched to Frontline which is several levels better - unfortunately also 100x more expensive) but are you sure you capture whole sequence where should empty ATT Notify packet appear?

  • I can't say I'm happy with the TI sniffer but it was all I had in the past and I haven't tried the nordic sniffer as I currently only have one noridic devkit.

    PS: I'd love to get a dedicated sniffer if they aren't too expensive, if you have a recommendation.

  • For me TI dev kit with sniffer FW is only little worse then nRF51 DK (~$40) with Nordic sniffer FW (it uses Wireshark unlike TI which uses this proprietary colorful GUI), in the end they are both same price so roughly same performance. Frontline BPA Low Energy is much better (kind of industry standard) but it costs typically ~$4000, if you get lucky you can get it as low as $1000 in some markets and distributers. Then you can have also support and maintenance license extension beyond 1st year, typically costs 1-2k USD per 3 years. But that only gives you SW and FW upgrades, you can save some money, not pay for maintenance and live on older SW version. I do recommend it, fo any mid/large company 4k USD is nothing when it comes to R&D tools.

  • Well I might try to get the nordic sniffer working. Atleast wireshark probably won't crash every few minutes.

    I'm in a small company working on a project for a large company. So 4k maybe a hard sell. But I can always ask :P

  • Soooo, too many TI in one thread made me angry so I've tried "empty" Notification on nRF52 with S132 V2.0.1 (using GAP Peripheral and GATT Server roles). It works like a charm, I can see empty HVX packet (only 3 bytes in ATT layer: HVX op code 0x1B and 16-bit ATT handle id) on my sniffer. Here is code sample which made the job for me (called right after GAP Central/GATT Client enabled notifications on my Characteristic):

    uint16_t               hvx_len = 0;
    ble_gatts_hvx_params_t hvx_params;
    memset(&hvx_params, 0, sizeof(hvx_params));
    
    hvx_params.handle   = m_message_handles.value_handle;
    hvx_params.type     = BLE_GATT_HVX_NOTIFICATION;
    hvx_params.offset   = 0;
    hvx_params.p_len    = &hvx_len;
    hvx_params.p_data   = NULL;
    
    sd_ble_gatts_hvx(m_conn_handle, &hvx_params);
    
Related