Dual Roles BLEUART

If you are not familiar with Central Role, it is advised to look at the "Central BLEUART" example first then continue with this afterwards.

This example demonstrates how you can use a Feather nRF52/nRF52840 to connect to two other Bluefruit or BLE devices using the bleuart (AKA 'NUS') service concurrently, with the device running at both a peripheral and a central at the same time.

This dual role example acts as a BLE bridge that sits between a central and a peripheral forwarding bleuart messages back and forth, as shown in the image below:

Server & Client Service Setup

Since the Bluefruit device will act as both a central and a peripheral, we will need to declare both server and client instance of the bleuart helper class:

Download: file
// Peripheral uart service
BLEUart bleuart;

// Central uart client
BLEClientUart clientUart;

Before we can configure client services, Bluefruit.begin() must be called with at least 1 for the number of concurrent connection for both peripheral and central mode:

Download: file
// Initialize Bluefruit with max concurrent connections as Peripheral = 1, Central = 1
Bluefruit.begin(1, 1);

After this, client services must be initialized by calling their begin() function, followed by any callbacks that you wish to wire up as well:

Download: file
// Configure and Start BLE Uart Service
bleuart.begin();
bleuart.setRxCallback(prph_bleuart_rx_callback);

// Init BLE Central Uart Serivce
clientUart.begin();
clientUart.setRxCallback(cent_bleuart_rx_callback);

We are then ready to forward data from central to peripheral and vice versa using callbacks:

Download: file
void cent_bleuart_rx_callback(BLEClientUart& cent_uart)
{
  char str[20+1] = { 0 };
  cent_uart.read(str, 20);
      
  Serial.print("[Cent] RX: ");
  Serial.println(str);

  if ( bleuart.notifyEnabled() )
  {
    // Forward data from our peripheral to Mobile
    bleuart.print( str );
  }else
  {
    // response with no prph message
    clientUart.println("[Cent] Peripheral role not connected");
  }  
}

void prph_bleuart_rx_callback(void)
{
  // Forward data from Mobile to our peripheral
  char str[20+1] = { 0 };
  bleuart.read(str, 20);

  Serial.print("[Prph] RX: ");
  Serial.println(str);  

  if ( clientUart.discovered() )
  {
    clientUart.print(str);
  }else
  {
    bleuart.println("[Prph] Central role not connected");
  }
}

Peripheral Role

The first thing to do for the peripheral part of our code is to setup the connect callback, which fires when a connection is established/disconnected with the central. Alternatively you could poll the connection status with connected(), but callbacks helps to simplify the code significantly:

Download: file
// Callbacks for Peripheral
Bluefruit.setConnectCallback(prph_connect_callback);
Bluefruit.setDisconnectCallback(prph_disconnect_callback);

Central Role

Next we setup the Central mode connect callback, which fires when a connection is established/disconnected with a peripheral device:

Download: file
// Callbacks for Central
Bluefruit.Central.setConnectCallback(cent_connect_callback);
Bluefruit.Central.setDisconnectCallback(cent_disconnect_callback);

Advertising and Scanner

It is possible to start both the scanner and advertising at the same time so that we can discover and be discovered by other BLE devices. For the scanner, we use a filter that only fires the callback if a specific UUID is found in the advertising data of the peer device:

Download: file
/* Start Central Scanning
 * - Enable auto scan if disconnected
 * - Interval = 100 ms, window = 80 ms
 * - Filter only accept bleuart service
 * - Don't use active scan
 * - Start(timeout) with timeout = 0 will scan forever (until connected)
 */
Bluefruit.Scanner.setRxCallback(scan_callback);
Bluefruit.Scanner.restartOnDisconnect(true);
Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
Bluefruit.Scanner.filterUuid(bleuart.uuid);
Bluefruit.Scanner.useActiveScan(false);
Bluefruit.Scanner.start(0);                   // 0 = Don't stop scanning after n seconds

// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();

// Include bleuart 128-bit uuid
Bluefruit.Advertising.addService(bleuart);

// Secondary Scan Response packet (optional)
// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.addName();

/* Start Advertising
 * - Enable auto advertising if disconnected
 * - Interval:  fast mode = 20 ms, slow mode = 152.5 ms
 * - Timeout for fast mode is 30 seconds
 * - Start(timeout) with timeout = 0 will advertise forever (until connected)
 *
 * For recommended advertising interval
 * https://developer.apple.com/library/content/qa/qa1931/_index.html
 */
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244);    // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds

Full Sample Code

The full sample code for this example can be seen below:

/*********************************************************************
 This is an example for our nRF52 based Bluefruit LE modules

 Pick one up today in the adafruit shop!

 Adafruit invests time and resources providing this open source code,
 please support Adafruit and open-source hardware by purchasing
 products from Adafruit!

 MIT license, check LICENSE for more information
 All text above, and the splash screen below must be included in
 any redistribution
*********************************************************************/

/*
 * This sketch demonstrate how to run both Central and Peripheral roles
 * at the same time. It will act as a relay between an central (mobile)
 * to another peripheral using bleuart service.
 * 
 * Mobile <--> DualRole <--> peripheral Ble Uart
 */
#include <bluefruit.h>

// OTA DFU service
BLEDfu bledfu;

// Peripheral uart service
BLEUart bleuart;

// Central uart client
BLEClientUart clientUart;

void setup()
{
  Serial.begin(115200);
  while ( !Serial ) delay(10);   // for nrf52840 with native usb

  Serial.println("Bluefruit52 Dual Role BLEUART Example");
  Serial.println("-------------------------------------\n");
  
  // Initialize Bluefruit with max concurrent connections as Peripheral = 1, Central = 1
  // SRAM usage required by SoftDevice will increase with number of connections
  Bluefruit.begin(1, 1);
  Bluefruit.setTxPower(4);    // Check bluefruit.h for supported values
  Bluefruit.setName("Bluefruit52 duo");

  // Callbacks for Peripheral
  Bluefruit.Periph.setConnectCallback(prph_connect_callback);
  Bluefruit.Periph.setDisconnectCallback(prph_disconnect_callback);

  // Callbacks for Central
  Bluefruit.Central.setConnectCallback(cent_connect_callback);
  Bluefruit.Central.setDisconnectCallback(cent_disconnect_callback);

  // To be consistent OTA DFU should be added first if it exists
  bledfu.begin();

  // Configure and Start BLE Uart Service
  bleuart.begin();
  bleuart.setRxCallback(prph_bleuart_rx_callback);

  // Init BLE Central Uart Serivce
  clientUart.begin();
  clientUart.setRxCallback(cent_bleuart_rx_callback);


  /* Start Central Scanning
   * - Enable auto scan if disconnected
   * - Interval = 100 ms, window = 80 ms
   * - Filter only accept bleuart service
   * - Don't use active scan
   * - Start(timeout) with timeout = 0 will scan forever (until connected)
   */
  Bluefruit.Scanner.setRxCallback(scan_callback);
  Bluefruit.Scanner.restartOnDisconnect(true);
  Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
  Bluefruit.Scanner.filterUuid(bleuart.uuid);
  Bluefruit.Scanner.useActiveScan(false);
  Bluefruit.Scanner.start(0);                   // 0 = Don't stop scanning after n seconds

  // Set up and start advertising
  startAdv();
}

void startAdv(void)
{
  // Advertising packet
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();

  // Include bleuart 128-bit uuid
  Bluefruit.Advertising.addService(bleuart);

  // Secondary Scan Response packet (optional)
  // Since there is no room for 'Name' in Advertising packet
  Bluefruit.ScanResponse.addName();

  /* Start Advertising
   * - Enable auto advertising if disconnected
   * - Interval:  fast mode = 20 ms, slow mode = 152.5 ms
   * - Timeout for fast mode is 30 seconds
   * - Start(timeout) with timeout = 0 will advertise forever (until connected)
   *
   * For recommended advertising interval
   * https://developer.apple.com/library/content/qa/qa1931/_index.html
   */
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244);    // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds
}

void loop()
{
  // do nothing, all the work is done in callback
}

/*------------------------------------------------------------------*/
/* Peripheral
 *------------------------------------------------------------------*/
void prph_connect_callback(uint16_t conn_handle)
{
  // Get the reference to current connection
  BLEConnection* connection = Bluefruit.Connection(conn_handle);

  char peer_name[32] = { 0 };
  connection->getPeerName(peer_name, sizeof(peer_name));

  Serial.print("[Prph] Connected to ");
  Serial.println(peer_name);
}

void prph_disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) conn_handle;
  (void) reason;

  Serial.println();
  Serial.println("[Prph] Disconnected");
}

void prph_bleuart_rx_callback(uint16_t conn_handle)
{
  (void) conn_handle;
  
  // Forward data from Mobile to our peripheral
  char str[20+1] = { 0 };
  bleuart.read(str, 20);

  Serial.print("[Prph] RX: ");
  Serial.println(str);  

  if ( clientUart.discovered() )
  {
    clientUart.print(str);
  }else
  {
    bleuart.println("[Prph] Central role not connected");
  }
}

/*------------------------------------------------------------------*/
/* Central
 *------------------------------------------------------------------*/
void scan_callback(ble_gap_evt_adv_report_t* report)
{
  // Since we configure the scanner with filterUuid()
  // Scan callback only invoked for device with bleuart service advertised  
  // Connect to the device with bleuart service in advertising packet  
  Bluefruit.Central.connect(report);
}

void cent_connect_callback(uint16_t conn_handle)
{
  // Get the reference to current connection
  BLEConnection* connection = Bluefruit.Connection(conn_handle);

  char peer_name[32] = { 0 };
  connection->getPeerName(peer_name, sizeof(peer_name));

  Serial.print("[Cent] Connected to ");
  Serial.println(peer_name);;

  if ( clientUart.discover(conn_handle) )
  {
    // Enable TXD's notify
    clientUart.enableTXD();
  }else
  {
    // disconnect since we couldn't find bleuart service
    Bluefruit.disconnect(conn_handle);
  }  
}

void cent_disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) conn_handle;
  (void) reason;
  
  Serial.println("[Cent] Disconnected");
}

/**
 * Callback invoked when uart received data
 * @param cent_uart Reference object to the service where the data 
 * arrived. In this example it is clientUart
 */
void cent_bleuart_rx_callback(BLEClientUart& cent_uart)
{
  char str[20+1] = { 0 };
  cent_uart.read(str, 20);
      
  Serial.print("[Cent] RX: ");
  Serial.println(str);

  if ( bleuart.notifyEnabled() )
  {
    // Forward data from our peripheral to Mobile
    bleuart.print( str );
  }else
  {
    // response with no prph message
    clientUart.println("[Cent] Peripheral role not connected");
  }  
}
This guide was first published on Mar 22, 2017. It was last updated on Mar 22, 2017. This page (Dual Roles BLEUART) was last updated on Sep 17, 2019.