BLE Scan fails in mbed 6.9.0 (Arduino Nano 33 BLE)

Hi all,

I’ve been working working with the BLE stack on mbed os and the Arduino Nano 33 BLE for a bit now. Previously, on version 6.2 (or 6.3?) I had a working scan implemented.

Since upgrading to 6.8 (though now I’m on 6.9) – I’ve had an issue where I get the following error:

++ MbedOS Error Info ++
Error Status: 0x80FF0144 Code: 324 Module: 255
Error Message: Assertion failed: _scan_state == ScanState::pending_scan
Location: 0x4DAA1
File: ./mbed-os/connectivity/FEATURE_BLE/source/generic/GapImpl.cpp+1296
Error Value: 0x0
Current Thread: ble event processing Id: 0x20001A28 Entry: 0x4EBDD StackSize: 0x1000 StackMem: 0x20018F78 SP: 0x20019DA4 

You can see it’s a failed assertion related to the current _scan_state. I did some digging and got mbed trace enabled on my build/board and noticed that I don’t get any trace info on set scan state, which I believe should be logging via trace:

void Gap::set_scan_state(ScanState state)
{
    _scan_state = state;

    if (state == ScanState::idle) {
        tr_info("Scan state: idle");
    } else if (state == ScanState::pending_scan) {
        tr_info("Scan state: pending_scan");
    } else if (state == ScanState::pending_stop_scan) {
        tr_info("Scan state: pending_stop_scan");
    } else if (state == ScanState::scan) {
        tr_info("Scan state: scan");
    }
}

What is interesting in my tracing output is I never see any state info traced:

e[2Ke[39m[INFO][BLGP]: Set scan parameters - own_address_type=RANDOM, scanning_filter_policy=NO_FILTER, phys=1, phy_1m_configuration:[interval=50ms,window=37ms,active_scanning=true]e[0m
e[2Ke[39m[INFO][BLGP]: Start scan - duration=0ms, filtering=DISABLE, period=0mse[0m
e[2Ke[39m[INFO][BLGP]: Initiate scane[0m
e[2Ke[39m[INFO][BLDM]: Set random address - address=4f:db:01:3c:7e:dfe[0m
e[2Ke[39m[INFO][BLDM]: Extended scan enable - enable=true, filter_duplicates=DISABLE, duration=0, period=0e[0m
e[2Ke[39m[INFO][BLGP]: Scan successfully startede[0m

Does anyone have any insight into why my _scan_state has an unexpected value? Any insights would be very helpful.

After some further digging, I’ve found it’s failing at this call in PalGapImpl’s function extended_scan_enable

        DmScanStart(
            scanning_phys.value(),
            DM_DISC_MODE_NONE,
            extended_scan_type,
            filter_duplicates.value(),
            duration_ms > 0xFFFF ? 0xFFFF : duration_ms,
            period
        );

This function is here:

void DmScanStart(uint8_t scanPhys, uint8_t mode, const uint8_t *pScanType, bool_t filterDup,
                 uint16_t duration, uint16_t period)
{
  uint8_t i;              /* scanPhy bit position */
  uint8_t idx;            /* param array index */
  dmScanApiStart_t *pMsg;


  if ((pMsg = WsfMsgAlloc(sizeof(dmScanApiStart_t))) != NULL)
  {
    pMsg->hdr.event = DM_SCAN_MSG_API_START;
    pMsg->scanPhys = scanPhys;

    for (i = 0, idx = 0; (i < 8) && (idx < DM_NUM_PHYS); i++)
    {
      if (scanPhys & (1 << i))
      {
        /* scan type for this PHY */
        pMsg->scanType[idx] = pScanType[idx];
        idx++;
      }
    }

    pMsg->mode = mode;
    pMsg->duration = duration;
    pMsg->period = period;
    pMsg->filterDup = filterDup;
    WsfMsgSend(dmCb.handlerId, pMsg);
  }
}

Does anyone have any advice on debugging this issue further? The next output I get is the assert failing as mentioned before (for clarity, this assert is in Gap::on_scan_started)

After even more investigation I found this is a bug in mbed os. In reality there is a race-condition on the assert mentioned, so if the thread processing BLE events has a higher priority than the thread starting the scan, the assert will be false because the initial thread has not finished setting the _scan_state at that point.

I plan on making a minimal reproduction and posting to GitHub to figure out a proper solution, but if anyone else stumbles upon this, adjusting thread priority may be a helpful workaround.

Final update here for anyone having similar issues – the BLE API is not threadsafe, and all calls to the BLE api need to happen in a single thread – so always aim to put those calls into some event_queue when needing to access from multiple places.