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

Parents
  • 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?

Reply
  • 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?

Children
  • 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. 

  • As described above, I was using the handle to look up which NUS peripheral connection sent incoming messages to my central device. Further testing indicates that this is not working properly. When I have more than 1 connection, the NUS handler is not getting called with with a valid handle all of the time. 

    I have already invested too much time into getting multiple NUS connections to peripherals to work. I will address the issue, by pulling NUS code out of my central and peripheral devices and I will use my own service and charactoristics for BLE comms.

    Its disappointing that this NUS issue could not be resolved.

    Recreating the issue should be easy as I have described above. If you would like to review my entire central app, I can privately supply it to you. I am not sure how it is done. This won't be for my benefit as I am moving past NUS use, but you can work on the issue for others. Please advise if you would like access to my code and advise how this can be done privately.

    I will post back to this thread (for ref) after getting my own service and charactoristics up and running as accessing these on separate connections will still be important for anyone else that struggles with this issue.

    Mark J

  • I have successfully removed both NUS and Database Discovery modules from my central code. I am now just using the Scanning and GATT modules.The remaining modules EACH work for multiple connections whereas the removed modules where designed to have a separate module instance for each connection with cryptic macros defining an array of these instances.

    Without the modules, I can work directly with connection handles. Much cleaner. 

    After connecting to each peripheral, I had to use the older APIs to discover services, discover charactoristics and their descriptors. Message Sequence Charts gave me the clues needed and then I looked for and found examples of how to use the GATT Functions required.

    I am now able to connect/reconnect, write and get notifications with handles identifying their peripheral source device. This was the goal.

    I am having a final issue that I will open another thread for (cannot get central notification events fast enough from peripherals when those peripherals push data faster than 160 bytes every 125 mSec). I am confident that this relates to connection intervals and other connection parameters.

    To properly support NUS connections to multiple peripherals, a Nordic example (with associate QA) would be great. The philosophy seems to be that module use is preferred over lower-level APIs, but if there are issues with those modules, its sets users back much further than if they did not try to use the modules in the first place (my situation).

    My $0.02.

    Mark J

Related