We have bots (what I'm calling our device for this question) that play both the peripheral and central role depending on their context (not unlike the relay example; except these bots talk to each other, not other devices).
In our code, we do the following:
-
Scan
-
Advertise Non connectable
-
Bot1 indicates it wants to go connect (GAP PERIPHERAL)
-
Change to connectable advertising
-
Bot 2 connects (GAP CENTRAL)
-
Bot 1 and 2 do some things, and then each bot disconnects (no service discovery at this point).
-
at disconnect, each bot starts scanning and advertising again (non-connectable).
-
Bot 1 (the GAP Peripheral) disconnects,
ble_conn_state_status_t
is 1, forBLE_CONN_STATUS_DISCONNECTED
. -
When Bot 1 tries to start advertising again, it gets NRF_INVALID_STATE both when I try to stop and start scanning.
Relevant is that we're using flash data storage to store some data right before disconnect; both devices do it, but the central seems to record far fewer flash operations than the peripheral; which seems strange since they're doing the same thing.
Here's the code:
static void on_ble_peripheral_evt(ble_evt_t *p_ble_evt) {
uint32_t err_code;
switch (p_ble_evt->header.evt_id) {
case BLE_GAP_EVT_CONNECTED: {
m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
const ble_gap_addr_t *peer_addr = &p_ble_evt->evt.gap_evt.params.connected.peer_addr;
NRF_LOG("PERIPHERAL: CONNECTED\r\n");
start_advertising(ADV_MODE_NONCONNECTABLE);
break;
}
case BLE_GAP_EVT_DISCONNECTED:
NRF_LOG("PERIPHERAL: DISCONNECTED\r\n");
m_gap_role = BLE_GAP_ROLE_INVALID;
start_advertising(ADV_MODE_NONCONNECTABLE);
start_delayed_scanning();
break;
case BLE_GATTS_EVT_SYS_ATTR_MISSING:
err_code = sd_ble_gatts_sys_attr_set(m_conn_handle, NULL, 0, 0);
APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_TIMEOUT:
NRF_LOG("BLE_GAP_EVT_TIMEOUT\r\n");
start_scanning();
start_advertising(ADV_MODE_NONCONNECTABLE);
default:
// No implementation needed.
break;
}
}
void on_ble_central_evt(const ble_evt_t *const p_ble_evt) {
const ble_gap_evt_t *p_gap_evt = &p_ble_evt->evt.gap_evt;
switch (p_ble_evt->header.evt_id) {
case BLE_GAP_EVT_CONNECTED: {
const ble_gap_addr_t *peer_addr = &p_gap_evt->params.connected.peer_addr;
m_conn_handle = p_gap_evt->conn_handle;
NRF_LOG("CENTRAL: CONNECTED\r\n");
if (m_conn_handle != BLE_CONN_HANDLE_INVALID && !friend_adding_mode()) {
uint32_t err_code =
ble_db_discovery_start(&m_ble_db_discovery_aas, m_conn_handle);
NRF_LOG_PRINTF("STARTING DISCOVERY: %u\r\n", err_code);
APP_ERROR_CHECK(err_code);
}
start_scanning();
start_advertising(ADV_MODE_NONCONNECTABLE);
break; // BLE_GAP_EVT_CONNECTED
}
case BLE_GAP_EVT_DISCONNECTED: {
NRF_LOG("CENTRAL: DISCONNECTED\r\n");
m_gap_role = BLE_GAP_ROLE_INVALID;
if (p_gap_evt->conn_handle == m_conn_handle) {
m_conn_handle = BLE_CONN_HANDLE_INVALID;
}
if (m_conn_handle == BLE_CONN_HANDLE_INVALID) {
start_delayed_scanning();
}
start_advertising(ADV_MODE_NONCONNECTABLE);
break;
}
case BLE_GAP_EVT_ADV_REPORT: {
//snipp for our purposes. Irrelevant.
break;
}
case BLE_GAP_EVT_TIMEOUT: {
if (p_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_CONN) {
NRF_LOG("[APPL]: Connection Request timed out.\r\n");
}
NRF_LOG("BLE_GAP_EVT_TIMEOUT\r\n");
start_scanning();
start_advertising(ADV_MODE_NONCONNECTABLE);
} break;
case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: {
// Accept parameters requested by peer.
ret_code_t err_code;
err_code = sd_ble_gap_conn_param_update(
p_gap_evt->conn_handle,
&p_gap_evt->params.conn_param_update_request.conn_params);
NRF_LOG_PRINTF("BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: %u\r\n", err_code);
APP_ERROR_CHECK(err_code);
} break; // BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST
default:
// No implementation needed.
break;
}
}
static void ble_evt_dispatch(ble_evt_t *p_ble_evt) {
ble_conn_state_on_ble_evt(p_ble_evt);
notify_dfu_ble_evt(p_ble_evt);
m_gap_role = ble_conn_state_role(p_ble_evt->evt.gap_evt.conn_handle);
if (m_gap_role == BLE_GAP_ROLE_PERIPH) {
on_ble_peripheral_evt(p_ble_evt);
ble_conn_params_on_ble_evt(p_ble_evt);
//custom service
}
else if ((m_gap_role == BLE_GAP_ROLE_CENTRAL) || (p_ble_evt->header.evt_id == BLE_GAP_EVT_ADV_REPORT)) {
if(p_ble_evt->header.evt_id != BLE_GAP_EVT_ADV_REPORT) {
NRF_LOG_PRINTF("GATT CLIENT, GAP CENTRAL EVENT: %04x\r\n", p_ble_evt->header.evt_id);
}
//snip
if (p_ble_evt->header.evt_id != BLE_GAP_EVT_DISCONNECTED) {
on_ble_central_evt(p_ble_evt);
}
if (p_ble_evt->evt.gap_evt.conn_handle == m_conn_handle) {
//leaving discovery out for this question
}
if (p_ble_evt->header.evt_id == BLE_GAP_EVT_DISCONNECTED) {
on_ble_central_evt(p_ble_evt);
}
}
}
static void sys_evt_dispatch(uint32_t sys_evt) {
fs_sys_event_handler(sys_evt);
advertising_on_sys_evt(sys_evt);
scan_on_sys_evt(sys_evt);
power_management_on_sys_evt(sys_evt);
}
###scan.c
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "app_error.h"
#include "app_timer.h"
#include "ble.h"
#include "nordic_common.h"
#include "nrf.h"
#include "nrf_delay.h"
#include "nrf_soc.h"
#include "sdk_config.h"
#include "sdk_errors.h"
#include "ble_central_event_handler.h"
#include "fstorage.h"
#include "scan.h"
#include "utils.h"
//#include "SEGGER_RTT.h"
#define SCAN_INTERVAL 0x0168
#define SCAN_WINDOW 0x00E0
APP_TIMER_DEF(scan_timer_id);
#define SCAN_RESTART_TIMEOUT APP_TIMER_TICKS(20000, APP_TIMER_PRESCALER)
static ble_opt_t m_ble_opt;
static bool m_scan_start_pending = false;
static bool m_scanning = false;
static const ble_gap_scan_params_t m_scan_param = {
1, // Active scanning set.
0, // Selective scanning not set.
NULL, // No whitelist provided.
SCAN_INTERVAL, SCAN_WINDOW,
0x0000 // No timeout.
};
const ble_gap_scan_params_t *get_scan_params(void) { return &m_scan_param; }
static void scan_timer_handler(void * p_context) {
UNUSED_PARAMETER(p_context);
uint32_t err_code = app_timer_stop(scan_timer_id);
APP_ERROR_CHECK(err_code);
//disconnect();
//stop_scanning();
start_scanning();
}
void scan_init() {
app_timer_create(&scan_timer_id, APP_TIMER_MODE_SINGLE_SHOT, scan_timer_handler);
}
void scan_on_sys_evt(uint32_t sys_evt) {
switch (sys_evt) {
case NRF_EVT_FLASH_OPERATION_SUCCESS:
case NRF_EVT_FLASH_OPERATION_ERROR:
if (m_scan_start_pending) {
start_scanning();
//NRF_LOG_PRINTF("FLASH OP SCAN PENDING: %u\r\n", m_scan_start_pending);
}
break;
default:
if (m_scan_start_pending) {
start_scanning();
}
break;
}
}
void set_scan_report(void) {
m_ble_opt.gap_opt.scan_req_report.enable = 1;
uint32_t err_code = sd_ble_opt_set(BLE_GAP_OPT_SCAN_REQ_REPORT, &m_ble_opt);
APP_ERROR_CHECK(err_code);
}
void stop_scanning(void) {
uint32_t err_code = sd_ble_gap_scan_stop();
if (err_code == NRF_SUCCESS || err_code == NRF_ERROR_INVALID_STATE) {
m_scanning = false;
}
NRF_LOG_PRINTF("SCAN STOP, %u\r\n", err_code);
}
void start_delayed_scanning() {
NRF_LOG("DELAYED SCANNING\r\n");
app_timer_start(scan_timer_id, SCAN_RESTART_TIMEOUT, NULL);
}
void start_scanning(void) {
uint32_t count = 0;
uint32_t err_code = fs_queued_op_count_get(&count);
if (count != 0) {
NRF_LOG_PRINTF("SCAN_START: FP err: %u, count: %u \r\n", err_code, count);
m_scan_start_pending = true;
return;
}
APP_ERROR_CHECK(err_code);
stop_scanning();
err_code = sd_ble_gap_scan_start(&m_scan_param);
if (err_code == NRF_SUCCESS) {
m_scanning = true;
//NRF_LOG("SCAN START\r\n");
m_scan_start_pending = false;
}
if (err_code != NRF_SUCCESS) {
//NRF_LOG_PRINTF("ERROR ON START SCANNING: %u\r\n", err_code);
APP_ERROR_CHECK(err_code);
m_scan_start_pending = true;
app_timer_start(scan_timer_id, SCAN_RESTART_TIMEOUT, NULL);
}
}
bool is_scanning() {
return m_scanning;
}
###advertising.c
can be found in this answer.
Screenshots of logging on failure (only happens to peripheral):
Why is this only happening to the peripheral bot, and why does it only happen when the Central disconnects from the peripheral before the peripheral is able to disconnect?
Edit: It fails due to the fact that there is a NRF_EVT_FLASH_OPERATION_ERROR
being emanated by the sys_evt_dispatch. The advertising is able to keep trying to get around that; but the scanner is not.