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

Connection Parameter Selection for Central Device with 10 Peripheral Simultaneous Connections

Hi,

I am developing both Central and Peripheral devices with nRF52840. For consistency, both are using SDK 15.3.0 and S140 V7.

I am able to connect to peripherals and negotiate MTU ATT size of 247 and I am able to negotiate 2 Mbps phy. Logging on both peripheral and central devices confirm this is working.

My challenge is getting notification data from my peripherals to my central. If I set up (for example) 2 peripherals to notify my central device every 500 mSec with approx 160 bytes of data, all data comes into the BLE event handler (BLE_GATTC_EVT_HVX) within my central device just fine. If I double the data speed on peripherals (ie. 160 bytes every 250 mSec from each of the 2 devices), I only get BLE_GATTC_EVT_HVX events notifying me of incoming data from the first peripheral only.

I believe that I am not getting correct connection service intervals setup after peripheral connection.

For a scenario where a central is talking to 10 peripherals and getting notification data from each every 250 mSec, what would good connection interval, slave latency values be? I cannot seem to find a good reference for setup of connection intervals for multiple simultaneous peripheral connections to a central device - where each peripheral is notifying the central independently.

Note that my connection event length is 6 x 1.25 mSec or 7.5 mSec. This should be plenty long enough to get an MTU of 247 bytes transferred.

I have been using default min and max connection intervals of 7.5 and 30 mSec respectively and a slave latency of 0 (for both central and peripheral devices).

Suggestions for my scenario would be much appreciated.

Thanks in advance,

Mark J

Parents
  • Hello Mark,

    Sorry for the late reply. We are quite short staffed on support due to summer holidays. I am sorry, I didn't have time to reply before the weekend. Answering only your first post first:

    Are you sure that you are only receiving data from one peripheral connection? Perhaps you are spending too much time in the interrupt from the first device, so that the second device is not handling. I assume (based on your case history) that you are using the Nordic Uart Service (NUS). Do you print all the incoming data on the UART? What happens if you only try to leave the interrupt immediately, and just print something like: NRF_LOG_INFO("notification received from conn_handle %02x", conn_handle) whenever you receive data? Do you get both devices then?

  • Hi 

    As per my last entry: Increase Supported Links to Peripherals on a Central Device

    ... I took out NUS as it would not work in providing me a proper reference. I am now using direct connection handles and this is working better in that I always seem to get the correct reference to the connected peripheral sending me notification data.

    I am leaving the interrupt quickly and running my code on the main thread using a scheduler.  Here is my main loop:

     // Enter main loop.
      while (true)
      {
        // Proc USB events.
        while (app_usbd_event_queue_process()) { /* Nothing to do */  }
    
        // if(tempCnt++ >= 300000) { SEGGER_RTT_printf(0, "X\n"); tempCnt = 0;}
    
        // Handled scheduler actions.
        app_sched_execute();
    
        // Manage power.
        // nrf_pwr_mgmt_run(); 
      }

    By printing "x" I can see that my loop iterates < 10 uSec, so I am leaving the interrupt quickly and able to service the scheduler quickly on the main thread. I suspected that scheduling delay and handler execution time (ie. too long in a handler) may be the issue, but I am quite convinced it is related to the radio. More in my next reply below.

    IMPORTANTLY - I am not interested in scanning when transferring data. I am only scanning to setup connections. I scan, stop scanning, connect to my 10 peripherals and then I request that they notify me every 250 mSec. I have to setup a ble_gap_scan_params_t* to pass to sd_ble_gap_connect() - when connecting to each peripheral. I have been using the same scan interval and window within this ble_gap_scan_params_t object. Maybe that is my issue? If I want no scanning after connection, can I set this ble_gap_scan_params_t* to null or some other value that drastically or totally eliminates scanning after connections?

    Regards,

    Mark J 

  • Hi ,

    I am still porting code to ble_app_uart (peripheral) and ble_app_uart_c (central) pca10040 projects (ie replacing the code in this projects that is setup for pca10040 with my code). I am about 2/3 finished and will have working projects that you can run on nrf52 Dev Kits tomorrow. 

    When moving my code to these test projects, I noticed something that might by an issue. I have been assuming that the data processed within my central when each BLE_GATTC_EVT_HVX  event occurs is a single packet with length of 243 bytes or less. 

    case BLE_GATTC_EVT_HVX:
    {
      // Setup reusable vars.
      ble_gattc_evt_t const * p_gatt_evt = &p_ble_evt->evt.gattc_evt;
    
      // Process p_gatt_evt->params.hvx.data with length p_gatt_evt->params.hvx.len
      // Assume that processing is for a single BLE packet with length < max MTU size.
    
      break;
    }

    I have assumed that there will be a BLE_GATTC_EVT_HVX event generated for each notification that I send. Today I noticed that the p_gatt_evt->params.hvx.len is a uint16_t. If, on my peripheral, I call sd_ble_gatts_hvx() twice in rapid succession, each time with a hvx_params.p_len of 240 bytes, can I get a SINGLE event at my central (BLE_GATTC_EVT_HVX) that has a length of 480? This could explain my issue as I am casting p_gatt_evt->params.hvx.len to a uint8_t assuming it would always be 240 bytes or less. I could be getting more than 1 packet in a single BLE_GATTC_EVT_HVX event with a p_gatt_evt->params.hvx.len that is greater than 255. When this happens, I would ignore the data beyond 255 bytes.

    Please advise if multiple received packets can be consolidated into a single BLE_GATTC_EVT_HVX event.

    Thanks,

    Mark J

  • I assumed it would generate the same number of events as the number of sd_ble_gatts_hvx(). But you should check the

    p_ble_evt->evt.gattc_evt.params.hvx.len

    I suppose it may be that they are merged if they originate from one notification. (a notification may be larger than the MTU, I think). 

    BR,

    Edvin

  • Given the importance of understanding if the sd_ble_gatts_hvx() call supports more than 1 packet and understanding if multiple packets could result in a single BLE_GATTC_EVT_HVX event, I checked S140 sd_ble_gatts_hvx() Details and BLE_GATTC_EVT_HVX Details. These descriptions seem pretty clear. Only 1 packet can be sent with sd_ble_gatts_hvx() and BLE_GATTC_EVT_HVX results when each packet is received (not multiple packets). Given that this is critically important - I would suggest that it be confirmed (ie the docs could be wrong or I may misunderstand them).

    Thanks,

    Mark J

  • I have created a peripheral and central app that - sort of shows the issue. First, the apps.

    I have simply updated the ble_app_uart peripheral example under my SDK folder: nRF5_SDK_15.3.0_59ac345\examples\ble_peripheral\ble_app_uart. Zip here.

    8422.ble_app_uart.zip

    This exposes my service for connection and when the central app connects to it and sends a simple command (automatically) it notifies the central with 240 bytes (rotating byte pattern) every 25 mSec. It stops automatically after 401 notifications (10 sec).

    I have also updated the ble_app_uart_c central example under my SDK folder: nRF5_SDK_15.3.0_59ac345\examples\ble_central\ble_app_uart_c. Zip here:

    7848.ble_app_uart_c.zip

    This central scans for the peripheral and automatically connects to it, queries services and registers for notifications. After this, it sends a simple 0xAA, 0xBB command to the peripheral - which starts the peripheral sending notifications (for 10 sec).

    NRF_SDH_BLE_GAP_EVENT_LENGTH is set to 8 (10 mSec) for both peripheral and central and an MTU and data length is negotiated upon connection that is larger than the required 240 bytes (on both peripheral and central). Data extension should be enabled on both as well (no error response).

    If you set a notification interval on both peripheral and central by setting min and max (equal) of 80 mSec or greater, you will see that some notifications are missed (i.e. you won't get the 401 notifications expected at the central). On the peripheral, sd_ble_gatts_hvx() will return NRF_ERROR_RESOURCES on occasion when it cannot send a notification. If you use a notification interval of 70 sec or less, you will get all 401 notifications at the central.

    The issue in my app that I am trying to recreate in these examples is similar - but not identical. I get the same NRF_ERROR_RESOURCES response from sd_ble_gatts_hvx() on my peripherals when they cannot send notifications, but I am seeing this issue even when I use connection interval of 25 mSec or greater. In my app, I must keep the connection interval below 25 mSec and in examples, it must be kept below 70 or 75 mSec. I need to support a connection interval of 50 mSec or greater in my app to be able to service 4 connections with event length of 10 mSec.

    My app (peripheral and central) NRF_SDH_BLE_GAP_EVENT_LENGTH, MTU and data length match the attached examples. The biggest difference between the attached ble_app_uart example and my peripheral app is that my app also interacts with an accelerometer over spi to get sample data. The attached example simply transfers stored memory with fake data.

    I suspect the attached example fails whenever the connection interval is more than 3x the notification period of 25 mSec (i.e. it can only queue 3 notifications). Maybe this queue in my app is only 1 and that is why the connection interval in my app must be less than 25 mSec?

    If not related to notification queuing, the issue may simply relate to the fact that I am using more processor cycles to interact with an accelerometer.

    To run the examples, just place them under SDK 15.3 "examples" folders shown above, compile them and then run them in debug. Just start the ble_app_uart example and then the ble_app_uart_c example and watch the debug output from both.

    Regards,

    Mark J

  • I suspect that I misunderstand something, but check this out. Do I need to change anything to replicate the issue? I am a bit confused about your description of the timers, and how you queue up the notifications. 

    I modified only the main files slightly by adding a counter and adding some logging. 

    /**
     * Copyright (c) 2014 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    /** @file
     *
     * @defgroup ble_sdk_uart_over_ble_main main.c
     * @{
     * @ingroup  ble_sdk_app_nus_eval
     * @brief    UART over BLE application main file.
     *
     * This file contains the source code for a sample application that uses the Nordic UART service.
     * This application uses the @ref srvlib_conn_params module.
     */
    
    
    #include <stdint.h>
    #include <string.h>
    #include "nordic_common.h"
    #include "nrf.h"
    #include "ble_hci.h"
    #include "ble_advdata.h"
    #include "ble_advertising.h"
    #include "ble_conn_params.h"
    #include "nrf_sdh.h"
    #include "nrf_sdh_soc.h"
    #include "nrf_sdh_ble.h"
    #include "nrf_ble_gatt.h"
    #include "nrf_ble_qwr.h"
    #include "ble_dis.h"
    #include "app_timer.h"
    #include "ble_nus.h"
    #include "app_uart.h"
    #include "app_util_platform.h"
    #include "bsp_btn_ble.h"
    #include "nrf_pwr_mgmt.h"
    
    #include "nrf_drv_clock.h"
    #include "app_scheduler.h"
    #include "mem_manager.h"
    
    #include "SEGGER_RTT.h"
    
    #define DEVICE_NAME                     "xTag"                                  // As Friendlyname and other BLE services.
    #define DEVICE_NAME_SUFFIX_LEN          14                                      // Extra space after for DEVICE_NAME + " xxxxxxxxxxxx" with \0 at end.
    #define MANUFACTURER_NAME               "Deviceworx"                            // For BLE services.
    #define APP_ADV_INTERVAL                800                                     // Mult of 0.625 ms. Use 1600 for 1 sec.
    #define APP_BLE_OBSERVER_PRIO           3                                       // Default
    #define APP_BLE_CONN_CFG_TAG            1                                       // SoftDevice BLE configuration.
    
    #define MIN_CONN_INTERVAL               MSEC_TO_UNITS(80, UNIT_1_25_MS)             /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
    #define MAX_CONN_INTERVAL               MSEC_TO_UNITS(80, UNIT_1_25_MS)             /**< Maximum acceptable connection interval (75 ms), Connection interval uses 1.25 ms units. */
    #define SLAVE_LATENCY                   0                                           /**< Slave latency. */
    #define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(4000, UNIT_10_MS)             /**< Connection supervisory timeout (4 seconds), Supervision Timeout uses 10 ms units. */
    #define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000)                       /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
    #define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000)                      /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
    #define MAX_CONN_PARAMS_UPDATE_COUNT    3                                           /**< Number of attempts before giving up the connection parameter negotiation. */
    
    #define DEAD_BEEF                       0xDEADBEEF                                  /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */
    
    #define BLE_MAX_TX_SVC_BYTES                244
    #define BLE_MAX_RX_SVC_BYTES                8
    
    
    // GUID gen for xTag online - 10c66c52-e5c9-4fe7-8a04-e40b13eb932c
    #define BLE_UUID_XTAG_BASE_UUID         {0x10, 0xC6, 0x6C, 0x52, 0xE5, 0xC9, 0x4F, 0xE7, 0x8A, 0x04, 0xE4, 0x0B, 0x13, 0xEB, 0x93, 0x2C}
    #define BLE_UUID_XTAG_SERVICE           0x0001 // 
    
    #define BLE_UUID_XTAG_RX_CHARACTERISTC_UUID          0x0002
    #define BLE_UUID_XTAG_TX_CHARACTERISTC_UUID          0x0003
    
    // Scheduler settings
    #define SCHED_MAX_EVENT_DATA_SIZE   sizeof(ble_nus_evt_t)
    #define SCHED_QUEUE_SIZE            5
    
    #define MFR_PAYLOAD_SIZE_GAP  14
    
    volatile uint16_t m_num_sent_packets = 0;
    
    typedef struct
    {
        uint16_t conn_handle;                     // Handle of the current connection (as provided by the BLE stack, is BLE_CONN_HANDLE_INVALID if not in a connection).
        uint16_t service_handle;                  // Handle of Our Service (as provided by the BLE stack). 
        ble_gatts_char_handles_t rx_char_handles; // Hnndles for the rx char.
        ble_gatts_char_handles_t tx_char_handles; // Hnndles for the tx char.
    }ble_os_t;
    
    NRF_BLE_GATT_DEF(m_gatt);                                                 // GATT module instance.
    
    //static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID;                  // Handle of the current connection..
    ble_os_t m_xtag_service;         
    
    static bool m_notif_busy = false;     // Notification out when true - must wait for client confirmation.
    
    // UUIDs for standard services here.
    static ble_uuid_t m_adv_uuids[] =                       
    {
      {BLE_UUID_DEVICE_INFORMATION_SERVICE, BLE_UUID_TYPE_BLE},
      {BLE_UUID_XTAG_SERVICE, BLE_UUID_TYPE_BLE }
    };
    
    static uint8_t rx_char_data[BLE_MAX_RX_SVC_BYTES];
    
    ble_gap_adv_params_t m_adv_params;                                        // Parameters to be passed to the stack when starting advertising.
    uint8_t              m_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET;       // Advertising handle used to identify an advertising set.
    uint8_t              m_enc_advdata1[BLE_GAP_ADV_SET_DATA_SIZE_MAX];       // 31 Byte Buffer for storing an encoded advertising set.
    uint8_t              m_enc_advdata2[BLE_GAP_ADV_SET_DATA_SIZE_MAX];       // 31 Byte Buffer for storing an encoded advertising set.
    uint8_t              m_enc_scanrespdata1[BLE_GAP_ADV_SET_DATA_SIZE_MAX];  // Buffer for storing an encoded advertising set.
    uint8_t              m_enc_scanrespdata2[BLE_GAP_ADV_SET_DATA_SIZE_MAX];  // Buffer for storing an encoded advertising set.
    
    
    // Setup 2 buffers for advertising and switching on the fly.
    // Ref here: https://devzone.nordicsemi.com/f/nordic-q-a/32824/dynamically-updating-advertising-data-in-sdk-15
    uint8_t m_bufIdx = 0;
    ble_gap_adv_data_t m_gapData[2] = 
    {
      { 
        .adv_data.p_data = m_enc_advdata1,
        .adv_data.len = sizeof(m_enc_advdata1),
        .scan_rsp_data.p_data = m_enc_scanrespdata1,
        .scan_rsp_data.len = sizeof(m_enc_scanrespdata1)
      },
      { 
        .adv_data.p_data = m_enc_advdata2,
        .adv_data.len = sizeof(m_enc_advdata2),
        .scan_rsp_data.p_data = m_enc_scanrespdata2,
        .scan_rsp_data.len = sizeof(m_enc_scanrespdata2)
      },
    };                                         // As per egs, we need a svc struct.
    
    APP_TIMER_DEF(m_100_msec_timer_id);                         // Misc stuff every 100 mSec.
    static uint16_t m_100_msec_60sec_cnt = 0;                   // Used to inc the min ctr.
    static uint16_t m_100_msec_10sec_cnt = 0;                   // Used to restart adverts.
    APP_TIMER_DEF(m_25_msec_timer_id);                          // Acq data collected every 15 mSec.
    
    static uint8_t* m_p_ramstore = NULL;    // Datastore for fake 240 bytes. Store values of 0 - 240 (0xF0) and rotate this data with each send.
    
    static uint8_t m_pendingNotifications = 0;
    static uint16_t m_handlerCount = 0;
    
    void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name) { app_error_handler(DEAD_BEEF, line_num, p_file_name); }
    
    void set_notif_busy(bool _busy) { m_notif_busy = _busy; }
    bool is_notif_busy(void) { return m_notif_busy; }
    
    void on_main_shed_rx_char_evt_handler(void *p_event_data, uint16_t event_size)
    {
      SEGGER_RTT_printf(0, "Incomming bytes length %d - %02X %02X ... Starting notifications.\n", event_size, ((uint8_t*) p_event_data)[0], ((uint8_t*) p_event_data)[1]);
    
      // Nothing too fancy. Anything in - start sending notifications to the central from a timer handler.
      // 25 mSec period is how often we send 40 samples at a rate of 1600 per sec.
      APP_ERROR_CHECK(app_timer_start(m_25_msec_timer_id, APP_TIMER_TICKS(25), NULL));
    
      // We will only handle x notifications.
      m_handlerCount = 0;
    }
    
    void on_main_shed_data_notification_handler(void *p_event_data, uint16_t event_size)
    {
      uint32_t err_code;
      uint8_t tempStore = 0;
      uint16_t pcktLen = 240;
      ble_gatts_hvx_params_t hvx_params;
    
      // Only process 400 times.
      if(m_handlerCount++ > 400)
      {
        SEGGER_RTT_printf(0, "Stopping notifications.\n"); 
    
        APP_ERROR_CHECK(app_timer_stop(m_25_msec_timer_id));
    
        return;
      }
    
      // New notification required whenever this handler is called.
      m_pendingNotifications++;
    
      // If we are still busy sending the last packet, cannot continue. m_pendingNotifications won't get reduced.
      // See BLE_GATTS_EVT_HVN_TX_COMPLETE event handling where notification busy flag is cleared.
      if(is_notif_busy())
      {
        return;
      }
    
      // Loop through pending notifications. We can send more than 1 every 15 mSec if deta length extensions are working.
      while((m_pendingNotifications>0) && (is_notif_busy() == false))
      {
        // Send the data.
        memset(&hvx_params, 0, sizeof(hvx_params));
        hvx_params.handle = m_xtag_service.tx_char_handles.value_handle;
        hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
        hvx_params.offset = 0;
        hvx_params.p_len  = &pcktLen;
        hvx_params.p_data = &(m_p_ramstore[0]);  
    
        err_code = sd_ble_gatts_hvx(m_xtag_service.conn_handle, &hvx_params);                       
        if (err_code == NRF_SUCCESS)
        {
            m_num_sent_packets++;
            SEGGER_RTT_printf(0, "num sent %d\n", m_num_sent_packets);
        }
        if ( (err_code == NRF_ERROR_RESOURCES) || (err_code == NRF_ERROR_BUSY) ) // Notification pending.
        {
          // Indicate that we are busy until a BLE_GATTS_EVT_HVN_TX_COMPLETE comes in.
          set_notif_busy(true);
    
          // DEBUG msgs.
          if(err_code == NRF_ERROR_BUSY) { SEGGER_RTT_printf(0, "Notification busy\n"); }
          else { SEGGER_RTT_printf(0, "Notification resource busy\n"); }
        }
        else if(err_code != NRF_SUCCESS)
        {
            SEGGER_RTT_printf(0, "Notification error: %d\n", err_code);
    
            // Some errors handled externally.
            return;
        }
    
        // Reduce m_pendingNotifications as we just handled one.
        m_pendingNotifications--;
    
        // Rotate the data by shuffling it up by 1 byte (Data at Idx 1 to Idx 0, etc). Data at Idx 0 to Idx 239.
        uint8_t tempStore = m_p_ramstore[0];
        for(int x=0; x<239; x++) { m_p_ramstore[x] = m_p_ramstore[x+1]; }
        m_p_ramstore[239] = tempStore;
    
      } // while()
    }
    
    static void timer_handler_100_msec(void* p_context)
    {
      if(++m_100_msec_10sec_cnt >= 100)
      {
        // Restart advertising.
        sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
    
        // Reset every cycle.
        m_100_msec_10sec_cnt = 0;
      }
    
      // Inc the min counter and handle periodic stuff.
      if(++m_100_msec_60sec_cnt >= 600)
      { 
        // Reset every min.
        m_100_msec_60sec_cnt = 0;
    
        SEGGER_RTT_printf(0, "Minute with mSec count %d.\n", (app_timer_cnt_get()/33));
      }
    
      return;
    }
    
    // Handler that emulates acquired data from a sensor. 25 samples (6 bytes each) every 15 mSec closely approx 1600 samples/sec/
    static void timer_handler_25_msec(void* p_context)
    {
      // Get out of handler quick. Schedule handler that runs on main thread.
      app_sched_event_put(NULL,  0, on_main_shed_data_notification_handler);
    
      return;
    }
    
    /**@brief Function for initializing the timer module.
     */
    static void timers_init(void)
    {
      // Initialize timer module.
      APP_ERROR_CHECK(app_timer_init());
    
      // Create the timer.
      APP_ERROR_CHECK(app_timer_create(&m_100_msec_timer_id, APP_TIMER_MODE_REPEATED, timer_handler_100_msec));
    
      APP_ERROR_CHECK(app_timer_create(&m_25_msec_timer_id, APP_TIMER_MODE_REPEATED, timer_handler_25_msec));
    }
    
    // Handle BLE events.
    static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
    {
      ret_code_t err_code = NRF_SUCCESS;
    
      ble_gap_phys_t phys;
      ble_gap_evt_phy_update_t const* p_phy_evt;
      ble_gap_evt_data_length_update_t const* p_datalen_evt;
    
      // SEGGER_RTT_printf(0, "Bluetooth Evt 0x%02X.\n", p_ble_evt->header.evt_id);
    
      switch (p_ble_evt->header.evt_id)
      {
        case BLE_GAP_EVT_ADV_SET_TERMINATED:
    
          //SEGGER_RTT_printf(0, "Adv Terminated.\n");
    
          break;
    
        case BLE_GAP_EVT_DISCONNECTED:
    
          SEGGER_RTT_printf(0, "Disconnect Evt.\n");
    
          m_xtag_service.conn_handle = BLE_CONN_HANDLE_INVALID;
    
          // xtag_set_device_state(DEV_STATE_DISCONN); // Update state.
    
          break;
    
        case BLE_GAP_EVT_CONNECTED:
    
          //NRF_LOG_INFO("Connected.");
          SEGGER_RTT_printf(0, "Connect Evt.\n");
          m_xtag_service.conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
    
          // Log the effective mtu.
          SEGGER_RTT_printf(0, "Effective MTU after connection is: %d bytes\n", 
            nrf_ble_gatt_eff_mtu_get(&m_gatt, p_ble_evt->evt.gap_evt.conn_handle));
    
          // Required to work around nordic issue wherin adv stops upon connection. Suggested by Nordic.
          sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
    
          break;
    
        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        
          SEGGER_RTT_printf(0, "Phy update request from central rx %d tx %d.\n", 
            p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys.rx_phys,
            p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys.tx_phys);
    
          // Handle the request that has been initiated in the server and accept the requested rx and tx phys.
          err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys);
          APP_ERROR_CHECK(err_code);
    
          break;
    
        case BLE_GAP_EVT_PHY_UPDATE: // Log only for debugging.
    
          p_phy_evt = &p_ble_evt->evt.gap_evt.params.phy_update;
    
          if (p_phy_evt->status == BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION)
          {
            SEGGER_RTT_printf(0, "Phy update: Trans collision during updated.\n");
            break;
          }
    
          if(p_phy_evt->status == BLE_HCI_STATUS_CODE_SUCCESS)
          {
            SEGGER_RTT_printf(0, "Phy updated: Tx phy: 0x%02X Rx phy: 0x%02X.\n", p_phy_evt->tx_phy, p_phy_evt->rx_phy);
          }
          else
          {
            if(p_phy_evt->status == BLE_HCI_DIFFERENT_TRANSACTION_COLLISION) // Common.
            {
              SEGGER_RTT_printf(0, "Phy update failed due to transaction collision.\n");
            }
            else { SEGGER_RTT_printf(0, "Phy update failed. Status: 0x%02X.\n", p_phy_evt->status); }
          }
    
          break;
    
        case BLE_GAP_EVT_DATA_LENGTH_UPDATE:
    
          p_datalen_evt = &p_ble_evt->evt.gap_evt.params.data_length_update;
    
          SEGGER_RTT_printf(0, "Data length (PDU) update. Max Tx octets: %d, Max Rx octets %d, Max Tx uSec: %d, Max Rx uSec: %d\n",
            p_datalen_evt->effective_params.max_tx_octets, p_datalen_evt->effective_params.max_rx_octets, 
            p_datalen_evt->effective_params.max_tx_time_us, p_datalen_evt->effective_params.max_rx_time_us);
    
        case BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST:
    
          SEGGER_RTT_printf(0, "GATT MTU Request In.\n");
    
          break;
    
        case BLE_GATTS_EVT_HVN_TX_COMPLETE:
    
          // Ensure that our notification busy status is updated to indicate idle and another notification is ok.
          set_notif_busy(false);
    
          // DEBUG
          //SEGGER_RTT_printf(0, "Notification complete.\n");
          
          break;
    
        case BLE_GATTC_EVT_TIMEOUT:
          // Disconnect on GATT Client timeout event.
          SEGGER_RTT_printf(0, "GATT Client Timeout.");
          err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                           BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GATTS_EVT_TIMEOUT:
          // Disconnect on GATT Server timeout event.
          SEGGER_RTT_printf(0, "GATT Server Timeout.");
          err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                           BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GATTS_EVT_WRITE:
        {
          // Incoming data over the rx char.
           ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
    
          // Ensure that we do not overun our buffer.
          if(p_evt_write->len > BLE_MAX_RX_SVC_BYTES)
          {
            SEGGER_RTT_printf(0, "GATT message in %d bytes when max is %d\n", p_evt_write->len, BLE_MAX_RX_SVC_BYTES);
            break;
          }
    
          // Handle notification enable/disable p_evt_write->handle == p_nus->tx_handles.cccd_handle
          if(p_evt_write->handle == m_xtag_service.tx_char_handles.cccd_handle)
          {
            if(p_evt_write->data[0] == 0x01) { SEGGER_RTT_printf(0, "Notifications on tx enabled.\n"); }
            else { SEGGER_RTT_printf(0, "Notifications on tx disabled.\n"); }
    
            break;
          }
    
          // Get the incomming cmd bytes.
          memcpy(rx_char_data, p_evt_write->data, p_evt_write->len);
    
          // Schedule handling on main() via scheduler.
          app_sched_event_put(rx_char_data,  p_evt_write->len, on_main_shed_rx_char_evt_handler);
    
          break;
        }
    
        default:
          // No implementation needed.
          break;
      }
    }
    
    void ble_stack_init(void)
    {
      ret_code_t err_code;
    
      err_code = nrf_sdh_enable_request();
      APP_ERROR_CHECK(err_code);
    
      // Configure the BLE stack using the default settings.
      uint32_t ram_start = 0;
      err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
      APP_ERROR_CHECK(err_code);
    
      // Enable BLE stack. To see required RAM config changes, enable RTT within nrf_sdh_ble.c 
      // (where nrf_sdh_ble_enable() is implemented). Uncomment #include "SEGGER_RTT.h" and logs in nrf_sdh_ble_enable(). 
      err_code = nrf_sdh_ble_enable(&ram_start);
      APP_ERROR_CHECK(err_code);
    
      // Register a handler for BLE events.
      NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
    }
    
    // Gap params init from egs.
    void gap_params_init(void)
    {
      ret_code_t              err_code;
      ble_opt_t               ble_opt;
      ble_gap_addr_t cur_gap_addr;
      ble_gap_conn_params_t   gap_conn_params;
      ble_gap_conn_sec_mode_t sec_mode;
      uint8_t id[BLE_GAP_ADDR_LEN];
    
      // Add extended connection event support to send multiple packets per event.
      memset(&ble_opt, 0x00, sizeof(ble_opt));
      ble_opt.common_opt.conn_evt_ext.enable = 1;
      err_code = sd_ble_opt_set(BLE_COMMON_OPT_CONN_EVT_EXT, &ble_opt);
      APP_ERROR_CHECK(err_code);
    
      // From egs.
      BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
    
      // Get the address from the device.
      err_code = sd_ble_gap_addr_get(&cur_gap_addr);
      APP_ERROR_CHECK(err_code);
    
      // Save off the MAC format as our ID (local and in xtag_data).
      memcpy(id, cur_gap_addr.addr, BLE_GAP_ADDR_LEN);
    
      // Save back ensuring type is public.
      cur_gap_addr.addr_type = BLE_GAP_ADDR_TYPE_PUBLIC;
      sd_ble_gap_addr_set(&cur_gap_addr);
      APP_ERROR_CHECK(err_code);
    
      // Setup and use a device name that includes the ID suffix bytes.
      char name_w_id[strlen(DEVICE_NAME) + DEVICE_NAME_SUFFIX_LEN];
      memset(name_w_id, 0, sizeof(name_w_id));
      sprintf(name_w_id, "%s %02X%02X%02X%02X%02X%02X", DEVICE_NAME, id[5], id[4], id[3], id[2], id[1], id[0]);
      err_code = sd_ble_gap_device_name_set(&sec_mode, (const uint8_t *)name_w_id, strlen(name_w_id));
      APP_ERROR_CHECK(err_code);
    
      // We will expose ourselves as a generic "tag".
      err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_GENERIC_TAG);
      APP_ERROR_CHECK(err_code);
    
      memset(&gap_conn_params, 0, sizeof(gap_conn_params));
    
      gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
      gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
      gap_conn_params.slave_latency     = SLAVE_LATENCY;
      gap_conn_params.conn_sup_timeout  = CONN_SUP_TIMEOUT;
    
      err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
      APP_ERROR_CHECK(err_code);
    }
    
    // Function for handling events from the GATT MODULE. See ble for other GATT events.
    static void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
    {
      switch (p_evt->evt_id)
      {
        case NRF_BLE_GATT_EVT_ATT_MTU_UPDATED:
    
          SEGGER_RTT_printf(0, "ATT MTU updated from central after connection. MTU is %u bytes.\n", 
            p_evt->params.att_mtu_effective);
    
          break;
    
        case NRF_BLE_GATT_EVT_DATA_LENGTH_UPDATED:
    
          SEGGER_RTT_printf(0, "Data length updated from central after connection to %u bytes.\n",
            p_evt->params.data_length);
    
          break;
    
        default:  // Ignore the rest.
    
          break;
      }
    }
    
    /// Init GATT
    void gatt_init(void)
    {
      ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
      APP_ERROR_CHECK(err_code);
    }
    
    // Init services.
    void services_init(ble_os_t * p_xtag_service)
    {
      uint32_t      err_code;
    
      // Setup our custom service for comms.
      ble_uuid_t service_uuid;
    
      // Declare 16-bit service and 128-bit base UUIDs and add them to the BLE stack
      ble_uuid128_t base_uuid = BLE_UUID_XTAG_BASE_UUID;
      service_uuid.uuid = BLE_UUID_XTAG_SERVICE;
      err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);
      APP_ERROR_CHECK(err_code);
    
      // Setup the default handle as per egs.
      p_xtag_service->conn_handle = BLE_CONN_HANDLE_INVALID;
    
      // Add our service
      err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                          &service_uuid,
                                          &p_xtag_service->service_handle);
      APP_ERROR_CHECK(err_code);
    
      SEGGER_RTT_printf(0, "Executing our_service_init().\n"); // Print message to RTT to the application flow
      SEGGER_RTT_printf(0, "Service UUID: 0x%#04x\n", service_uuid.uuid); // Print service UUID should match definition BLE_UUID_OUR_SERVICE
      SEGGER_RTT_printf(0, "Service UUID type: 0x%#02x\n", service_uuid.type); // Print UUID type. Should match BLE_UUID_TYPE_VENDOR_BEGIN. Search for BLE_UUID_TYPES in ble_types.h for more info
      SEGGER_RTT_printf(0, "Service handle: 0x%#04x\n", p_xtag_service->service_handle); // Print out the service handle. Should match service handle shown in MCP under Attribute values
    
      // Add the RX Characteristic. This is taken directly from ble_nus.c to match NUS.
      ble_add_char_params_t add_char_params;
      memset(&add_char_params, 0, sizeof(add_char_params));
      add_char_params.uuid                     = BLE_UUID_XTAG_RX_CHARACTERISTC_UUID;
      add_char_params.uuid_type                = service_uuid.type;
      add_char_params.max_len                  = BLE_MAX_RX_SVC_BYTES;
      add_char_params.init_len                 = sizeof(uint8_t);
      add_char_params.is_var_len               = true;
      add_char_params.char_props.write         = 1;
      add_char_params.char_props.write_wo_resp = 1;
    
      add_char_params.read_access  = SEC_OPEN;
      add_char_params.write_access = SEC_OPEN;
    
      characteristic_add(p_xtag_service->service_handle, &add_char_params, &p_xtag_service->rx_char_handles);
    
      // Add the TX Characteristic. This is taken directly from ble_nus.c to match NUS.
      memset(&add_char_params, 0, sizeof(add_char_params));
      add_char_params.uuid              = BLE_UUID_XTAG_TX_CHARACTERISTC_UUID;
      add_char_params.uuid_type         = service_uuid.type;
      add_char_params.max_len           = BLE_MAX_TX_SVC_BYTES;
      add_char_params.init_len          = sizeof(uint8_t);
      add_char_params.is_var_len        = true;
      add_char_params.char_props.notify = 1;
    
      add_char_params.read_access       = SEC_OPEN;
      add_char_params.write_access      = SEC_OPEN;
      add_char_params.cccd_write_access = SEC_OPEN;
    
      characteristic_add(p_xtag_service->service_handle, &add_char_params, &p_xtag_service->tx_char_handles);
    
      // To support Dev Info Service Profile details.
      ble_dis_init_t dis_init;
    
      // Initialize Device Information Service.
      memset(&dis_init, 0, sizeof(dis_init));
    
      // Setup DIS attributes that we care about. Others null and won't be shown to clients.
      ble_srv_ascii_to_utf8(&dis_init.manufact_name_str, "Deviceworx");
      ble_srv_ascii_to_utf8(&dis_init.model_num_str, "xTAG-BLE-A");
      ble_srv_ascii_to_utf8(&dis_init.hw_rev_str, "1.00.01");
      ble_srv_ascii_to_utf8(&dis_init.fw_rev_str, "1.03.04");
    
      dis_init.dis_char_rd_sec = SEC_OPEN;
    
      err_code = ble_dis_init(&dis_init);
      APP_ERROR_CHECK(err_code);
    
      
    }
    
    // Connection parameters.
    static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
    {
      ret_code_t err_code;
    
      if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
      {
        err_code = sd_ble_gap_disconnect(m_xtag_service.conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
        APP_ERROR_CHECK(err_code);
      }
    }
    
    // Connection parameters error hander.
    static void conn_params_error_handler(uint32_t nrf_error) { APP_ERROR_HANDLER(nrf_error); }
    
    void conn_params_init(void)
    {
      ret_code_t             err_code;
      ble_conn_params_init_t cp_init;
    
      memset(&cp_init, 0, sizeof(cp_init));
    
      cp_init.p_conn_params                  = NULL;
      cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
      cp_init.next_conn_params_update_delay  = NEXT_CONN_PARAMS_UPDATE_DELAY;
      cp_init.max_conn_params_update_count   = MAX_CONN_PARAMS_UPDATE_COUNT;
      cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;
      cp_init.disconnect_on_fail             = false;
      cp_init.evt_handler                    = on_conn_params_evt;
      cp_init.error_handler                  = conn_params_error_handler;
    
      err_code = ble_conn_params_init(&cp_init);
      APP_ERROR_CHECK(err_code);
    }
    
    // Config advertising.
    void advertising_config(void)
    {
      ret_code_t             err_code;
    
      // Setup var to hold ble_advdata_t advert data. 
      ble_advdata_t advdata;
      memset(&advdata, 0, sizeof(advdata));
    
      // Setup var to hold ble_advdata_t scan resp data. 
      ble_advdata_t srdata;
      memset(&srdata, 0, sizeof(srdata));
    
      // Just use srdata to indicate that we want to expose the name provided in gap_params_init().
      srdata.name_type = BLE_ADVDATA_FULL_NAME;
      
      // Setup variable to hold manufacturer specific advert data
      ble_advdata_manuf_data_t mfr_advdata; 
      memset(&mfr_advdata, 0, sizeof(mfr_advdata));
    
      // Update mfr_data and add to advdata.
      uint8_t data[MFR_PAYLOAD_SIZE_GAP];
      for(int x=1; x<MFR_PAYLOAD_SIZE_GAP; x++) { data[x]=x; } // For nordic troubleshooting we don't care about adv content.
    
      mfr_advdata.company_identifier =  0x030E; //Dworx company ID
      mfr_advdata.data.p_data = data;
      mfr_advdata.data.size = sizeof(data);
      advdata.name_type = BLE_ADVDATA_NO_NAME;
      advdata.p_manuf_specific_data = &mfr_advdata;
    
      advdata.include_appearance      = true;
      advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
      advdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
      advdata.uuids_complete.p_uuids  = m_adv_uuids;
    
      // Initialize advertising parameters (used when starting advertising).
      memset(&m_adv_params, 0, sizeof(m_adv_params));
    
      m_adv_params.properties.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED;
      m_adv_params.p_peer_addr     = NULL;    // Undirected advertisement.
      m_adv_params.filter_policy   = BLE_GAP_ADV_FP_ANY;
      m_adv_params.interval        = APP_ADV_INTERVAL;
      m_adv_params.duration        = 950;       // 10 mSec counts - 100 for each sec. Timer restarts every 10 sec, so use 9.5 sec.
    
      // Encode advdata to m_gapData[m_bufIdx]
      err_code = ble_advdata_encode(&advdata, m_gapData[m_bufIdx].adv_data.p_data, (uint16_t *) &m_gapData[m_bufIdx].adv_data.len);
      APP_ERROR_CHECK(err_code);
    
      // Encode srdata to m_gapData[m_bufIdx]
      err_code = ble_advdata_encode(&srdata, m_gapData[m_bufIdx].scan_rsp_data.p_data, (uint16_t *) &m_gapData[m_bufIdx].scan_rsp_data.len);
      APP_ERROR_CHECK(err_code);
     
      err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_gapData[m_bufIdx], &m_adv_params);
      APP_ERROR_CHECK(err_code);
    
      err_code = sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV,m_adv_handle, 4); 
      APP_ERROR_CHECK(err_code);
    }
    
    /**@brief Application main function.
     */
    int main(void)
    {
        bool erase_bonds;
    
        SEGGER_RTT_Init();
        SEGGER_RTT_printf(0, "Peripheral Logging started.\n");
    
        // As per egs, setup the clock driver to use the lfclk (before timers_init)
        APP_ERROR_CHECK(nrf_drv_clock_init());
        nrf_drv_clock_lfclk_request(NULL);
      
        // Additional Inits.
        APP_SCHED_INIT(SCHED_MAX_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE);
        timers_init();
        APP_ERROR_CHECK(nrf_pwr_mgmt_init());
        APP_ERROR_CHECK(nrf_mem_init());
    
        // Alloc RAM.
        m_p_ramstore = (uint8_t*) nrf_malloc(240);
    
        // Setup a pattern of 0 - 239 in the store.
        for(int x=0; x<240; x++) { m_p_ramstore[x] = x; }
    
        // More inits.
        ble_stack_init();
        gap_params_init();
        gatt_init();
        services_init(&m_xtag_service);
        conn_params_init();
    
        advertising_config();
    
        // Start advertising..
        sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
    
        // Start the 100 msec timer.
        APP_ERROR_CHECK(app_timer_start(m_100_msec_timer_id, APP_TIMER_TICKS(100), NULL));
    
        // Enable DCDC mode (low power support in idle mode).
        APP_ERROR_CHECK(sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE));
    
        SEGGER_RTT_printf(0, "BLE peripheral example started!");
    
        // Enter main loop.
        for (;;) 
        { 
          nrf_pwr_mgmt_run(); 
    
          // Handled scheduler actions.
          app_sched_execute();
        }
    }
    
    
    /**
     * @}
     */
    

    /**
     * Copyright (c) 2016 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    #include <stdio.h>
    #include <stdint.h>
    #include <stdbool.h>
    #include "nordic_common.h"
    #include "app_error.h"
    #include "app_uart.h"
    #include "ble_db_discovery.h"
    #include "app_timer.h"
    #include "app_util.h"
    #include "bsp_btn_ble.h"
    #include "ble.h"
    #include "ble_gap.h"
    #include "ble_hci.h"
    #include "nrf_sdh.h"
    #include "nrf_sdh_ble.h"
    #include "nrf_sdh_soc.h"
    #include "ble_nus_c.h"
    #include "nrf_ble_gatt.h"
    #include "nrf_pwr_mgmt.h"
    #include "nrf_ble_scan.h"
    
    #include "nrf_drv_clock.h"
    #include "app_scheduler.h"
    
    #include "SEGGER_RTT.h"
    
    // GUID gen for xTag online - 10c66c52-e5c9-4fe7-8a04-e40b13eb932c
    #define BLE_UUID_XTAG_BASE_UUID             { 0x10, 0xC6, 0x6C, 0x52, 0xE5, 0xC9, 0x4F, 0xE7, 0x8A, 0x04, 0xE4, 0x0B, 0x13, 0xEB, 0x93, 0x2C }
    #define BLE_UUID_XTAG_SERVICE               0x0001 
    #define BLE_UUID_XTAG_RX_CHARACTERISTC_UUID 0x0002
    #define BLE_UUID_XTAG_TX_CHARACTERISTC_UUID 0x0003
    
    // #define BLE_UUID_XTAG_SERVICE   0x0001                         // Must match the service UUID in the xtag.
    #define XTAG_SERVICE_UUID_TYPE  BLE_UUID_TYPE_VENDOR_BEGIN        // UUID type for the xtag svc.
    
    #define APP_BLE_CONN_CFG_TAG    1                                 // Tag that refers to the BLE stack configuration set with @ref sd_ble_cfg_set. The default tag is @ref BLE_CONN_CFG_TAG_DEFAULT. */
    #define APP_BLE_OBSERVER_PRIO   3                                 // BLE observer priority of the application. There is no need to modify this value. */
    
    #define SCAN_INTERVAL           128                               // Scan interval in 625 us units (240 = 150 mSec, 64 = 40 mSec).
    #define SCAN_WINDOW             64                              // Scan window in 625 us units (112 = 70 mSec, 64 = 40 mSec).
    //#define SCAN_DURATION           600                             // Set explicitly via api.
    
    #define MIN_CONN_INTERVAL       MSEC_TO_UNITS(80,UNIT_1_25_MS)   // Min acceptable con int. Def is 7.5 mSec. Set peripherals to match.
    #define MAX_CONN_INTERVAL       MSEC_TO_UNITS(80, UNIT_1_25_MS)   // Max acceptable con int. Def is 30 mSec. Set peripherals to match.
    #define SLAVE_LATENCY           0                                 // Match egs.
    #define CONN_SUP_TIMEOUT        MSEC_TO_UNITS(4000, UNIT_10_MS)   // Match egs.
    
    // Scheduler settings
    #define SCHED_MAX_EVENT_DATA_SIZE   256  // Larger needed for our own stuff.
    #define SCHED_QUEUE_SIZE            32
    
    NRF_BLE_GATT_DEF(m_gatt);                                               /**< GATT module instance. */
    NRF_BLE_SCAN_DEF(m_scan);                                               /**< Scanning Module instance. */
    
    APP_TIMER_DEF(m_10_Sec_timer_id);                   // timer.
    
    // Add to array within xgateway_data.c
    static uint16_t m_rx_char_value_handle = BLE_CONN_HANDLE_INVALID; // Charactoristic VALUE declaration handle.
    static uint16_t m_tx_char_value_handle = BLE_CONN_HANDLE_INVALID; // Charactoristic VALUE declaration handle.
    static uint16_t m_tx_char_descript_handle = BLE_CONN_HANDLE_INVALID; // Charactoristic DESCRIPTOR declaration handle.
    static uint16_t m_tx_handle = BLE_CONN_HANDLE_INVALID;
    
    static ble_gattc_handle_range_t m_char_disco_handle_range;
    
    static uint8_t m_ble_rx_buffer[256];  // Data from BLE peers. Max 244 bytes.
    static uint16_t m_ble_rx_buffer_size;
    
    volatile uint16_t m_num_received_notifs = 0;
    
    // Maximum length of data (in bytes) that can be transmitted to the peer by the Nordic UART service module.
    static uint16_t m_ble_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - OPCODE_LENGTH - HANDLE_LENGTH; 
    
    static ble_uuid_t const m_xtag_svc_uuid =  
    {
        .uuid = BLE_UUID_XTAG_SERVICE,       // Scan only for devices with our xtag service.
        .type = XTAG_SERVICE_UUID_TYPE
    };
    
    static ble_gap_scan_params_t const m_scan_param =
    {
        .active        = 0x01,
        .interval      = SCAN_INTERVAL,
        .window        = SCAN_WINDOW,
        .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL, //BLE_GAP_SCAN_FP_WHITELIST,
        .timeout       = BLE_GAP_SCAN_TIMEOUT_UNLIMITED,
        .scan_phys     = BLE_GAP_PHY_1MBPS,
        .channel_mask[4] = 0x00, // Use 0xC0 if you want to block channels 39 and 38 and only scan 37. 3 chan besat for noisy areas.
    };
    
    static ble_gap_conn_params_t const m_con_param =
    {
      .min_conn_interval  = MIN_CONN_INTERVAL,
      .max_conn_interval  = MAX_CONN_INTERVAL,
      .slave_latency      = SLAVE_LATENCY,
      .conn_sup_timeout   = CONN_SUP_TIMEOUT,
    };
    
    static uint16_t m_notificationCount = 0;
    
    void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
    {
        app_error_handler(0xDEADBEEF, line_num, p_file_name);
    }
    
    static void timer_handler(void* p_context)
    {
      // This handler was used initially to stop scanning in 10 sec - now we stop scanning whenever a device is found and we can connect.
    
      //nrf_ble_scan_stop();
      //SEGGER_RTT_printf(0, "Scanning stopped.\n");
    
      return;
    }
    
    void on_main_ble_sched_handler(void *p_event_data, uint16_t event_size)
    {
      m_num_received_notifs++;
      // Handle incoming notification data that is preset. Note that args p_event_data and size are unused.
      SEGGER_RTT_printf(0, "%d, Recieved notifcation %d with length %d. Starting with data %d %d.\n",m_num_received_notifs, ++m_notificationCount, m_ble_rx_buffer_size, m_ble_rx_buffer[0], m_ble_rx_buffer[1]);
    }
    
    // Fcn that confirms scanned device / peripheral is ours.
    bool ble_adv_contains_mfrid(uint8_t* pAdvADBlocks, uint8_t AdvLen, uint16_t mfrId)
    {
      uint8_t curIdx = 0;
      uint8_t curADLen = 0;
      uint8_t sanityCnt = 0;
      
      // Separate ms and ls bytes in mfrId.
      uint8_t msMfrByte = (uint8_t) ((mfrId >> 8) & 0x00FF);
      uint8_t lsMfrByte = (uint8_t) ((mfrId >> 0) & 0x00FF);
      uint8_t cnt = 0;
    
      // Walk through Adv packet (max len 32 bytes) looking for individual AD blocks by looking at each AD block length, starting at 0.
      while(curIdx < AdvLen)
      {
        // Get the cur AD block len using the idx.
        curADLen = pAdvADBlocks[curIdx];
    
        // Misalign if we ever get a length of zero or beyond AdvLen.
        if((curADLen == 0) || (curIdx + (curADLen + 1)) > AdvLen) { SEGGER_RTT_printf(0, "Bad len %d. Fail out.\n", curADLen); return false; }
        
        // If mfr block with type 0xFF, check mfrId.
        if(pAdvADBlocks[curIdx + 1] == 0xFF)
        {
          // If backwards mfrId match return true, else false.
          if((pAdvADBlocks[curIdx + 2] == lsMfrByte) && (pAdvADBlocks[curIdx + 3] == msMfrByte)) { return true;}
          else { return false; }
        }
        else { curIdx += curADLen + 1; }  // Advance to the next AD block (length in packet does not include its own byte).
    
        // Increase the sanity count and return if it gets high as we cannot stay in here forever.
        if(++sanityCnt > 10) { return false; }
      }
    
      // Should never get here as we should always find an AD block with type 0xFF (mfr data).
      return false;
    }
    
    static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
    {
      ret_code_t            err_code;
      ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
      ble_gap_evt_data_length_update_t const* p_datalen_evt;
    
      switch (p_ble_evt->header.evt_id)
      {
        case BLE_GAP_EVT_ADV_REPORT:
    
          // Look for Dworx Mfr ID 0x030E.
          if(ble_adv_contains_mfrid(p_ble_evt->evt.gap_evt.params.adv_report.data.p_data,
                                        p_ble_evt->evt.gap_evt.params.adv_report.data.len, 0x030E))
          {
            SEGGER_RTT_printf(0, "Scanned xTAG. Will stop scanning and connect.\n");
    
            nrf_ble_scan_stop();
    
            // Connect using the address and other memebers.
            // Connect status in our store will be updated when a connected event comes in.
            err_code = sd_ble_gap_connect(&(p_ble_evt->evt.gap_evt.params.adv_report.peer_addr), &m_scan_param, &m_con_param, APP_BLE_CONN_CFG_TAG);
    
            // Provide status.
            if(err_code != NRF_SUCCESS) 
            {
              if(err_code == NRF_ERROR_CONN_COUNT)
              {
                SEGGER_RTT_printf(0, "Connection error. Max connections exceeded.\n"); 
              }
              else
              {
                SEGGER_RTT_printf(0, "Connection error %d\n", err_code); 
              }
            }
            else 
            { 
              SEGGER_RTT_printf(0, "Connected to xTAG.\n"); 
            }
          }
    
          break;
    
        case BLE_GAP_EVT_CONNECTED:
        {
          SEGGER_RTT_printf(0, "Connected to tag\n");
    
          // Request 1MBPS or 2MBPS or Coded phy on the peripheral. 
          // NOTE: The peripheral phy update request will only go out if it is different than the current connection phy.
          ble_gap_phys_t phys;
          phys.rx_phys = BLE_GAP_PHY_2MBPS;
          phys.tx_phys = BLE_GAP_PHY_2MBPS;
          err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
          APP_ERROR_CHECK(err_code);
    
          // Log the effective mtu.
          SEGGER_RTT_printf(0, "Effective MTU after connection is: %d bytes\n", 
            nrf_ble_gatt_eff_mtu_get(&m_gatt, p_ble_evt->evt.gap_evt.conn_handle));
    
          break;
        }
        case BLE_GAP_EVT_DISCONNECTED:
        {
          uint8_t Idx;
    
          if(p_gap_evt->params.disconnected.reason == BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED)
          {
            SEGGER_RTT_printf(0, "Connection Failed!\n", err_code);
          }
          else
          {
            SEGGER_RTT_printf(0, "Unsolicited disconnection from tag with handle %04X\n", 
             p_ble_evt->evt.gap_evt.conn_handle);
          }
    
          break;
        }
        case BLE_GAP_EVT_TIMEOUT:
    
          //SEGGER_RTT_printf(0, "BLE_GAP_EVT_TIMEOUT\n.");
    
          if (p_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_CONN)
          {
            SEGGER_RTT_printf(0, "Connection Request timed out.\n");
          }
          else if (p_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_SCAN)
          {
            SEGGER_RTT_printf(0, "Scan timed out.\n");
          }
          break;
    
        case BLE_GAP_EVT_PHY_UPDATE:
          
          SEGGER_RTT_printf(0, "Phy updated. rx %d tx %d\n", 
          p_gap_evt->params.phy_update.rx_phy, p_gap_evt->params.phy_update.tx_phy);
    
          // Start service discovery.
          err_code = sd_ble_gattc_primary_services_discover(p_ble_evt->evt.gap_evt.conn_handle, 0x0B, &m_xtag_svc_uuid);
          APP_ERROR_CHECK(err_code);
    
          break;
    
        case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
    
          // Pairing not supported.
          err_code = sd_ble_gap_sec_params_reply(p_ble_evt->evt.gap_evt.conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
            
          // Accepting parameters requested by peer.
          SEGGER_RTT_printf(0, "Connection param update from peer. Min 1.25 mSec units %d. Max 1.25 mSec units %d\n", 
            p_gap_evt->params.conn_param_update_request.conn_params.min_conn_interval,
            p_gap_evt->params.conn_param_update_request.conn_params.max_conn_interval);
    
          err_code = sd_ble_gap_conn_param_update(p_gap_evt->conn_handle,
                                                  &p_gap_evt->params.conn_param_update_request.conn_params);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GAP_EVT_CONN_PARAM_UPDATE:
    
          SEGGER_RTT_printf(0, "Connection param update. Min 1.25 mSec units %d. Max 1.25 mSec units %d\n", 
            p_gap_evt->params.conn_param_update.conn_params.min_conn_interval,
            p_gap_evt->params.conn_param_update.conn_params.max_conn_interval);
    
            break;
          
        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        {
          // We don't handle peripheral requests. We make the request after connection.
          SEGGER_RTT_printf(0, "PHY update request in. Will ignore requested rx phy %d and tx phy %d.\n",
           p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys.rx_phys,
           p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys.tx_phys);
    
          break;
        }
    
        case BLE_GAP_EVT_DATA_LENGTH_UPDATE:
        {
          p_datalen_evt = &p_ble_evt->evt.gap_evt.params.data_length_update;
    
          SEGGER_RTT_printf(0, "Data length (PDU) update. Max Tx octets: %d, Max Rx octets %d, Max Tx uSec: %d, Max Rx uSec: %d\n",
            p_datalen_evt->effective_params.max_tx_octets, p_datalen_evt->effective_params.max_rx_octets, 
            p_datalen_evt->effective_params.max_tx_time_us, p_datalen_evt->effective_params.max_rx_time_us);
    
            break;
        }
    
        case BLE_GATTC_EVT_TIMEOUT:
            
          // Disconnect on GATT Client timeout event.
          SEGGER_RTT_printf(0, "GATT Client Timeout.\n");
          err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                           BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GATTS_EVT_TIMEOUT:
            
          // Disconnect on GATT Server timeout event.
          SEGGER_RTT_printf(0, "GATT Server Timeout.\n");
          err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                           BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP :
        {
          ble_gattc_evt_t const * p_gatt_evt = &p_ble_evt->evt.gattc_evt;
          uint16_t num_svcs = p_gatt_evt->params.prim_srvc_disc_rsp.count;
    
          // Confirm status and at least 1 service was found.
          if((p_gatt_evt->gatt_status != NRF_SUCCESS) || (num_svcs < 1))
          {
            SEGGER_RTT_printf(0, "GATT Svc resp with bad status of %d or num svcs of %d.\n",
                p_gatt_evt->gatt_status, num_svcs);
            break;
          }
    
          // If the UUID does not match - ignore. We should only get 1 svc.
          if(p_gatt_evt->params.prim_srvc_disc_rsp.services[0].uuid.uuid != BLE_UUID_XTAG_SERVICE)
          {
            SEGGER_RTT_printf(0, "GATT Svc disco resp with bad UUID of %d\n",
                p_gatt_evt->params.prim_srvc_disc_rsp.services[0].uuid.uuid);
            break;
          }
    
          for(int x=0; x<num_svcs; x++) { SEGGER_RTT_printf(0, "Primary svc found with UUID %d, handle range start %d and end %d.\n", 
                p_gatt_evt->params.prim_srvc_disc_rsp.services[x].uuid.uuid,
                p_gatt_evt->params.prim_srvc_disc_rsp.services[x].handle_range.start_handle,
                p_gatt_evt->params.prim_srvc_disc_rsp.services[x].handle_range.end_handle); }
    
          // Setup handle ranges to look for chars on the server.
          m_char_disco_handle_range.start_handle = p_gatt_evt->params.prim_srvc_disc_rsp.services[0].handle_range.start_handle;
          m_char_disco_handle_range.end_handle = p_gatt_evt->params.prim_srvc_disc_rsp.services[0].handle_range.end_handle;
    
          // Clear handles. These will be setup within charactoristic (rx) and descriptor (tx) disco event handling.
          m_rx_char_value_handle = BLE_CONN_HANDLE_INVALID;
          m_tx_char_value_handle = BLE_CONN_HANDLE_INVALID;
    
          // Results in BLE_GATTC_EVT_CHAR_DISC_RSP event (case below).
          err_code = sd_ble_gattc_characteristics_discover(p_gatt_evt->conn_handle, &m_char_disco_handle_range);
          APP_ERROR_CHECK(err_code);
    
          break;
        }
    
        case BLE_GATTC_EVT_CHAR_DISC_RSP:
        {
          // Look through charactoristics and retrieve char declaration and char value declaration handles as req'd.
          // Critically - for each char, the UUID set will be for the char value declaration and not the char declaration
          // as the char declaration UUID is always static (0x2803).
    
          // Setup reusable vars.
          ble_gattc_evt_t const * p_gatt_evt = &p_ble_evt->evt.gattc_evt;
          uint16_t num_chars = p_gatt_evt->params.char_disc_rsp.count;
    
          // DEBUG
          SEGGER_RTT_printf(0, "GATT char disco with %d chars\n", num_chars);
    
          // Confirm status
          if(p_gatt_evt->gatt_status != NRF_SUCCESS)
          {
            SEGGER_RTT_printf(0, "GATT char disco resp with bad status of %d.\n", p_gatt_evt->gatt_status);
    
            break;
          }
    
          // Loop through chars looking for our Rx UUID
          ble_gattc_char_t idx_char;
          for(int x=0; x<num_chars; x++) 
          {
            // Setup indexed charactoristic.
            idx_char = p_gatt_evt->params.char_disc_rsp.chars[x];
    
            SEGGER_RTT_printf(0, "GATT char disco with UUID %04X and declaration handle %04X\n", idx_char.uuid.uuid, idx_char.handle_decl);
    
            if(idx_char.uuid.uuid == BLE_UUID_XTAG_RX_CHARACTERISTC_UUID) 
            {
              // Store the handle for subsequent writes.
              m_rx_char_value_handle = idx_char.handle_value;
    
              // Increment the start handle for subsequent char discoveries.
              m_char_disco_handle_range.start_handle = idx_char.handle_decl + 1;
            }
            else if(idx_char.uuid.uuid == BLE_UUID_XTAG_TX_CHARACTERISTC_UUID)
            {
              // Set the m_tx_char_value_handle to match the char value handle.
              m_tx_char_value_handle = idx_char.handle_value; 
    
              // We can start to look for the descriptor for this char.
              // ... handled within the BLE_GATTC_EVT_DESC_DISC_RSP event case that follows.
              // The descriptor handle should follow the char value declaration handle. Set start_handle.
              m_char_disco_handle_range.start_handle = idx_char.handle_value + 1;
              err_code = sd_ble_gattc_descriptors_discover(p_gatt_evt->conn_handle, &m_char_disco_handle_range);
              APP_ERROR_CHECK(err_code);
            }
            else
            {
              // Just inc the start_handle if ther is no match.
              m_char_disco_handle_range.start_handle = idx_char.handle_decl + 1;
            }
          }
    
          // If either handle is not set ...
          if( (m_rx_char_value_handle == BLE_CONN_HANDLE_INVALID) || (m_tx_char_value_handle == BLE_CONN_HANDLE_INVALID))
          {
            // If we are at the end of the handles, give up.
            if(m_char_disco_handle_range.start_handle > m_char_disco_handle_range.end_handle)
            {
              SEGGER_RTT_printf(0, "GATT char disco resp with missing UUIDs.\n");
            }
            else
            {
              // Try again using the updated handle range.
              err_code = sd_ble_gattc_characteristics_discover(p_gatt_evt->conn_handle, &m_char_disco_handle_range);
              APP_ERROR_CHECK(err_code);
            }
          }
          
          break;
        }
    
        case BLE_GATTC_EVT_DESC_DISC_RSP:
        {
          // Setup reusable vars.
          ble_gattc_evt_t const * p_gatt_evt = &p_ble_evt->evt.gattc_evt;
          uint16_t num_descriptors = p_gatt_evt->params.desc_disc_rsp.count;
    
          // DEBUG
          SEGGER_RTT_printf(0, "GATT descriptor disco with %d descriptors\n", num_descriptors);
    
          // Confirm status
          if(p_gatt_evt->gatt_status != NRF_SUCCESS)
          {
            SEGGER_RTT_printf(0, "GATT descriptor disco resp with bad status of %d.\n", p_gatt_evt->gatt_status);
    
            break;
          }
    
          // Loop through descriptors looking for our CCCD UUID. Because we setup this event with the handle that
          // should match our tx char descriptor, we should only get 1 event and it should match.
          ble_gattc_desc_t idx_descriptor;
          for(int x=0; x<num_descriptors; x++) 
          {
            SEGGER_RTT_printf(0, "GATT descriptor disco with UUID %04X and handle %04X\n", 
              p_gatt_evt->params.desc_disc_rsp.descs[x].uuid.uuid,
              p_gatt_evt->params.desc_disc_rsp.descs[x].handle );
    
            if(p_gatt_evt->params.desc_disc_rsp.descs[x].uuid.uuid == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG)
            {
              // Simply use the descriptor handle to write a value to enable notificatins on the tx char.
              // First - create write_params and and a byte array to write.
              ble_gattc_write_params_t write_params;
              memset(&write_params, 0, sizeof(write_params));
              m_tx_char_descript_handle = p_gatt_evt->params.desc_disc_rsp.descs[x].handle;
    
              uint8_t cccd_value[2] = { BLE_GATT_HVX_NOTIFICATION, 0 };       // 1 to enable notifications.
    
              write_params.handle = m_tx_char_descript_handle;
              write_params.len = 2;
              write_params.p_value = cccd_value;
              write_params.write_op = BLE_GATT_OP_WRITE_REQ;
              write_params.offset = 0;
              write_params.flags = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE;
    
              err_code = sd_ble_gattc_write(p_gatt_evt->conn_handle, &write_params);
              APP_ERROR_CHECK(err_code);
    
              return; // Only need the 1 descriptor.
            }
          }
    
          // If we get here, there is an issue finding our descriptor.
          SEGGER_RTT_printf(0, "GATT descriptor disco fail. No cccd found\n");
          
          break;
        }
    
        case BLE_GATTC_EVT_WRITE_RSP:
        {
          // Setup reusable vars.
          ble_gattc_evt_t const * p_gatt_evt = &p_ble_evt->evt.gattc_evt;
    
          // Handle descripter var write responses.
          if(p_gatt_evt->params.write_rsp.handle == m_tx_char_descript_handle)
          {
            // Notification setup complete. If the other handles are valid, connection is complete.
            if((m_rx_char_value_handle != BLE_CONN_HANDLE_INVALID) && (m_tx_char_value_handle != BLE_CONN_HANDLE_INVALID))
            {
              // Store the handle for tx. Wont be used as we will handle notifications only.
              m_tx_handle = p_gatt_evt->conn_handle;
    
              // DEBUG
              SEGGER_RTT_printf(0, "Connect success with handle %04X. Sending cmd to start notifications\n", p_gatt_evt->conn_handle);
    
              // Send simple made up Cmd to start data notifications. 
              uint8_t cmd_len = 2;
              uint8_t cmdBuf[cmd_len];
              cmdBuf[0] = 0xAA;    
              cmdBuf[1] = 0xBB;   
    
              // Setup a ble_gattc_write_params_t for the write.
              ble_gattc_write_params_t write_params;
              memset(&write_params, 0, sizeof(write_params));
              write_params.handle = m_rx_char_value_handle;
              write_params.len = cmd_len;
              write_params.p_value = cmdBuf;
              write_params.write_op = BLE_GATT_OP_WRITE_CMD;
              write_params.offset = 0;
    
              // Actually send the cmd.
              err_code = sd_ble_gattc_write(m_tx_handle, &write_params);
              APP_ERROR_CHECK(err_code);
    
            }
            else
            {
              SEGGER_RTT_printf(0, "Connect fail. GATT Notification ok but error on rx or tx char handle.\n");
            }
          }
    
          break;
        }
    
        case BLE_GATTC_EVT_HVX:
        {
          // Setup reusable vars.
          ble_gattc_evt_t const * p_gatt_evt = &(p_ble_evt->evt.gattc_evt);
    
          // Setup data for subsequent processing on main.
          memcpy_fast(m_ble_rx_buffer, p_gatt_evt->params.hvx.data, (size_t) p_gatt_evt->params.hvx.len);
          m_ble_rx_buffer_size = p_gatt_evt->params.hvx.len;
    
          // Schedule an event on the main thread to process the response.
          app_sched_event_put(NULL,  0, on_main_ble_sched_handler);
    
          break;
        }
    
        default:
    
            break;
      }
    }
    
    /**@brief Function for handling events from the GATT library. */
    void ble_gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
    {
      ret_code_t err_code;
    
      if (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED)
      {
        // Our NRF_SDH_BLE_GAP_DATA_LENGTH is setup as 251 in sdk_config.h. This is the PDU max length.
        // After connection, the central tries to update the MTU in the peripheral using NRF_SDH_BLE_GAP_DATA_LENGTH.
        // If successful, we get the ATT header (3 byte opcode and handle) and 244 byte ATT body back as 247 total or mtu effective. 
        // We can only use the ATT body, so set m_ble_max_data_len to that.
        // Good info here: https://punchthrough.com/maximizing-ble-throughput-part-3-data-length-extension-dle-2/
        m_ble_max_data_len = p_evt->params.att_mtu_effective - 3; // 1 Byte for att opcode and 2 for att handle (3 byte att header).
        SEGGER_RTT_printf(0, "BLE GATT ATT MTU update done. MTU effective in %d. Max data len set to %d\n",
          p_evt->params.att_mtu_effective, m_ble_max_data_len);
      }
      else if(p_evt->evt_id == NRF_BLE_GATT_EVT_DATA_LENGTH_UPDATED)
      {
        SEGGER_RTT_printf(0, "BLE GATT ATT data length update done. Event data len %d\n", p_evt->params.data_length);
      }
    }
    
    void ble_init(void)
    {
      ret_code_t err_code;
      ble_opt_t ble_opt;
    
      ///////////////////////////////////////////////////////////////////
      // First - init the stack /////////////////////////////////////////
      ///////////////////////////////////////////////////////////////////
    
      // Init BLE with NUS. BLE stuff in xgateway_bluetooth.*
      err_code = nrf_sdh_enable_request();
      APP_ERROR_CHECK(err_code);
    
      // Configure the BLE stack using the default settings.
      // Fetch the start address of the application RAM.
      uint32_t ram_start = 0;
      err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
      APP_ERROR_CHECK(err_code);
    
      // Enable BLE stack.
      err_code = nrf_sdh_ble_enable(&ram_start);
      APP_ERROR_CHECK(err_code);
    
      // Register a handler for BLE events.
      NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
    
      ///////////////////////////////////////////////////////////////////
      // End stack init.
      ///////////////////////////////////////////////////////////////////
    
      // GAP - Add extended connection event support to send multiple packets per event.
      memset(&ble_opt, 0x00, sizeof(ble_opt));
      ble_opt.common_opt.conn_evt_ext.enable = 1;
      err_code = sd_ble_opt_set(BLE_COMMON_OPT_CONN_EVT_EXT, &ble_opt);
      APP_ERROR_CHECK(err_code);
    
      ///////////////////////////////////////////////////////////////////
      // GATT init            
      ///////////////////////////////////////////////////////////////////
    
      err_code = nrf_ble_gatt_init(&m_gatt, ble_gatt_evt_handler);
      APP_ERROR_CHECK(err_code);
    
      // Declare 16-bit service and 128-bit base UUIDs and add them to the BLE stack
      ble_uuid_t service_uuid;
      ble_uuid128_t base_uuid = BLE_UUID_XTAG_BASE_UUID;
      service_uuid.uuid = BLE_UUID_XTAG_SERVICE;
      err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);
      APP_ERROR_CHECK(err_code);
    
      ///////////////////////////////////////////////////////////////////
      // End GATT init             
      ///////////////////////////////////////////////////////////////////
    
      // Init scan and set params explicitly. Both always return NRF_SUCCESS
      nrf_ble_scan_init(&m_scan, NULL, NULL);
      nrf_ble_scan_params_set(&m_scan, &m_scan_param);
    }
    
    int main(void)
    {
        // Initialize.
        SEGGER_RTT_Init();
        SEGGER_RTT_printf(0, "Central Logging started.\n");
    
        // As per egs, setup the clock driver to use the lfclk (before timers_init)
        APP_ERROR_CHECK(nrf_drv_clock_init());
        nrf_drv_clock_lfclk_request(NULL);
    
        APP_SCHED_INIT(SCHED_MAX_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE);
    
        // Init timers.
        APP_ERROR_CHECK(app_timer_init());
        APP_ERROR_CHECK(app_timer_create(&m_10_Sec_timer_id, APP_TIMER_MODE_SINGLE_SHOT , timer_handler));
    
        APP_ERROR_CHECK(nrf_pwr_mgmt_init());
    
        ble_init();
    
        SEGGER_RTT_printf(0, "BLE central example started! Will try to scan peer in the next 10 sec ...\n");
    
        // Start scanning.
        nrf_ble_scan_start(&m_scan);
    
        // Start timeer with handler that will stop scanning.
        APP_ERROR_CHECK(app_timer_start(m_10_Sec_timer_id, APP_TIMER_TICKS(10000), NULL));
    
        // Enter main loop.
        for (;;) 
        { 
          app_sched_execute();
    
          // Manage power.
          nrf_pwr_mgmt_run(); 
        }
    }
    

    The peripheral will now print "num sent %d", where %d is the number of successfully sent packets. The central will print:

    374, Recieved notifcation 374 with length 240. Starting with data 151 152.

    just like before, but I added the first number, indicating the number of received notifications. 

    When I run the central and peripheral, I see that these numbers are always the same, so no notifications are lost. In my case, that was 382 successful notifications both sent and received. Is the issue that you mean that this number should be higher? If so, what I have been trying to say is that when sd_ble_gatts_hvx() returns NRF_ERROR_RESOURCES, the packet is not queued. You need to wait for the next TX_COMPLETE event before you try to queue it again. Even then, there is no guarantee that it will return NRF_SUCCESS, based on the size of the packet that was Acked (causing the TX_COMPLETE), and the size of the packet you are trying to queue.

    Check out and test the attached main.c files, and let me know if I misunderstood.

Reply
  • I suspect that I misunderstand something, but check this out. Do I need to change anything to replicate the issue? I am a bit confused about your description of the timers, and how you queue up the notifications. 

    I modified only the main files slightly by adding a counter and adding some logging. 

    /**
     * Copyright (c) 2014 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    /** @file
     *
     * @defgroup ble_sdk_uart_over_ble_main main.c
     * @{
     * @ingroup  ble_sdk_app_nus_eval
     * @brief    UART over BLE application main file.
     *
     * This file contains the source code for a sample application that uses the Nordic UART service.
     * This application uses the @ref srvlib_conn_params module.
     */
    
    
    #include <stdint.h>
    #include <string.h>
    #include "nordic_common.h"
    #include "nrf.h"
    #include "ble_hci.h"
    #include "ble_advdata.h"
    #include "ble_advertising.h"
    #include "ble_conn_params.h"
    #include "nrf_sdh.h"
    #include "nrf_sdh_soc.h"
    #include "nrf_sdh_ble.h"
    #include "nrf_ble_gatt.h"
    #include "nrf_ble_qwr.h"
    #include "ble_dis.h"
    #include "app_timer.h"
    #include "ble_nus.h"
    #include "app_uart.h"
    #include "app_util_platform.h"
    #include "bsp_btn_ble.h"
    #include "nrf_pwr_mgmt.h"
    
    #include "nrf_drv_clock.h"
    #include "app_scheduler.h"
    #include "mem_manager.h"
    
    #include "SEGGER_RTT.h"
    
    #define DEVICE_NAME                     "xTag"                                  // As Friendlyname and other BLE services.
    #define DEVICE_NAME_SUFFIX_LEN          14                                      // Extra space after for DEVICE_NAME + " xxxxxxxxxxxx" with \0 at end.
    #define MANUFACTURER_NAME               "Deviceworx"                            // For BLE services.
    #define APP_ADV_INTERVAL                800                                     // Mult of 0.625 ms. Use 1600 for 1 sec.
    #define APP_BLE_OBSERVER_PRIO           3                                       // Default
    #define APP_BLE_CONN_CFG_TAG            1                                       // SoftDevice BLE configuration.
    
    #define MIN_CONN_INTERVAL               MSEC_TO_UNITS(80, UNIT_1_25_MS)             /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
    #define MAX_CONN_INTERVAL               MSEC_TO_UNITS(80, UNIT_1_25_MS)             /**< Maximum acceptable connection interval (75 ms), Connection interval uses 1.25 ms units. */
    #define SLAVE_LATENCY                   0                                           /**< Slave latency. */
    #define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(4000, UNIT_10_MS)             /**< Connection supervisory timeout (4 seconds), Supervision Timeout uses 10 ms units. */
    #define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000)                       /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
    #define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000)                      /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
    #define MAX_CONN_PARAMS_UPDATE_COUNT    3                                           /**< Number of attempts before giving up the connection parameter negotiation. */
    
    #define DEAD_BEEF                       0xDEADBEEF                                  /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */
    
    #define BLE_MAX_TX_SVC_BYTES                244
    #define BLE_MAX_RX_SVC_BYTES                8
    
    
    // GUID gen for xTag online - 10c66c52-e5c9-4fe7-8a04-e40b13eb932c
    #define BLE_UUID_XTAG_BASE_UUID         {0x10, 0xC6, 0x6C, 0x52, 0xE5, 0xC9, 0x4F, 0xE7, 0x8A, 0x04, 0xE4, 0x0B, 0x13, 0xEB, 0x93, 0x2C}
    #define BLE_UUID_XTAG_SERVICE           0x0001 // 
    
    #define BLE_UUID_XTAG_RX_CHARACTERISTC_UUID          0x0002
    #define BLE_UUID_XTAG_TX_CHARACTERISTC_UUID          0x0003
    
    // Scheduler settings
    #define SCHED_MAX_EVENT_DATA_SIZE   sizeof(ble_nus_evt_t)
    #define SCHED_QUEUE_SIZE            5
    
    #define MFR_PAYLOAD_SIZE_GAP  14
    
    volatile uint16_t m_num_sent_packets = 0;
    
    typedef struct
    {
        uint16_t conn_handle;                     // Handle of the current connection (as provided by the BLE stack, is BLE_CONN_HANDLE_INVALID if not in a connection).
        uint16_t service_handle;                  // Handle of Our Service (as provided by the BLE stack). 
        ble_gatts_char_handles_t rx_char_handles; // Hnndles for the rx char.
        ble_gatts_char_handles_t tx_char_handles; // Hnndles for the tx char.
    }ble_os_t;
    
    NRF_BLE_GATT_DEF(m_gatt);                                                 // GATT module instance.
    
    //static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID;                  // Handle of the current connection..
    ble_os_t m_xtag_service;         
    
    static bool m_notif_busy = false;     // Notification out when true - must wait for client confirmation.
    
    // UUIDs for standard services here.
    static ble_uuid_t m_adv_uuids[] =                       
    {
      {BLE_UUID_DEVICE_INFORMATION_SERVICE, BLE_UUID_TYPE_BLE},
      {BLE_UUID_XTAG_SERVICE, BLE_UUID_TYPE_BLE }
    };
    
    static uint8_t rx_char_data[BLE_MAX_RX_SVC_BYTES];
    
    ble_gap_adv_params_t m_adv_params;                                        // Parameters to be passed to the stack when starting advertising.
    uint8_t              m_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET;       // Advertising handle used to identify an advertising set.
    uint8_t              m_enc_advdata1[BLE_GAP_ADV_SET_DATA_SIZE_MAX];       // 31 Byte Buffer for storing an encoded advertising set.
    uint8_t              m_enc_advdata2[BLE_GAP_ADV_SET_DATA_SIZE_MAX];       // 31 Byte Buffer for storing an encoded advertising set.
    uint8_t              m_enc_scanrespdata1[BLE_GAP_ADV_SET_DATA_SIZE_MAX];  // Buffer for storing an encoded advertising set.
    uint8_t              m_enc_scanrespdata2[BLE_GAP_ADV_SET_DATA_SIZE_MAX];  // Buffer for storing an encoded advertising set.
    
    
    // Setup 2 buffers for advertising and switching on the fly.
    // Ref here: https://devzone.nordicsemi.com/f/nordic-q-a/32824/dynamically-updating-advertising-data-in-sdk-15
    uint8_t m_bufIdx = 0;
    ble_gap_adv_data_t m_gapData[2] = 
    {
      { 
        .adv_data.p_data = m_enc_advdata1,
        .adv_data.len = sizeof(m_enc_advdata1),
        .scan_rsp_data.p_data = m_enc_scanrespdata1,
        .scan_rsp_data.len = sizeof(m_enc_scanrespdata1)
      },
      { 
        .adv_data.p_data = m_enc_advdata2,
        .adv_data.len = sizeof(m_enc_advdata2),
        .scan_rsp_data.p_data = m_enc_scanrespdata2,
        .scan_rsp_data.len = sizeof(m_enc_scanrespdata2)
      },
    };                                         // As per egs, we need a svc struct.
    
    APP_TIMER_DEF(m_100_msec_timer_id);                         // Misc stuff every 100 mSec.
    static uint16_t m_100_msec_60sec_cnt = 0;                   // Used to inc the min ctr.
    static uint16_t m_100_msec_10sec_cnt = 0;                   // Used to restart adverts.
    APP_TIMER_DEF(m_25_msec_timer_id);                          // Acq data collected every 15 mSec.
    
    static uint8_t* m_p_ramstore = NULL;    // Datastore for fake 240 bytes. Store values of 0 - 240 (0xF0) and rotate this data with each send.
    
    static uint8_t m_pendingNotifications = 0;
    static uint16_t m_handlerCount = 0;
    
    void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name) { app_error_handler(DEAD_BEEF, line_num, p_file_name); }
    
    void set_notif_busy(bool _busy) { m_notif_busy = _busy; }
    bool is_notif_busy(void) { return m_notif_busy; }
    
    void on_main_shed_rx_char_evt_handler(void *p_event_data, uint16_t event_size)
    {
      SEGGER_RTT_printf(0, "Incomming bytes length %d - %02X %02X ... Starting notifications.\n", event_size, ((uint8_t*) p_event_data)[0], ((uint8_t*) p_event_data)[1]);
    
      // Nothing too fancy. Anything in - start sending notifications to the central from a timer handler.
      // 25 mSec period is how often we send 40 samples at a rate of 1600 per sec.
      APP_ERROR_CHECK(app_timer_start(m_25_msec_timer_id, APP_TIMER_TICKS(25), NULL));
    
      // We will only handle x notifications.
      m_handlerCount = 0;
    }
    
    void on_main_shed_data_notification_handler(void *p_event_data, uint16_t event_size)
    {
      uint32_t err_code;
      uint8_t tempStore = 0;
      uint16_t pcktLen = 240;
      ble_gatts_hvx_params_t hvx_params;
    
      // Only process 400 times.
      if(m_handlerCount++ > 400)
      {
        SEGGER_RTT_printf(0, "Stopping notifications.\n"); 
    
        APP_ERROR_CHECK(app_timer_stop(m_25_msec_timer_id));
    
        return;
      }
    
      // New notification required whenever this handler is called.
      m_pendingNotifications++;
    
      // If we are still busy sending the last packet, cannot continue. m_pendingNotifications won't get reduced.
      // See BLE_GATTS_EVT_HVN_TX_COMPLETE event handling where notification busy flag is cleared.
      if(is_notif_busy())
      {
        return;
      }
    
      // Loop through pending notifications. We can send more than 1 every 15 mSec if deta length extensions are working.
      while((m_pendingNotifications>0) && (is_notif_busy() == false))
      {
        // Send the data.
        memset(&hvx_params, 0, sizeof(hvx_params));
        hvx_params.handle = m_xtag_service.tx_char_handles.value_handle;
        hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
        hvx_params.offset = 0;
        hvx_params.p_len  = &pcktLen;
        hvx_params.p_data = &(m_p_ramstore[0]);  
    
        err_code = sd_ble_gatts_hvx(m_xtag_service.conn_handle, &hvx_params);                       
        if (err_code == NRF_SUCCESS)
        {
            m_num_sent_packets++;
            SEGGER_RTT_printf(0, "num sent %d\n", m_num_sent_packets);
        }
        if ( (err_code == NRF_ERROR_RESOURCES) || (err_code == NRF_ERROR_BUSY) ) // Notification pending.
        {
          // Indicate that we are busy until a BLE_GATTS_EVT_HVN_TX_COMPLETE comes in.
          set_notif_busy(true);
    
          // DEBUG msgs.
          if(err_code == NRF_ERROR_BUSY) { SEGGER_RTT_printf(0, "Notification busy\n"); }
          else { SEGGER_RTT_printf(0, "Notification resource busy\n"); }
        }
        else if(err_code != NRF_SUCCESS)
        {
            SEGGER_RTT_printf(0, "Notification error: %d\n", err_code);
    
            // Some errors handled externally.
            return;
        }
    
        // Reduce m_pendingNotifications as we just handled one.
        m_pendingNotifications--;
    
        // Rotate the data by shuffling it up by 1 byte (Data at Idx 1 to Idx 0, etc). Data at Idx 0 to Idx 239.
        uint8_t tempStore = m_p_ramstore[0];
        for(int x=0; x<239; x++) { m_p_ramstore[x] = m_p_ramstore[x+1]; }
        m_p_ramstore[239] = tempStore;
    
      } // while()
    }
    
    static void timer_handler_100_msec(void* p_context)
    {
      if(++m_100_msec_10sec_cnt >= 100)
      {
        // Restart advertising.
        sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
    
        // Reset every cycle.
        m_100_msec_10sec_cnt = 0;
      }
    
      // Inc the min counter and handle periodic stuff.
      if(++m_100_msec_60sec_cnt >= 600)
      { 
        // Reset every min.
        m_100_msec_60sec_cnt = 0;
    
        SEGGER_RTT_printf(0, "Minute with mSec count %d.\n", (app_timer_cnt_get()/33));
      }
    
      return;
    }
    
    // Handler that emulates acquired data from a sensor. 25 samples (6 bytes each) every 15 mSec closely approx 1600 samples/sec/
    static void timer_handler_25_msec(void* p_context)
    {
      // Get out of handler quick. Schedule handler that runs on main thread.
      app_sched_event_put(NULL,  0, on_main_shed_data_notification_handler);
    
      return;
    }
    
    /**@brief Function for initializing the timer module.
     */
    static void timers_init(void)
    {
      // Initialize timer module.
      APP_ERROR_CHECK(app_timer_init());
    
      // Create the timer.
      APP_ERROR_CHECK(app_timer_create(&m_100_msec_timer_id, APP_TIMER_MODE_REPEATED, timer_handler_100_msec));
    
      APP_ERROR_CHECK(app_timer_create(&m_25_msec_timer_id, APP_TIMER_MODE_REPEATED, timer_handler_25_msec));
    }
    
    // Handle BLE events.
    static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
    {
      ret_code_t err_code = NRF_SUCCESS;
    
      ble_gap_phys_t phys;
      ble_gap_evt_phy_update_t const* p_phy_evt;
      ble_gap_evt_data_length_update_t const* p_datalen_evt;
    
      // SEGGER_RTT_printf(0, "Bluetooth Evt 0x%02X.\n", p_ble_evt->header.evt_id);
    
      switch (p_ble_evt->header.evt_id)
      {
        case BLE_GAP_EVT_ADV_SET_TERMINATED:
    
          //SEGGER_RTT_printf(0, "Adv Terminated.\n");
    
          break;
    
        case BLE_GAP_EVT_DISCONNECTED:
    
          SEGGER_RTT_printf(0, "Disconnect Evt.\n");
    
          m_xtag_service.conn_handle = BLE_CONN_HANDLE_INVALID;
    
          // xtag_set_device_state(DEV_STATE_DISCONN); // Update state.
    
          break;
    
        case BLE_GAP_EVT_CONNECTED:
    
          //NRF_LOG_INFO("Connected.");
          SEGGER_RTT_printf(0, "Connect Evt.\n");
          m_xtag_service.conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
    
          // Log the effective mtu.
          SEGGER_RTT_printf(0, "Effective MTU after connection is: %d bytes\n", 
            nrf_ble_gatt_eff_mtu_get(&m_gatt, p_ble_evt->evt.gap_evt.conn_handle));
    
          // Required to work around nordic issue wherin adv stops upon connection. Suggested by Nordic.
          sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
    
          break;
    
        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        
          SEGGER_RTT_printf(0, "Phy update request from central rx %d tx %d.\n", 
            p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys.rx_phys,
            p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys.tx_phys);
    
          // Handle the request that has been initiated in the server and accept the requested rx and tx phys.
          err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys);
          APP_ERROR_CHECK(err_code);
    
          break;
    
        case BLE_GAP_EVT_PHY_UPDATE: // Log only for debugging.
    
          p_phy_evt = &p_ble_evt->evt.gap_evt.params.phy_update;
    
          if (p_phy_evt->status == BLE_HCI_STATUS_CODE_LMP_ERROR_TRANSACTION_COLLISION)
          {
            SEGGER_RTT_printf(0, "Phy update: Trans collision during updated.\n");
            break;
          }
    
          if(p_phy_evt->status == BLE_HCI_STATUS_CODE_SUCCESS)
          {
            SEGGER_RTT_printf(0, "Phy updated: Tx phy: 0x%02X Rx phy: 0x%02X.\n", p_phy_evt->tx_phy, p_phy_evt->rx_phy);
          }
          else
          {
            if(p_phy_evt->status == BLE_HCI_DIFFERENT_TRANSACTION_COLLISION) // Common.
            {
              SEGGER_RTT_printf(0, "Phy update failed due to transaction collision.\n");
            }
            else { SEGGER_RTT_printf(0, "Phy update failed. Status: 0x%02X.\n", p_phy_evt->status); }
          }
    
          break;
    
        case BLE_GAP_EVT_DATA_LENGTH_UPDATE:
    
          p_datalen_evt = &p_ble_evt->evt.gap_evt.params.data_length_update;
    
          SEGGER_RTT_printf(0, "Data length (PDU) update. Max Tx octets: %d, Max Rx octets %d, Max Tx uSec: %d, Max Rx uSec: %d\n",
            p_datalen_evt->effective_params.max_tx_octets, p_datalen_evt->effective_params.max_rx_octets, 
            p_datalen_evt->effective_params.max_tx_time_us, p_datalen_evt->effective_params.max_rx_time_us);
    
        case BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST:
    
          SEGGER_RTT_printf(0, "GATT MTU Request In.\n");
    
          break;
    
        case BLE_GATTS_EVT_HVN_TX_COMPLETE:
    
          // Ensure that our notification busy status is updated to indicate idle and another notification is ok.
          set_notif_busy(false);
    
          // DEBUG
          //SEGGER_RTT_printf(0, "Notification complete.\n");
          
          break;
    
        case BLE_GATTC_EVT_TIMEOUT:
          // Disconnect on GATT Client timeout event.
          SEGGER_RTT_printf(0, "GATT Client Timeout.");
          err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                           BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GATTS_EVT_TIMEOUT:
          // Disconnect on GATT Server timeout event.
          SEGGER_RTT_printf(0, "GATT Server Timeout.");
          err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                           BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GATTS_EVT_WRITE:
        {
          // Incoming data over the rx char.
           ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
    
          // Ensure that we do not overun our buffer.
          if(p_evt_write->len > BLE_MAX_RX_SVC_BYTES)
          {
            SEGGER_RTT_printf(0, "GATT message in %d bytes when max is %d\n", p_evt_write->len, BLE_MAX_RX_SVC_BYTES);
            break;
          }
    
          // Handle notification enable/disable p_evt_write->handle == p_nus->tx_handles.cccd_handle
          if(p_evt_write->handle == m_xtag_service.tx_char_handles.cccd_handle)
          {
            if(p_evt_write->data[0] == 0x01) { SEGGER_RTT_printf(0, "Notifications on tx enabled.\n"); }
            else { SEGGER_RTT_printf(0, "Notifications on tx disabled.\n"); }
    
            break;
          }
    
          // Get the incomming cmd bytes.
          memcpy(rx_char_data, p_evt_write->data, p_evt_write->len);
    
          // Schedule handling on main() via scheduler.
          app_sched_event_put(rx_char_data,  p_evt_write->len, on_main_shed_rx_char_evt_handler);
    
          break;
        }
    
        default:
          // No implementation needed.
          break;
      }
    }
    
    void ble_stack_init(void)
    {
      ret_code_t err_code;
    
      err_code = nrf_sdh_enable_request();
      APP_ERROR_CHECK(err_code);
    
      // Configure the BLE stack using the default settings.
      uint32_t ram_start = 0;
      err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
      APP_ERROR_CHECK(err_code);
    
      // Enable BLE stack. To see required RAM config changes, enable RTT within nrf_sdh_ble.c 
      // (where nrf_sdh_ble_enable() is implemented). Uncomment #include "SEGGER_RTT.h" and logs in nrf_sdh_ble_enable(). 
      err_code = nrf_sdh_ble_enable(&ram_start);
      APP_ERROR_CHECK(err_code);
    
      // Register a handler for BLE events.
      NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
    }
    
    // Gap params init from egs.
    void gap_params_init(void)
    {
      ret_code_t              err_code;
      ble_opt_t               ble_opt;
      ble_gap_addr_t cur_gap_addr;
      ble_gap_conn_params_t   gap_conn_params;
      ble_gap_conn_sec_mode_t sec_mode;
      uint8_t id[BLE_GAP_ADDR_LEN];
    
      // Add extended connection event support to send multiple packets per event.
      memset(&ble_opt, 0x00, sizeof(ble_opt));
      ble_opt.common_opt.conn_evt_ext.enable = 1;
      err_code = sd_ble_opt_set(BLE_COMMON_OPT_CONN_EVT_EXT, &ble_opt);
      APP_ERROR_CHECK(err_code);
    
      // From egs.
      BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
    
      // Get the address from the device.
      err_code = sd_ble_gap_addr_get(&cur_gap_addr);
      APP_ERROR_CHECK(err_code);
    
      // Save off the MAC format as our ID (local and in xtag_data).
      memcpy(id, cur_gap_addr.addr, BLE_GAP_ADDR_LEN);
    
      // Save back ensuring type is public.
      cur_gap_addr.addr_type = BLE_GAP_ADDR_TYPE_PUBLIC;
      sd_ble_gap_addr_set(&cur_gap_addr);
      APP_ERROR_CHECK(err_code);
    
      // Setup and use a device name that includes the ID suffix bytes.
      char name_w_id[strlen(DEVICE_NAME) + DEVICE_NAME_SUFFIX_LEN];
      memset(name_w_id, 0, sizeof(name_w_id));
      sprintf(name_w_id, "%s %02X%02X%02X%02X%02X%02X", DEVICE_NAME, id[5], id[4], id[3], id[2], id[1], id[0]);
      err_code = sd_ble_gap_device_name_set(&sec_mode, (const uint8_t *)name_w_id, strlen(name_w_id));
      APP_ERROR_CHECK(err_code);
    
      // We will expose ourselves as a generic "tag".
      err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_GENERIC_TAG);
      APP_ERROR_CHECK(err_code);
    
      memset(&gap_conn_params, 0, sizeof(gap_conn_params));
    
      gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
      gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
      gap_conn_params.slave_latency     = SLAVE_LATENCY;
      gap_conn_params.conn_sup_timeout  = CONN_SUP_TIMEOUT;
    
      err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
      APP_ERROR_CHECK(err_code);
    }
    
    // Function for handling events from the GATT MODULE. See ble for other GATT events.
    static void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
    {
      switch (p_evt->evt_id)
      {
        case NRF_BLE_GATT_EVT_ATT_MTU_UPDATED:
    
          SEGGER_RTT_printf(0, "ATT MTU updated from central after connection. MTU is %u bytes.\n", 
            p_evt->params.att_mtu_effective);
    
          break;
    
        case NRF_BLE_GATT_EVT_DATA_LENGTH_UPDATED:
    
          SEGGER_RTT_printf(0, "Data length updated from central after connection to %u bytes.\n",
            p_evt->params.data_length);
    
          break;
    
        default:  // Ignore the rest.
    
          break;
      }
    }
    
    /// Init GATT
    void gatt_init(void)
    {
      ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
      APP_ERROR_CHECK(err_code);
    }
    
    // Init services.
    void services_init(ble_os_t * p_xtag_service)
    {
      uint32_t      err_code;
    
      // Setup our custom service for comms.
      ble_uuid_t service_uuid;
    
      // Declare 16-bit service and 128-bit base UUIDs and add them to the BLE stack
      ble_uuid128_t base_uuid = BLE_UUID_XTAG_BASE_UUID;
      service_uuid.uuid = BLE_UUID_XTAG_SERVICE;
      err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);
      APP_ERROR_CHECK(err_code);
    
      // Setup the default handle as per egs.
      p_xtag_service->conn_handle = BLE_CONN_HANDLE_INVALID;
    
      // Add our service
      err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                          &service_uuid,
                                          &p_xtag_service->service_handle);
      APP_ERROR_CHECK(err_code);
    
      SEGGER_RTT_printf(0, "Executing our_service_init().\n"); // Print message to RTT to the application flow
      SEGGER_RTT_printf(0, "Service UUID: 0x%#04x\n", service_uuid.uuid); // Print service UUID should match definition BLE_UUID_OUR_SERVICE
      SEGGER_RTT_printf(0, "Service UUID type: 0x%#02x\n", service_uuid.type); // Print UUID type. Should match BLE_UUID_TYPE_VENDOR_BEGIN. Search for BLE_UUID_TYPES in ble_types.h for more info
      SEGGER_RTT_printf(0, "Service handle: 0x%#04x\n", p_xtag_service->service_handle); // Print out the service handle. Should match service handle shown in MCP under Attribute values
    
      // Add the RX Characteristic. This is taken directly from ble_nus.c to match NUS.
      ble_add_char_params_t add_char_params;
      memset(&add_char_params, 0, sizeof(add_char_params));
      add_char_params.uuid                     = BLE_UUID_XTAG_RX_CHARACTERISTC_UUID;
      add_char_params.uuid_type                = service_uuid.type;
      add_char_params.max_len                  = BLE_MAX_RX_SVC_BYTES;
      add_char_params.init_len                 = sizeof(uint8_t);
      add_char_params.is_var_len               = true;
      add_char_params.char_props.write         = 1;
      add_char_params.char_props.write_wo_resp = 1;
    
      add_char_params.read_access  = SEC_OPEN;
      add_char_params.write_access = SEC_OPEN;
    
      characteristic_add(p_xtag_service->service_handle, &add_char_params, &p_xtag_service->rx_char_handles);
    
      // Add the TX Characteristic. This is taken directly from ble_nus.c to match NUS.
      memset(&add_char_params, 0, sizeof(add_char_params));
      add_char_params.uuid              = BLE_UUID_XTAG_TX_CHARACTERISTC_UUID;
      add_char_params.uuid_type         = service_uuid.type;
      add_char_params.max_len           = BLE_MAX_TX_SVC_BYTES;
      add_char_params.init_len          = sizeof(uint8_t);
      add_char_params.is_var_len        = true;
      add_char_params.char_props.notify = 1;
    
      add_char_params.read_access       = SEC_OPEN;
      add_char_params.write_access      = SEC_OPEN;
      add_char_params.cccd_write_access = SEC_OPEN;
    
      characteristic_add(p_xtag_service->service_handle, &add_char_params, &p_xtag_service->tx_char_handles);
    
      // To support Dev Info Service Profile details.
      ble_dis_init_t dis_init;
    
      // Initialize Device Information Service.
      memset(&dis_init, 0, sizeof(dis_init));
    
      // Setup DIS attributes that we care about. Others null and won't be shown to clients.
      ble_srv_ascii_to_utf8(&dis_init.manufact_name_str, "Deviceworx");
      ble_srv_ascii_to_utf8(&dis_init.model_num_str, "xTAG-BLE-A");
      ble_srv_ascii_to_utf8(&dis_init.hw_rev_str, "1.00.01");
      ble_srv_ascii_to_utf8(&dis_init.fw_rev_str, "1.03.04");
    
      dis_init.dis_char_rd_sec = SEC_OPEN;
    
      err_code = ble_dis_init(&dis_init);
      APP_ERROR_CHECK(err_code);
    
      
    }
    
    // Connection parameters.
    static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
    {
      ret_code_t err_code;
    
      if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
      {
        err_code = sd_ble_gap_disconnect(m_xtag_service.conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
        APP_ERROR_CHECK(err_code);
      }
    }
    
    // Connection parameters error hander.
    static void conn_params_error_handler(uint32_t nrf_error) { APP_ERROR_HANDLER(nrf_error); }
    
    void conn_params_init(void)
    {
      ret_code_t             err_code;
      ble_conn_params_init_t cp_init;
    
      memset(&cp_init, 0, sizeof(cp_init));
    
      cp_init.p_conn_params                  = NULL;
      cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
      cp_init.next_conn_params_update_delay  = NEXT_CONN_PARAMS_UPDATE_DELAY;
      cp_init.max_conn_params_update_count   = MAX_CONN_PARAMS_UPDATE_COUNT;
      cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;
      cp_init.disconnect_on_fail             = false;
      cp_init.evt_handler                    = on_conn_params_evt;
      cp_init.error_handler                  = conn_params_error_handler;
    
      err_code = ble_conn_params_init(&cp_init);
      APP_ERROR_CHECK(err_code);
    }
    
    // Config advertising.
    void advertising_config(void)
    {
      ret_code_t             err_code;
    
      // Setup var to hold ble_advdata_t advert data. 
      ble_advdata_t advdata;
      memset(&advdata, 0, sizeof(advdata));
    
      // Setup var to hold ble_advdata_t scan resp data. 
      ble_advdata_t srdata;
      memset(&srdata, 0, sizeof(srdata));
    
      // Just use srdata to indicate that we want to expose the name provided in gap_params_init().
      srdata.name_type = BLE_ADVDATA_FULL_NAME;
      
      // Setup variable to hold manufacturer specific advert data
      ble_advdata_manuf_data_t mfr_advdata; 
      memset(&mfr_advdata, 0, sizeof(mfr_advdata));
    
      // Update mfr_data and add to advdata.
      uint8_t data[MFR_PAYLOAD_SIZE_GAP];
      for(int x=1; x<MFR_PAYLOAD_SIZE_GAP; x++) { data[x]=x; } // For nordic troubleshooting we don't care about adv content.
    
      mfr_advdata.company_identifier =  0x030E; //Dworx company ID
      mfr_advdata.data.p_data = data;
      mfr_advdata.data.size = sizeof(data);
      advdata.name_type = BLE_ADVDATA_NO_NAME;
      advdata.p_manuf_specific_data = &mfr_advdata;
    
      advdata.include_appearance      = true;
      advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
      advdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
      advdata.uuids_complete.p_uuids  = m_adv_uuids;
    
      // Initialize advertising parameters (used when starting advertising).
      memset(&m_adv_params, 0, sizeof(m_adv_params));
    
      m_adv_params.properties.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED;
      m_adv_params.p_peer_addr     = NULL;    // Undirected advertisement.
      m_adv_params.filter_policy   = BLE_GAP_ADV_FP_ANY;
      m_adv_params.interval        = APP_ADV_INTERVAL;
      m_adv_params.duration        = 950;       // 10 mSec counts - 100 for each sec. Timer restarts every 10 sec, so use 9.5 sec.
    
      // Encode advdata to m_gapData[m_bufIdx]
      err_code = ble_advdata_encode(&advdata, m_gapData[m_bufIdx].adv_data.p_data, (uint16_t *) &m_gapData[m_bufIdx].adv_data.len);
      APP_ERROR_CHECK(err_code);
    
      // Encode srdata to m_gapData[m_bufIdx]
      err_code = ble_advdata_encode(&srdata, m_gapData[m_bufIdx].scan_rsp_data.p_data, (uint16_t *) &m_gapData[m_bufIdx].scan_rsp_data.len);
      APP_ERROR_CHECK(err_code);
     
      err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_gapData[m_bufIdx], &m_adv_params);
      APP_ERROR_CHECK(err_code);
    
      err_code = sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV,m_adv_handle, 4); 
      APP_ERROR_CHECK(err_code);
    }
    
    /**@brief Application main function.
     */
    int main(void)
    {
        bool erase_bonds;
    
        SEGGER_RTT_Init();
        SEGGER_RTT_printf(0, "Peripheral Logging started.\n");
    
        // As per egs, setup the clock driver to use the lfclk (before timers_init)
        APP_ERROR_CHECK(nrf_drv_clock_init());
        nrf_drv_clock_lfclk_request(NULL);
      
        // Additional Inits.
        APP_SCHED_INIT(SCHED_MAX_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE);
        timers_init();
        APP_ERROR_CHECK(nrf_pwr_mgmt_init());
        APP_ERROR_CHECK(nrf_mem_init());
    
        // Alloc RAM.
        m_p_ramstore = (uint8_t*) nrf_malloc(240);
    
        // Setup a pattern of 0 - 239 in the store.
        for(int x=0; x<240; x++) { m_p_ramstore[x] = x; }
    
        // More inits.
        ble_stack_init();
        gap_params_init();
        gatt_init();
        services_init(&m_xtag_service);
        conn_params_init();
    
        advertising_config();
    
        // Start advertising..
        sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
    
        // Start the 100 msec timer.
        APP_ERROR_CHECK(app_timer_start(m_100_msec_timer_id, APP_TIMER_TICKS(100), NULL));
    
        // Enable DCDC mode (low power support in idle mode).
        APP_ERROR_CHECK(sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE));
    
        SEGGER_RTT_printf(0, "BLE peripheral example started!");
    
        // Enter main loop.
        for (;;) 
        { 
          nrf_pwr_mgmt_run(); 
    
          // Handled scheduler actions.
          app_sched_execute();
        }
    }
    
    
    /**
     * @}
     */
    

    /**
     * Copyright (c) 2016 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    #include <stdio.h>
    #include <stdint.h>
    #include <stdbool.h>
    #include "nordic_common.h"
    #include "app_error.h"
    #include "app_uart.h"
    #include "ble_db_discovery.h"
    #include "app_timer.h"
    #include "app_util.h"
    #include "bsp_btn_ble.h"
    #include "ble.h"
    #include "ble_gap.h"
    #include "ble_hci.h"
    #include "nrf_sdh.h"
    #include "nrf_sdh_ble.h"
    #include "nrf_sdh_soc.h"
    #include "ble_nus_c.h"
    #include "nrf_ble_gatt.h"
    #include "nrf_pwr_mgmt.h"
    #include "nrf_ble_scan.h"
    
    #include "nrf_drv_clock.h"
    #include "app_scheduler.h"
    
    #include "SEGGER_RTT.h"
    
    // GUID gen for xTag online - 10c66c52-e5c9-4fe7-8a04-e40b13eb932c
    #define BLE_UUID_XTAG_BASE_UUID             { 0x10, 0xC6, 0x6C, 0x52, 0xE5, 0xC9, 0x4F, 0xE7, 0x8A, 0x04, 0xE4, 0x0B, 0x13, 0xEB, 0x93, 0x2C }
    #define BLE_UUID_XTAG_SERVICE               0x0001 
    #define BLE_UUID_XTAG_RX_CHARACTERISTC_UUID 0x0002
    #define BLE_UUID_XTAG_TX_CHARACTERISTC_UUID 0x0003
    
    // #define BLE_UUID_XTAG_SERVICE   0x0001                         // Must match the service UUID in the xtag.
    #define XTAG_SERVICE_UUID_TYPE  BLE_UUID_TYPE_VENDOR_BEGIN        // UUID type for the xtag svc.
    
    #define APP_BLE_CONN_CFG_TAG    1                                 // Tag that refers to the BLE stack configuration set with @ref sd_ble_cfg_set. The default tag is @ref BLE_CONN_CFG_TAG_DEFAULT. */
    #define APP_BLE_OBSERVER_PRIO   3                                 // BLE observer priority of the application. There is no need to modify this value. */
    
    #define SCAN_INTERVAL           128                               // Scan interval in 625 us units (240 = 150 mSec, 64 = 40 mSec).
    #define SCAN_WINDOW             64                              // Scan window in 625 us units (112 = 70 mSec, 64 = 40 mSec).
    //#define SCAN_DURATION           600                             // Set explicitly via api.
    
    #define MIN_CONN_INTERVAL       MSEC_TO_UNITS(80,UNIT_1_25_MS)   // Min acceptable con int. Def is 7.5 mSec. Set peripherals to match.
    #define MAX_CONN_INTERVAL       MSEC_TO_UNITS(80, UNIT_1_25_MS)   // Max acceptable con int. Def is 30 mSec. Set peripherals to match.
    #define SLAVE_LATENCY           0                                 // Match egs.
    #define CONN_SUP_TIMEOUT        MSEC_TO_UNITS(4000, UNIT_10_MS)   // Match egs.
    
    // Scheduler settings
    #define SCHED_MAX_EVENT_DATA_SIZE   256  // Larger needed for our own stuff.
    #define SCHED_QUEUE_SIZE            32
    
    NRF_BLE_GATT_DEF(m_gatt);                                               /**< GATT module instance. */
    NRF_BLE_SCAN_DEF(m_scan);                                               /**< Scanning Module instance. */
    
    APP_TIMER_DEF(m_10_Sec_timer_id);                   // timer.
    
    // Add to array within xgateway_data.c
    static uint16_t m_rx_char_value_handle = BLE_CONN_HANDLE_INVALID; // Charactoristic VALUE declaration handle.
    static uint16_t m_tx_char_value_handle = BLE_CONN_HANDLE_INVALID; // Charactoristic VALUE declaration handle.
    static uint16_t m_tx_char_descript_handle = BLE_CONN_HANDLE_INVALID; // Charactoristic DESCRIPTOR declaration handle.
    static uint16_t m_tx_handle = BLE_CONN_HANDLE_INVALID;
    
    static ble_gattc_handle_range_t m_char_disco_handle_range;
    
    static uint8_t m_ble_rx_buffer[256];  // Data from BLE peers. Max 244 bytes.
    static uint16_t m_ble_rx_buffer_size;
    
    volatile uint16_t m_num_received_notifs = 0;
    
    // Maximum length of data (in bytes) that can be transmitted to the peer by the Nordic UART service module.
    static uint16_t m_ble_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - OPCODE_LENGTH - HANDLE_LENGTH; 
    
    static ble_uuid_t const m_xtag_svc_uuid =  
    {
        .uuid = BLE_UUID_XTAG_SERVICE,       // Scan only for devices with our xtag service.
        .type = XTAG_SERVICE_UUID_TYPE
    };
    
    static ble_gap_scan_params_t const m_scan_param =
    {
        .active        = 0x01,
        .interval      = SCAN_INTERVAL,
        .window        = SCAN_WINDOW,
        .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL, //BLE_GAP_SCAN_FP_WHITELIST,
        .timeout       = BLE_GAP_SCAN_TIMEOUT_UNLIMITED,
        .scan_phys     = BLE_GAP_PHY_1MBPS,
        .channel_mask[4] = 0x00, // Use 0xC0 if you want to block channels 39 and 38 and only scan 37. 3 chan besat for noisy areas.
    };
    
    static ble_gap_conn_params_t const m_con_param =
    {
      .min_conn_interval  = MIN_CONN_INTERVAL,
      .max_conn_interval  = MAX_CONN_INTERVAL,
      .slave_latency      = SLAVE_LATENCY,
      .conn_sup_timeout   = CONN_SUP_TIMEOUT,
    };
    
    static uint16_t m_notificationCount = 0;
    
    void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
    {
        app_error_handler(0xDEADBEEF, line_num, p_file_name);
    }
    
    static void timer_handler(void* p_context)
    {
      // This handler was used initially to stop scanning in 10 sec - now we stop scanning whenever a device is found and we can connect.
    
      //nrf_ble_scan_stop();
      //SEGGER_RTT_printf(0, "Scanning stopped.\n");
    
      return;
    }
    
    void on_main_ble_sched_handler(void *p_event_data, uint16_t event_size)
    {
      m_num_received_notifs++;
      // Handle incoming notification data that is preset. Note that args p_event_data and size are unused.
      SEGGER_RTT_printf(0, "%d, Recieved notifcation %d with length %d. Starting with data %d %d.\n",m_num_received_notifs, ++m_notificationCount, m_ble_rx_buffer_size, m_ble_rx_buffer[0], m_ble_rx_buffer[1]);
    }
    
    // Fcn that confirms scanned device / peripheral is ours.
    bool ble_adv_contains_mfrid(uint8_t* pAdvADBlocks, uint8_t AdvLen, uint16_t mfrId)
    {
      uint8_t curIdx = 0;
      uint8_t curADLen = 0;
      uint8_t sanityCnt = 0;
      
      // Separate ms and ls bytes in mfrId.
      uint8_t msMfrByte = (uint8_t) ((mfrId >> 8) & 0x00FF);
      uint8_t lsMfrByte = (uint8_t) ((mfrId >> 0) & 0x00FF);
      uint8_t cnt = 0;
    
      // Walk through Adv packet (max len 32 bytes) looking for individual AD blocks by looking at each AD block length, starting at 0.
      while(curIdx < AdvLen)
      {
        // Get the cur AD block len using the idx.
        curADLen = pAdvADBlocks[curIdx];
    
        // Misalign if we ever get a length of zero or beyond AdvLen.
        if((curADLen == 0) || (curIdx + (curADLen + 1)) > AdvLen) { SEGGER_RTT_printf(0, "Bad len %d. Fail out.\n", curADLen); return false; }
        
        // If mfr block with type 0xFF, check mfrId.
        if(pAdvADBlocks[curIdx + 1] == 0xFF)
        {
          // If backwards mfrId match return true, else false.
          if((pAdvADBlocks[curIdx + 2] == lsMfrByte) && (pAdvADBlocks[curIdx + 3] == msMfrByte)) { return true;}
          else { return false; }
        }
        else { curIdx += curADLen + 1; }  // Advance to the next AD block (length in packet does not include its own byte).
    
        // Increase the sanity count and return if it gets high as we cannot stay in here forever.
        if(++sanityCnt > 10) { return false; }
      }
    
      // Should never get here as we should always find an AD block with type 0xFF (mfr data).
      return false;
    }
    
    static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
    {
      ret_code_t            err_code;
      ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
      ble_gap_evt_data_length_update_t const* p_datalen_evt;
    
      switch (p_ble_evt->header.evt_id)
      {
        case BLE_GAP_EVT_ADV_REPORT:
    
          // Look for Dworx Mfr ID 0x030E.
          if(ble_adv_contains_mfrid(p_ble_evt->evt.gap_evt.params.adv_report.data.p_data,
                                        p_ble_evt->evt.gap_evt.params.adv_report.data.len, 0x030E))
          {
            SEGGER_RTT_printf(0, "Scanned xTAG. Will stop scanning and connect.\n");
    
            nrf_ble_scan_stop();
    
            // Connect using the address and other memebers.
            // Connect status in our store will be updated when a connected event comes in.
            err_code = sd_ble_gap_connect(&(p_ble_evt->evt.gap_evt.params.adv_report.peer_addr), &m_scan_param, &m_con_param, APP_BLE_CONN_CFG_TAG);
    
            // Provide status.
            if(err_code != NRF_SUCCESS) 
            {
              if(err_code == NRF_ERROR_CONN_COUNT)
              {
                SEGGER_RTT_printf(0, "Connection error. Max connections exceeded.\n"); 
              }
              else
              {
                SEGGER_RTT_printf(0, "Connection error %d\n", err_code); 
              }
            }
            else 
            { 
              SEGGER_RTT_printf(0, "Connected to xTAG.\n"); 
            }
          }
    
          break;
    
        case BLE_GAP_EVT_CONNECTED:
        {
          SEGGER_RTT_printf(0, "Connected to tag\n");
    
          // Request 1MBPS or 2MBPS or Coded phy on the peripheral. 
          // NOTE: The peripheral phy update request will only go out if it is different than the current connection phy.
          ble_gap_phys_t phys;
          phys.rx_phys = BLE_GAP_PHY_2MBPS;
          phys.tx_phys = BLE_GAP_PHY_2MBPS;
          err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
          APP_ERROR_CHECK(err_code);
    
          // Log the effective mtu.
          SEGGER_RTT_printf(0, "Effective MTU after connection is: %d bytes\n", 
            nrf_ble_gatt_eff_mtu_get(&m_gatt, p_ble_evt->evt.gap_evt.conn_handle));
    
          break;
        }
        case BLE_GAP_EVT_DISCONNECTED:
        {
          uint8_t Idx;
    
          if(p_gap_evt->params.disconnected.reason == BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED)
          {
            SEGGER_RTT_printf(0, "Connection Failed!\n", err_code);
          }
          else
          {
            SEGGER_RTT_printf(0, "Unsolicited disconnection from tag with handle %04X\n", 
             p_ble_evt->evt.gap_evt.conn_handle);
          }
    
          break;
        }
        case BLE_GAP_EVT_TIMEOUT:
    
          //SEGGER_RTT_printf(0, "BLE_GAP_EVT_TIMEOUT\n.");
    
          if (p_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_CONN)
          {
            SEGGER_RTT_printf(0, "Connection Request timed out.\n");
          }
          else if (p_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_SCAN)
          {
            SEGGER_RTT_printf(0, "Scan timed out.\n");
          }
          break;
    
        case BLE_GAP_EVT_PHY_UPDATE:
          
          SEGGER_RTT_printf(0, "Phy updated. rx %d tx %d\n", 
          p_gap_evt->params.phy_update.rx_phy, p_gap_evt->params.phy_update.tx_phy);
    
          // Start service discovery.
          err_code = sd_ble_gattc_primary_services_discover(p_ble_evt->evt.gap_evt.conn_handle, 0x0B, &m_xtag_svc_uuid);
          APP_ERROR_CHECK(err_code);
    
          break;
    
        case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
    
          // Pairing not supported.
          err_code = sd_ble_gap_sec_params_reply(p_ble_evt->evt.gap_evt.conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
            
          // Accepting parameters requested by peer.
          SEGGER_RTT_printf(0, "Connection param update from peer. Min 1.25 mSec units %d. Max 1.25 mSec units %d\n", 
            p_gap_evt->params.conn_param_update_request.conn_params.min_conn_interval,
            p_gap_evt->params.conn_param_update_request.conn_params.max_conn_interval);
    
          err_code = sd_ble_gap_conn_param_update(p_gap_evt->conn_handle,
                                                  &p_gap_evt->params.conn_param_update_request.conn_params);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GAP_EVT_CONN_PARAM_UPDATE:
    
          SEGGER_RTT_printf(0, "Connection param update. Min 1.25 mSec units %d. Max 1.25 mSec units %d\n", 
            p_gap_evt->params.conn_param_update.conn_params.min_conn_interval,
            p_gap_evt->params.conn_param_update.conn_params.max_conn_interval);
    
            break;
          
        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        {
          // We don't handle peripheral requests. We make the request after connection.
          SEGGER_RTT_printf(0, "PHY update request in. Will ignore requested rx phy %d and tx phy %d.\n",
           p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys.rx_phys,
           p_ble_evt->evt.gap_evt.params.phy_update_request.peer_preferred_phys.tx_phys);
    
          break;
        }
    
        case BLE_GAP_EVT_DATA_LENGTH_UPDATE:
        {
          p_datalen_evt = &p_ble_evt->evt.gap_evt.params.data_length_update;
    
          SEGGER_RTT_printf(0, "Data length (PDU) update. Max Tx octets: %d, Max Rx octets %d, Max Tx uSec: %d, Max Rx uSec: %d\n",
            p_datalen_evt->effective_params.max_tx_octets, p_datalen_evt->effective_params.max_rx_octets, 
            p_datalen_evt->effective_params.max_tx_time_us, p_datalen_evt->effective_params.max_rx_time_us);
    
            break;
        }
    
        case BLE_GATTC_EVT_TIMEOUT:
            
          // Disconnect on GATT Client timeout event.
          SEGGER_RTT_printf(0, "GATT Client Timeout.\n");
          err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                           BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GATTS_EVT_TIMEOUT:
            
          // Disconnect on GATT Server timeout event.
          SEGGER_RTT_printf(0, "GATT Server Timeout.\n");
          err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                           BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
          APP_ERROR_CHECK(err_code);
          break;
    
        case BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP :
        {
          ble_gattc_evt_t const * p_gatt_evt = &p_ble_evt->evt.gattc_evt;
          uint16_t num_svcs = p_gatt_evt->params.prim_srvc_disc_rsp.count;
    
          // Confirm status and at least 1 service was found.
          if((p_gatt_evt->gatt_status != NRF_SUCCESS) || (num_svcs < 1))
          {
            SEGGER_RTT_printf(0, "GATT Svc resp with bad status of %d or num svcs of %d.\n",
                p_gatt_evt->gatt_status, num_svcs);
            break;
          }
    
          // If the UUID does not match - ignore. We should only get 1 svc.
          if(p_gatt_evt->params.prim_srvc_disc_rsp.services[0].uuid.uuid != BLE_UUID_XTAG_SERVICE)
          {
            SEGGER_RTT_printf(0, "GATT Svc disco resp with bad UUID of %d\n",
                p_gatt_evt->params.prim_srvc_disc_rsp.services[0].uuid.uuid);
            break;
          }
    
          for(int x=0; x<num_svcs; x++) { SEGGER_RTT_printf(0, "Primary svc found with UUID %d, handle range start %d and end %d.\n", 
                p_gatt_evt->params.prim_srvc_disc_rsp.services[x].uuid.uuid,
                p_gatt_evt->params.prim_srvc_disc_rsp.services[x].handle_range.start_handle,
                p_gatt_evt->params.prim_srvc_disc_rsp.services[x].handle_range.end_handle); }
    
          // Setup handle ranges to look for chars on the server.
          m_char_disco_handle_range.start_handle = p_gatt_evt->params.prim_srvc_disc_rsp.services[0].handle_range.start_handle;
          m_char_disco_handle_range.end_handle = p_gatt_evt->params.prim_srvc_disc_rsp.services[0].handle_range.end_handle;
    
          // Clear handles. These will be setup within charactoristic (rx) and descriptor (tx) disco event handling.
          m_rx_char_value_handle = BLE_CONN_HANDLE_INVALID;
          m_tx_char_value_handle = BLE_CONN_HANDLE_INVALID;
    
          // Results in BLE_GATTC_EVT_CHAR_DISC_RSP event (case below).
          err_code = sd_ble_gattc_characteristics_discover(p_gatt_evt->conn_handle, &m_char_disco_handle_range);
          APP_ERROR_CHECK(err_code);
    
          break;
        }
    
        case BLE_GATTC_EVT_CHAR_DISC_RSP:
        {
          // Look through charactoristics and retrieve char declaration and char value declaration handles as req'd.
          // Critically - for each char, the UUID set will be for the char value declaration and not the char declaration
          // as the char declaration UUID is always static (0x2803).
    
          // Setup reusable vars.
          ble_gattc_evt_t const * p_gatt_evt = &p_ble_evt->evt.gattc_evt;
          uint16_t num_chars = p_gatt_evt->params.char_disc_rsp.count;
    
          // DEBUG
          SEGGER_RTT_printf(0, "GATT char disco with %d chars\n", num_chars);
    
          // Confirm status
          if(p_gatt_evt->gatt_status != NRF_SUCCESS)
          {
            SEGGER_RTT_printf(0, "GATT char disco resp with bad status of %d.\n", p_gatt_evt->gatt_status);
    
            break;
          }
    
          // Loop through chars looking for our Rx UUID
          ble_gattc_char_t idx_char;
          for(int x=0; x<num_chars; x++) 
          {
            // Setup indexed charactoristic.
            idx_char = p_gatt_evt->params.char_disc_rsp.chars[x];
    
            SEGGER_RTT_printf(0, "GATT char disco with UUID %04X and declaration handle %04X\n", idx_char.uuid.uuid, idx_char.handle_decl);
    
            if(idx_char.uuid.uuid == BLE_UUID_XTAG_RX_CHARACTERISTC_UUID) 
            {
              // Store the handle for subsequent writes.
              m_rx_char_value_handle = idx_char.handle_value;
    
              // Increment the start handle for subsequent char discoveries.
              m_char_disco_handle_range.start_handle = idx_char.handle_decl + 1;
            }
            else if(idx_char.uuid.uuid == BLE_UUID_XTAG_TX_CHARACTERISTC_UUID)
            {
              // Set the m_tx_char_value_handle to match the char value handle.
              m_tx_char_value_handle = idx_char.handle_value; 
    
              // We can start to look for the descriptor for this char.
              // ... handled within the BLE_GATTC_EVT_DESC_DISC_RSP event case that follows.
              // The descriptor handle should follow the char value declaration handle. Set start_handle.
              m_char_disco_handle_range.start_handle = idx_char.handle_value + 1;
              err_code = sd_ble_gattc_descriptors_discover(p_gatt_evt->conn_handle, &m_char_disco_handle_range);
              APP_ERROR_CHECK(err_code);
            }
            else
            {
              // Just inc the start_handle if ther is no match.
              m_char_disco_handle_range.start_handle = idx_char.handle_decl + 1;
            }
          }
    
          // If either handle is not set ...
          if( (m_rx_char_value_handle == BLE_CONN_HANDLE_INVALID) || (m_tx_char_value_handle == BLE_CONN_HANDLE_INVALID))
          {
            // If we are at the end of the handles, give up.
            if(m_char_disco_handle_range.start_handle > m_char_disco_handle_range.end_handle)
            {
              SEGGER_RTT_printf(0, "GATT char disco resp with missing UUIDs.\n");
            }
            else
            {
              // Try again using the updated handle range.
              err_code = sd_ble_gattc_characteristics_discover(p_gatt_evt->conn_handle, &m_char_disco_handle_range);
              APP_ERROR_CHECK(err_code);
            }
          }
          
          break;
        }
    
        case BLE_GATTC_EVT_DESC_DISC_RSP:
        {
          // Setup reusable vars.
          ble_gattc_evt_t const * p_gatt_evt = &p_ble_evt->evt.gattc_evt;
          uint16_t num_descriptors = p_gatt_evt->params.desc_disc_rsp.count;
    
          // DEBUG
          SEGGER_RTT_printf(0, "GATT descriptor disco with %d descriptors\n", num_descriptors);
    
          // Confirm status
          if(p_gatt_evt->gatt_status != NRF_SUCCESS)
          {
            SEGGER_RTT_printf(0, "GATT descriptor disco resp with bad status of %d.\n", p_gatt_evt->gatt_status);
    
            break;
          }
    
          // Loop through descriptors looking for our CCCD UUID. Because we setup this event with the handle that
          // should match our tx char descriptor, we should only get 1 event and it should match.
          ble_gattc_desc_t idx_descriptor;
          for(int x=0; x<num_descriptors; x++) 
          {
            SEGGER_RTT_printf(0, "GATT descriptor disco with UUID %04X and handle %04X\n", 
              p_gatt_evt->params.desc_disc_rsp.descs[x].uuid.uuid,
              p_gatt_evt->params.desc_disc_rsp.descs[x].handle );
    
            if(p_gatt_evt->params.desc_disc_rsp.descs[x].uuid.uuid == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG)
            {
              // Simply use the descriptor handle to write a value to enable notificatins on the tx char.
              // First - create write_params and and a byte array to write.
              ble_gattc_write_params_t write_params;
              memset(&write_params, 0, sizeof(write_params));
              m_tx_char_descript_handle = p_gatt_evt->params.desc_disc_rsp.descs[x].handle;
    
              uint8_t cccd_value[2] = { BLE_GATT_HVX_NOTIFICATION, 0 };       // 1 to enable notifications.
    
              write_params.handle = m_tx_char_descript_handle;
              write_params.len = 2;
              write_params.p_value = cccd_value;
              write_params.write_op = BLE_GATT_OP_WRITE_REQ;
              write_params.offset = 0;
              write_params.flags = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE;
    
              err_code = sd_ble_gattc_write(p_gatt_evt->conn_handle, &write_params);
              APP_ERROR_CHECK(err_code);
    
              return; // Only need the 1 descriptor.
            }
          }
    
          // If we get here, there is an issue finding our descriptor.
          SEGGER_RTT_printf(0, "GATT descriptor disco fail. No cccd found\n");
          
          break;
        }
    
        case BLE_GATTC_EVT_WRITE_RSP:
        {
          // Setup reusable vars.
          ble_gattc_evt_t const * p_gatt_evt = &p_ble_evt->evt.gattc_evt;
    
          // Handle descripter var write responses.
          if(p_gatt_evt->params.write_rsp.handle == m_tx_char_descript_handle)
          {
            // Notification setup complete. If the other handles are valid, connection is complete.
            if((m_rx_char_value_handle != BLE_CONN_HANDLE_INVALID) && (m_tx_char_value_handle != BLE_CONN_HANDLE_INVALID))
            {
              // Store the handle for tx. Wont be used as we will handle notifications only.
              m_tx_handle = p_gatt_evt->conn_handle;
    
              // DEBUG
              SEGGER_RTT_printf(0, "Connect success with handle %04X. Sending cmd to start notifications\n", p_gatt_evt->conn_handle);
    
              // Send simple made up Cmd to start data notifications. 
              uint8_t cmd_len = 2;
              uint8_t cmdBuf[cmd_len];
              cmdBuf[0] = 0xAA;    
              cmdBuf[1] = 0xBB;   
    
              // Setup a ble_gattc_write_params_t for the write.
              ble_gattc_write_params_t write_params;
              memset(&write_params, 0, sizeof(write_params));
              write_params.handle = m_rx_char_value_handle;
              write_params.len = cmd_len;
              write_params.p_value = cmdBuf;
              write_params.write_op = BLE_GATT_OP_WRITE_CMD;
              write_params.offset = 0;
    
              // Actually send the cmd.
              err_code = sd_ble_gattc_write(m_tx_handle, &write_params);
              APP_ERROR_CHECK(err_code);
    
            }
            else
            {
              SEGGER_RTT_printf(0, "Connect fail. GATT Notification ok but error on rx or tx char handle.\n");
            }
          }
    
          break;
        }
    
        case BLE_GATTC_EVT_HVX:
        {
          // Setup reusable vars.
          ble_gattc_evt_t const * p_gatt_evt = &(p_ble_evt->evt.gattc_evt);
    
          // Setup data for subsequent processing on main.
          memcpy_fast(m_ble_rx_buffer, p_gatt_evt->params.hvx.data, (size_t) p_gatt_evt->params.hvx.len);
          m_ble_rx_buffer_size = p_gatt_evt->params.hvx.len;
    
          // Schedule an event on the main thread to process the response.
          app_sched_event_put(NULL,  0, on_main_ble_sched_handler);
    
          break;
        }
    
        default:
    
            break;
      }
    }
    
    /**@brief Function for handling events from the GATT library. */
    void ble_gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
    {
      ret_code_t err_code;
    
      if (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED)
      {
        // Our NRF_SDH_BLE_GAP_DATA_LENGTH is setup as 251 in sdk_config.h. This is the PDU max length.
        // After connection, the central tries to update the MTU in the peripheral using NRF_SDH_BLE_GAP_DATA_LENGTH.
        // If successful, we get the ATT header (3 byte opcode and handle) and 244 byte ATT body back as 247 total or mtu effective. 
        // We can only use the ATT body, so set m_ble_max_data_len to that.
        // Good info here: https://punchthrough.com/maximizing-ble-throughput-part-3-data-length-extension-dle-2/
        m_ble_max_data_len = p_evt->params.att_mtu_effective - 3; // 1 Byte for att opcode and 2 for att handle (3 byte att header).
        SEGGER_RTT_printf(0, "BLE GATT ATT MTU update done. MTU effective in %d. Max data len set to %d\n",
          p_evt->params.att_mtu_effective, m_ble_max_data_len);
      }
      else if(p_evt->evt_id == NRF_BLE_GATT_EVT_DATA_LENGTH_UPDATED)
      {
        SEGGER_RTT_printf(0, "BLE GATT ATT data length update done. Event data len %d\n", p_evt->params.data_length);
      }
    }
    
    void ble_init(void)
    {
      ret_code_t err_code;
      ble_opt_t ble_opt;
    
      ///////////////////////////////////////////////////////////////////
      // First - init the stack /////////////////////////////////////////
      ///////////////////////////////////////////////////////////////////
    
      // Init BLE with NUS. BLE stuff in xgateway_bluetooth.*
      err_code = nrf_sdh_enable_request();
      APP_ERROR_CHECK(err_code);
    
      // Configure the BLE stack using the default settings.
      // Fetch the start address of the application RAM.
      uint32_t ram_start = 0;
      err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
      APP_ERROR_CHECK(err_code);
    
      // Enable BLE stack.
      err_code = nrf_sdh_ble_enable(&ram_start);
      APP_ERROR_CHECK(err_code);
    
      // Register a handler for BLE events.
      NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
    
      ///////////////////////////////////////////////////////////////////
      // End stack init.
      ///////////////////////////////////////////////////////////////////
    
      // GAP - Add extended connection event support to send multiple packets per event.
      memset(&ble_opt, 0x00, sizeof(ble_opt));
      ble_opt.common_opt.conn_evt_ext.enable = 1;
      err_code = sd_ble_opt_set(BLE_COMMON_OPT_CONN_EVT_EXT, &ble_opt);
      APP_ERROR_CHECK(err_code);
    
      ///////////////////////////////////////////////////////////////////
      // GATT init            
      ///////////////////////////////////////////////////////////////////
    
      err_code = nrf_ble_gatt_init(&m_gatt, ble_gatt_evt_handler);
      APP_ERROR_CHECK(err_code);
    
      // Declare 16-bit service and 128-bit base UUIDs and add them to the BLE stack
      ble_uuid_t service_uuid;
      ble_uuid128_t base_uuid = BLE_UUID_XTAG_BASE_UUID;
      service_uuid.uuid = BLE_UUID_XTAG_SERVICE;
      err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);
      APP_ERROR_CHECK(err_code);
    
      ///////////////////////////////////////////////////////////////////
      // End GATT init             
      ///////////////////////////////////////////////////////////////////
    
      // Init scan and set params explicitly. Both always return NRF_SUCCESS
      nrf_ble_scan_init(&m_scan, NULL, NULL);
      nrf_ble_scan_params_set(&m_scan, &m_scan_param);
    }
    
    int main(void)
    {
        // Initialize.
        SEGGER_RTT_Init();
        SEGGER_RTT_printf(0, "Central Logging started.\n");
    
        // As per egs, setup the clock driver to use the lfclk (before timers_init)
        APP_ERROR_CHECK(nrf_drv_clock_init());
        nrf_drv_clock_lfclk_request(NULL);
    
        APP_SCHED_INIT(SCHED_MAX_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE);
    
        // Init timers.
        APP_ERROR_CHECK(app_timer_init());
        APP_ERROR_CHECK(app_timer_create(&m_10_Sec_timer_id, APP_TIMER_MODE_SINGLE_SHOT , timer_handler));
    
        APP_ERROR_CHECK(nrf_pwr_mgmt_init());
    
        ble_init();
    
        SEGGER_RTT_printf(0, "BLE central example started! Will try to scan peer in the next 10 sec ...\n");
    
        // Start scanning.
        nrf_ble_scan_start(&m_scan);
    
        // Start timeer with handler that will stop scanning.
        APP_ERROR_CHECK(app_timer_start(m_10_Sec_timer_id, APP_TIMER_TICKS(10000), NULL));
    
        // Enter main loop.
        for (;;) 
        { 
          app_sched_execute();
    
          // Manage power.
          nrf_pwr_mgmt_run(); 
        }
    }
    

    The peripheral will now print "num sent %d", where %d is the number of successfully sent packets. The central will print:

    374, Recieved notifcation 374 with length 240. Starting with data 151 152.

    just like before, but I added the first number, indicating the number of received notifications. 

    When I run the central and peripheral, I see that these numbers are always the same, so no notifications are lost. In my case, that was 382 successful notifications both sent and received. Is the issue that you mean that this number should be higher? If so, what I have been trying to say is that when sd_ble_gatts_hvx() returns NRF_ERROR_RESOURCES, the packet is not queued. You need to wait for the next TX_COMPLETE event before you try to queue it again. Even then, there is no guarantee that it will return NRF_SUCCESS, based on the size of the packet that was Acked (causing the TX_COMPLETE), and the size of the packet you are trying to queue.

    Check out and test the attached main.c files, and let me know if I misunderstood.

Children
  • Hi @Edvin,

    Apologies for not replying sooner .... I managed to resolve an issue and optimize comms. My issue was quite simple. I was assuming packets were being delivered - even when NRF_ERROR_RESOURCES was being returned. Now, when I get NRF_ERROR_RESOURCES returned from sd_ble_gatts_hvx(), I am waiting for a TX_COMPLETE event and then I am retrying send OF THE SAME PACKET using another sd_ble_gatts_hvx() call. After making these changes, I am not seeing any packet loss. On occasion, I am getting my data streaming backed up waiting for TX_COMPLETE events. This, however, quite rare at the data rate described within this thread (specifics below). I have selected the following comms parameters and setup to optimize throughput over 4 connections.

    Set NRF_SDH_BLE_GAP_EVENT_LENGTH to 7 - 1.25 mSec counts or 8.75 mSec. Given the packet max rx and tx times logged (and discussed above) of 2120 uSec or 2.12 mSec, this event time supports up to 4 packets (4 * 2.12 = 8.48 mSec). My logging indicates that I am seeing up to 4 packets come in at once (usually 2).

    Set my connection interval to 40 mSec to support 4 of these connection events per interval.

    Increased GATT MTU and GAP data length as discussed in this thread to 251 each.

    Enabled connection event length extension as discussed in this thread (to get up to 4 packets per connection event).

    Whenever my packet transmission gets backed up too far, I am simply deleting some data and reporting this deletion on my data stream. This is rare. Think 30 mSec of data stream data deletion once every 3-4 min.

    So, I have taken a solution as far as I can.

    Thanks for your help and with your permission - this thread can be closed off. Not sure if you would suggest this as an answer or if I should or ?

    Regards,

    Mark J

  • Sorry for the late reply. I have been out of office for a couple of weeks. 

    This looks reasonable. Supporting 4 but usually needing 2 packets per connection interval sounds good. In that case, you support a moderate amount of packet loss. This means that you can miss two packets in a connection event, and they will be retransmitted by the softdevice in the next connection event, and your application will not notice. First when you queue up packets and they return NRF_ERROR_RESOURCES it probably means that you have had some lost packets on air, and hence your queue is already full. Remember that the softdevice will handle all retransmissions of packets that have been queued successfully (sd_ble_gatts_hvx() returned NRF_SUCCESS). 

    I have marked your answer as the answer. If you verify it, it will be the answer for this ticket, making it easier for other users to find the answer if they stumble upon this thread.

    Best regards,

    Edvin

Related