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

Initialize BLE Peripheral Connections without BLE_NUS_C_ARRAY_DEF() and BLE_DB_DISCOVERY_ARRAY_DEF()

Hi,

I am using nRF52840 with SDK 15.3.0 and S140.

I am connecting to multiple peripherals as a central device and using NUS to communicate with these peripherals. I need to keep track of peripherals within my own array (scan_xtag_arr[]). I am using the BLE_NUS_C_ARRAY_DEF and BLE_DB_DISCOVERY_ARRAY_DEF() macros after referencing the ble_app_multilink_central example. BLE_NUS_C_ARRAY_DEF is defining my m_ble_nus_c_arr[].

After successfully connecting peripherals and discovering the NUS service on each, I receive a BLE_NUS_C_EVT_DISCOVERY_COMPLETE event within my NUS event handler called xgw_ble_nus_c_evt_handler(). Within this handler, I am setting the handles within the m_ble_nus_c_arr[] index to match my scan_xtag_arr[] index for the peripheral.

So, I connect peripheral_A and setup conn_handle 0 (provided as p_ble_nus_evt->conn_handle within the NUS handler) within m_ble_nus_c_arr[0] using ble_nus_c_handles_assign(). I connect peripheral_B and setup conn_handle 1 within m_ble_nus_c_arr[1]. So far no issue.

I send commands to both peripheral_A and peripheral_B and I get responses back as expected. Specifically, my NUS handler is passed event BLE_NUS_C_EVT_NUS_TX_EVT with p_ble_nus_c pointing at m_ble_nus_c_arr[0] and p_ble_nus_evt->conn_handle = 0 when getting responses from peripheral_A. Likewise, p_ble_nus_c points at m_ble_nus_c_arr[1] and p_ble_nus_evt->conn_handle = 1 when getting responses from peripheral_B. Still good.

Issues occur after I disconnect peripheral_B. I send a message to peripheral_A, and the response comes into my NUS handler (event BLE_NUS_C_EVT_NUS_TX_EVT) with p_ble_nus_c pointing at m_ble_nus_c_arr[1]  (the disconnected peripheral m_ble_nus_c_arr[] element) and p_ble_nus_evt->conn_handle = 0xFFFF (denoting invalid handle). The actual data in the message from my peripehral_A is correct, just the references provided within the NUS handler (p_ble_nus_c  and p_ble_nus_evt->conn_handle) are incorrect.

It looks like I am getting incorrect references passed back to the NUS event handler, even when I am able to send and receive data ok over NUS.

Is there another way that I can ref the individual NUS connections? Both NUS client pointer (p_ble_nus_c) and p_ble_nus_evt->conn_handle values passed to the NUS handler seem unreliable.

As a last resort, I can update the protocol running over NUS to determine the source of responses from peripherals, but this would be a hack to work around a Nordic API issue. There must be some way to properly reference which NUS connection is the source for incoming data as indicated by the BLE_NUS_C_EVT_NUS_TX_EVT event.

Thanks,

Mark J

  • Hello Mark,

    I am not sure I understan exactly how your m_ble_nus_c_arr[] array is set up (not finding the equivalent in the ble_app_multilink_central example). 

    However, the Nordic UART service has a "bug". Not really a bug since it isn't used in any examples with multiple connections, but the on_hvx() in ble_nus_c.c doesn't correctly forward the connection handle. Is this what you are seeing?

    Try to change on_hvx() in ble_nus_c.c to this:

    static void on_hvx(ble_nus_c_t * p_ble_nus_c, ble_evt_t const * p_ble_evt)
    {
        // HVX can only occur from client sending.
        if (   (p_ble_nus_c->handles.nus_tx_handle != BLE_GATT_HANDLE_INVALID)
            && (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_nus_c->handles.nus_tx_handle)
            && (p_ble_nus_c->evt_handler != NULL))
        {
            ble_nus_c_evt_t ble_nus_c_evt;
    
            ble_nus_c_evt.conn_handle = p_ble_evt->evt.gattc_evt.conn_handle;
            ble_nus_c_evt.evt_type = BLE_NUS_C_EVT_NUS_TX_EVT;
            ble_nus_c_evt.p_data   = (uint8_t *)p_ble_evt->evt.gattc_evt.params.hvx.data;
            ble_nus_c_evt.data_len = p_ble_evt->evt.gattc_evt.params.hvx.len;
    
            p_ble_nus_c->evt_handler(p_ble_nus_c, &ble_nus_c_evt);
            NRF_LOG_DEBUG("Client sending data.");
        }
    }

    (line 10 is added).

    Try this, and see if it helps. If not, perhaps it is possible to recreate this?

    BR,

    Edvin

  • Hi ,

    Thanks for your prompt reply. The addition of "ble_nus_c_evt.conn_handle = p_ble_evt->evt.gattc_evt.conn_handle;" within ble_nus_c.c made a difference. I am now seeing the correct p_ble_nus_evt->conn_handle within my NUS handler. 

    There were 2 issues with data passed to the NUS handler. I will use code snippets to better explain these 2 issues.

    I have declared both DB Discovery and NUS Client arrays (m_db_disco_arr[] and m_ble_nus_c_arr[]) using the macros discussed (as required).

    BLE_DB_DISCOVERY_ARRAY_DEF(m_db_disc_arr, NRF_SDH_BLE_CENTRAL_LINK_COUNT);  // Database discovery module array.
    BLE_NUS_C_ARRAY_DEF(m_ble_nus_c_arr, NRF_SDH_BLE_CENTRAL_LINK_COUNT);       // BLE Nordic UART Service (NUS) client array for multpile connections.

    I have also setup NUS clients as per egs. Note that this includes stipulating my handler for each NUS client array element as shown below.

    static void xgw_ble_nus_c_init(void)
    {
        ret_code_t err_code = 0;
        ble_nus_c_init_t init;
    
        init.evt_handler = xgw_ble_nus_c_evt_handler;
    
        for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
        {
          err_code = ble_nus_c_init(&m_ble_nus_c_arr[i], &init);
          APP_ERROR_CHECK(err_code);
        }
    }

    Within my NUS handler ("xgw_ble_nus_c_evt_handler()") processing of BLE_NUS_C_EVT_NUS_TX_EVT events, I have been trying to use p_ble_nus_c to match the address of a m_ble_nus_arr[] element. This tells me which NUS client connection data is coming in from when there is more than 1. Note that I also logged the p_ble_nus_evt->conn_handle that was passed in. 

    static void xgw_ble_nus_c_evt_handler(ble_nus_c_t * p_ble_nus_c, ble_nus_c_evt_t const * p_ble_nus_evt)
    {
      ret_code_t err_code;
    
      switch (p_ble_nus_evt->evt_type)
      {
        case BLE_NUS_C_EVT_DISCOVERY_COMPLETE:
    
          // Print the m_connecting_idx. This is stored to keep track of which
          // m_ble_nus_c_arr[x] element we are currently connecting to.
          SEGGER_RTT_printf(0, "NUS Discovery complete for idx %d\n", m_connecting_idx);
    
          // Assign handles.
          err_code = ble_nus_c_handles_assign(p_ble_nus_c, p_ble_nus_evt->conn_handle, &p_ble_nus_evt->handles);
          APP_ERROR_CHECK(err_code);
    
          // Enable NUS notifications.
          err_code = ble_nus_c_tx_notif_enable(p_ble_nus_c);
          APP_ERROR_CHECK(err_code);
    
          // Update the connection store to indicate connected.
          set_scan_xtag_connect_state(m_connecting_idx, 1);
    
          SEGGER_RTT_printf(0, "Set status to connected for idx %d\n", m_connecting_idx);
          print_scan_xtags();
    
          // Indicate to the host that a connection is now complete.
          usbd_connect_xtag_resp();
    
          // Reset m_connect_disconnect_idx as it is now unused.
          m_connecting_idx = NO_CONNECT_ACTIVE;
    
          // DEBUG 
          print_scan_xtags();
          xgw_ble_print_nus_array_handles();
    
          break;
    
        case BLE_NUS_C_EVT_NUS_TX_EVT:
        {  
          // Simply update data wtihin xgateway_usbd.c's BLE msg buffer and process on a scheduled handler.
          for(uint8_t x=0; x<NRF_SDH_BLE_CENTRAL_LINK_COUNT; x++)
          {
            // Use p_ble_nus_c to get the idx.
            if( &m_ble_nus_c_arr[x] ==  p_ble_nus_c)
            {
              // DEBUG LOG
              SEGGER_RTT_printf(0, "Setting up usbd handler. Nus client idx %d matches pointer passed in. Handle on client pointer %d and NUS client handle passed in %d\n", 
                  x, p_ble_nus_c->conn_handle, p_ble_nus_evt->conn_handle);
              usbd_ble_data_handler_setup(p_ble_nus_evt->p_data, p_ble_nus_evt->data_len, x);
            }
          }
    
          // Schedule an event on the main thread to process the response. Will run usbd_ble_data_handler() within xgateway_usbd.c
          app_sched_event_put(NULL,  0, main_usbd_ble_sched_handler);
    
          break;
        }
        case BLE_NUS_C_EVT_DISCONNECTED:
        {
          SEGGER_RTT_printf(0, "NUS Disconnected. xtag clear to follow.\n");
    
          // Get the idx of the disconnected device using p_ble_nus_c
          for(uint8_t x=0; x<NRF_SDH_BLE_CENTRAL_LINK_COUNT; x++)
          {
            if( &m_ble_nus_c_arr[x] ==  p_ble_nus_c)
            {
              // Clear the data at the idx x.
              clear_scan_xtag(x);
    
              // DEBUG
              SEGGER_RTT_printf(0, "Cleared the xtag at idx %d\n", x);
              print_scan_xtags();
              xgw_ble_print_nus_array_handles();
    
              break;
            }
          }
    
          break;
        }
      }
    }

    My steps (that should be repeatable) were to connect to a peripheral_A and then connect to periperal_B.

    I could send data to peripheral_A and when getting signaled of a response from peripheral_A (BLE_NUS_C_EVT_NUS_TX_EVT ), p_ble_nus_c pointed at m_ble_nus_arr[0] and p_ble_nus_evt->conn_handle matched the conn_handle at m_ble_nus_arr[0]. Both were "0". 

    I could also send data to peripheral_B and when getting signaled of a response from peripheral_B (BLE_NUS_C_EVT_NUS_TX_EVT ), p_ble_nus_c pointed at m_ble_nus_arr[1] and p_ble_nus_evt->conn_handle matched the conn_handle at m_ble_nus_arr[1]. Both were "1".

    I then disconnected periperhal_B. So, m_ble_nus_arr[1] should may be unusable. Certainly I can see that its conn_handle was set to 0xFFFF (invalid handle).

    I sent a command over NUS to peripheral_A using m_ble_nus_arr[0]. It responded and I was signalled via the BLE_NUS_C_EVT_NUS_TX_EVT as expected, but here were the issues

    1, The p_ble_nus_c passed into my xgw_ble_nus_c_evt_handler() was the address of m_ble_nus_arr[1]  when I expected it to be the address of m_ble_nus_arr[0].

    2. The p_ble_nus_evt->conn_handle passed into xgw_ble_nus_c_evt_handler() was 0xFFFF. This matched the handle at m_ble_nus_arr[1]. I was expecting p_ble_nus_evt->conn_handle to be "0" (to match conn_handle at m_ble_nus_arr[0]).

    After the patch to ble_nus_c.c, the first issue (1 above) persists. I am still getting p_ble_nus_c matching m_ble_nus_arr[1] - even though I used m_ble_nus_arr[0] to send the msg to the peripheral_A.

    After the patch, the second issue (2 above) seems resolved. I am now getting p_ble_nus_evt->conn_handle = 0 (matches the conn_handle within m_ble_nus_arr[0]). So, the p_ble_nus_evt->conn_handle value into the NUS handler is now correct, but the p_ble_nus_c  is still pointing at the wrong m_ble_nus_arr[] element.

    I can use the correct p_ble_nus_evt->conn_handle and match it to a m_ble_nus_arr[] element to get the correct element and assume that the p_ble_nus_c is invalid.

    I think that both can be classified as bugs. I can work around the remaining issue (1 above) with the fix provided for the other issue (2 above).

    You should be able to easily recreate this issue by connecting to 2 peripherals with 2 NUS Clients, disconnecting the second and messaging with the first. Expect a response, but the p_ble_nus_c won't be pointing at the correct NUS client array element.

    If you can find a fix to the remaining issue (1 above), that would be great. Looking at ble_nus_c.c, I can see that on_hvx() is called from ble_nus_c_on_ble_evt(). The ble_nus_c_on_ble_evt arg "p_context" ends up as the p_ble_nus_c within my handler. If this pointer is pointing at the wrong array element (as I suggest), it will be before ble_nus_c_on_ble_evt() is called with p_context. I cannot see where ble_nus_c_on_ble_evt() is being called. I would look there.

    Thanks,

    Mark J

  • I don't think I understand the issue you describe here. Can it be translated to the unmodified ble_app_multilink_central? Is what you describe similar to led_status_send_to_all() in ble_app_multilink_central?

    What is xgw_ble_nus_c_evt_handler()? What sort of events does it handle? And what event occurs when you check the conn_handle?

    Is there a way for me to reproduce this? Perhaps you can zip the project folder and add some short descriptions on how to reproduce?

  • I will answer each of your questions below in hopes that the remaining issue will become clearer. Note that I now have a workaround to this issue (described above and below) so I am not stuck anymore, but the issue should be understood and fixed by Nordic.

    Diamonds"Can it be translated to the unmodified ble_app_multilink_central?"Diamonds

    There is no Nordic example showing multiple NUS client connections to peripherals. The ble_app_multilink_central example is the closest. Another DevZone post included a Nordic employee suggesting to a poster (that wanted to make multiple NUS client connections to peripherals) that the ble_app_multilink_central was the best reference. In this example, you can (effectively) substitute an array of NUS clients for LBS clients. For example, in ble_app_multilink_central  the array of LBS clients is defined with: BLE_LBS_C_ARRAY_DEF(m_lbs_c, NRF_SDH_BLE_CENTRAL_LINK_COUNT); Instead, I am using: BLE_NUS_C_ARRAY_DEF(m_ble_nus_c_arr, NRF_SDH_BLE_CENTRAL_LINK_COUNT); See this as line 2 in the first code snippet I provided yesterday within this thread.

    DiamondsIs what you describe similar to led_status_send_to_all() in ble_app_multilink_central?Diamonds

    led_status_send_to_all() sends messages to all connected LBS clients. With NUS, I am explicitly writing messages to each connected peripheral using the m_ble_nus_c_arr[] element pointer for each connected peripheral. This ble_nus_c_string_send() call works as expected. The correct peripherals are getting messages just fine. Code snipped showing an example of this:

    void xgw_ble_meta_read_cmd(uint8_t idx, uint8_t* pCmd)
    {
      // Pass through the first 2 bytes from the incoming cmd - changing length to ignore addr.
      uint8_t cmd_len = 2;
      uint8_t cmdBuf[cmd_len];
      cmdBuf[0] = pCmd[0];       // Cmd
      cmdBuf[1] = cmd_len;      // NEW len.
      ble_nus_c_string_send(&m_ble_nus_c_arr[idx], cmdBuf, cmd_len);
      
      return;
    }

    Diamonds"What is xgw_ble_nus_c_evt_handler()? What sort of events does it handle? And what event occurs when you check the conn_handle?"Diamonds

    NUS clients must register a handler to receive messages from connected peripherals. xgw_ble_nus_c_evt_handler() is that handler for my app. It is being registered with NUS using ble_nus_c_init(). See Infocenter info on this handler here:

    ble_nus_c_evt_handler_t Typedef

    This is shown within my second code snippet above. Please review that snippet. My handler (xgw_ble_nus_c_evt_handler()) is being called by NUS correctly when there is a NUS connection and the NUS service is discovered on a peripheral (BLE_NUS_C_EVT_DISCOVERY_COMPLETE event), NUS disconnection from a peripheral (BLE_NUS_C_EVT_DISCONNECTED event) or when data comes in from a peripheral (BLE_NUS_C_EVT_NUS_TX_EVT event).  These events are coming in just fine, the only issue that I am having is that when the handler is called for a BLE_NUS_C_EVT_NUS_TX_EVT event with its 2 parameters...

    p_ble_nus_c - should point at the NUS client within the array m_ble_nus_c_arr for the specific peripheral that sent the message generating the BLE_NUS_C_EVT_NUS_TX_EVT event. It does not point at the correct m_ble_nus_c_arr[] element. I have been looking for this array element match to understand which peripheral sent the message.

    p_evt - includes a conn_handle member (p_evt->conn_handle). See Infocenter info on this event here:

    ble_nus_c_evt_t Struct Reference

    This conn_handle should match the conn_handle within the m_ble_nus_c_arr [] element for the peripheral that sent the message. It did not match the correct m_ble_nus_c_arr[] elements conn_handle before your suggested code change (adding "ble_nus_c_evt.conn_handle = p_ble_evt->evt.gattc_evt.conn_handle;" to on_hvx()). After putting in the suggested code change I am now getting the correct conn_handle within p_evt. I am able to use this p_evt->conn_handle to match with a m_ble_nus_c_arr[] element - so now I an see which peripheral is the source of the message. This his how I am able to work around the issue of p_ble_nus_c pointing at the wrong m_ble_nus_c_arr[] element.

    DiamondsIs there a way for me to reproduce this?Diamonds

    Yes. See my comment above.

    "You should be able to easily recreate this issue by connecting to 2 peripherals with 2 NUS Clients, disconnecting the second and messaging with the first. Expect a response, but the p_ble_nus_c won't be pointing at the correct NUS client array element."

    If Nordic had a good example showing multiple NUS Client connections to multiple peripherals (and that example were tested with each SDK release) there would be no issue. I think that the remaining bug would have been found and fixed. It is strange that the ble_app_multilink_central uses LBS clients instead of NUS clients. I would have expected that customers would use NUS far more frequently than LBS with multiple central connections to peripherals.

    Thanks for your attention to this issue,

    Mark J

  • Ok, so if you have solved your issue, then I'll consider this one closed. I am not able to compile your implementation. There are too many undefined functions. Unless I have a zipped project folder that I can compile and understand what's going on, I can't really spend much more time on it. 

    I did once port the multilink example to support several connections, and I don't recall seeing this type of bug. 

    I am sorry if this is really obvious for you, but I can't understand the bug that you report. 

Related