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

Peripheral disconnects when discovering services from a Windows central

Hi, I'm working on a cross-platform application that needs to communicate with a custom peripheral (nRF51822 with S130 v2.0.1). I've implemented the interface for both Linux and Mac OS X without problem, and am working on the Windows side now.

I don't expect to get much (if any) help with the Windows programming, but I am simply looking to understand what is happening.

Since the application is written purely in C++, I'm working with the WRL C++ Template library to interface with the Windows Runtime API. I've paired the device through Windows' built-in Settings UI (neccessary as the Windows API does not allow desktop applications to scan and connect for Bluetooth peripherals). I've successfully found the device through device enumeration, and I can see the BLE Service UUIDs (they come up as seperate devices during enumeration). The next step is to construct GattService objects in the Windows code, and this is where my peripheral just disconnects.

I set a breakpoint on the BLE_GAP_EVT_DISCONNECTED case in the on_ble_evt handler, and looked at the contents of the event. I can see that I get disconnected reason = 19 (0x13), which corresponds to BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION.

Now, there are only two occurrences of BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION I've found in my user code for the nRF51822: in reset_prepare and in the DFU source code. So the disconnect must be coming from the Softdevice.

Now, I'm simply trying to understand why the Windows machine terminates the connection. Any ideas? Anyone had a similar issues?

For reference (if anyone has experience working with WRL for BLE), here is the code that causes the disconnect:

ComPtr<IAsyncOperation<GattDeviceService*>> op;
auto callback = Callback<IAsyncOperationCompletedHandler<GattDeviceService*>>([](IAsyncOperation<GattDeviceService*> *op, AsyncStatus status) -> HRESULT {
    ComPtr<IGattDeviceService> service;
    op->GetResults(&service);

    GUID guid;
    service->get_Uuid(&guid);

    OLECHAR* guid_olechar;
    StringFromCLSID(guid, &guid_olechar);

    HString guid_str;
    guid_str.Set(guid_olechar);

    std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
    std::string guidStr = conv.to_bytes(WindowsGetStringRawBuffer(guid_str.Get(), nullptr));

    std::cout << "Service callback! GUID: " << guidStr << std::endl;

    return 0;
});

ComPtr<IGattDeviceServiceStatics> gattStatics;
hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Devices_Bluetooth_GenericAttributeProfile_GattDeviceService).Get(), &gattStatics);

hr = gattStatics->FromIdAsync(id, &op);

if (FAILED(hr)) {
    PrintError(__LINE__, hr);
}

op->put_Completed(callback.Get());

And here are the full contents of the event received on the peripheral side (obtained through gdb, sorry for the formatting):

p *p_ble_evt 
$3 = {header = {evt_id = 17, evt_len = 9}, evt = {common_evt = {conn_handle = 0, params = {tx_complete = {count = 19 '\023'}, user_mem_request = {type = 19 '\023'}, user_mem_release =     {type = 19 '\023', 
          mem_block = {p_mem = 0x1 "\a", len = 2}}}}, gap_evt = {conn_handle = 0, params = {connected = {peer_addr = {addr_type = 19 '\023', addr = ")\001\000\001\000"}, own_addr = {addr_type = 0 '\000', 
        addr = "\002\000\002\000\277", <incomplete sequence \305>}, role = 1 '\001', irk_match = 0 '\000', irk_match_idx = 0 '\000', conn_params = {min_conn_interval = 48, max_conn_interval = 48, 
            slave_latency = 0, conn_sup_timeout = 960}}, disconnected = {reason = 19 '\023'}, conn_param_update = {conn_params = {min_conn_interval = 10515, max_conn_interval = 1, slave_latency = 1, 
        conn_sup_timeout = 0}}, sec_params_request = {peer_params = {bond = 1 '\001', mitm = 1 '\001', lesc = 0 '\000', keypress = 0 '\000', io_caps = 1 '\001', oob = 0 '\000', min_key_size = 41 ')', 
            max_key_size = 1 '\001', kdist_own = {enc = 0 '\000', id = 0 '\000', sign = 0 '\000', link = 0 '\000'}, kdist_peer = {enc = 1 '\001', id = 0 '\000', sign = 0 '\000', link = 0 '\000'}}}, 
    sec_info_request = {peer_addr = {addr_type = 19 '\023', addr = ")\001\000\001\000"}, master_id = {ediv = 2, rand = "\002\000\277\305\001\000\060"}, enc_info = 0 '\000', id_info = 0 '\000', 
          sign_info = 0 '\000'}, passkey_display = {passkey = "\023)\001\000\001", match_request = 0 '\000'}, key_pressed = {kp_not = 19 '\023'}, auth_key_request = {key_type = 19 '\023'}, lesc_dhkey_request = {
      p_pk_peer = 0x12913, oobd_req = 1 '\001'}, auth_status = {auth_status = 19 '\023', error_src = 1 '\001', bonded = 0 '\000', sm1_levels = {lv1 = 1 '\001', lv2 = 0 '\000', lv3 = 0 '\000', 
            lv4 = 0 '\000'}, sm2_levels = {lv1 = 0 '\000', lv2 = 0 '\000', lv3 = 0 '\000', lv4 = 0 '\000'}, kdist_own = {enc = 1 '\001', id = 0 '\000', sign = 0 '\000', link = 0     '\000'}, kdist_peer = {
        enc = 0 '\000', id = 0 '\000', sign = 0 '\000', link = 0 '\000'}}, conn_sec_update = {conn_sec = {sec_mode = {sm = 3 '\003', lv = 1 '\001'}, encr_key_size = 41 ')'}}, timeout = {src = 19 '\023'}, 
        rssi_changed = {rssi = 19 '\023'}, adv_report = {peer_addr = {addr_type = 19 '\023', addr = ")\001\000\001\000"}, rssi = 0 '\000', scan_rsp = 0 '\000', type = 1 '\001', dlen = 0 '\000', 
      data = "\000\002\000\277\305\001\000\060\000\060\000\000\000\300\003", '\000' <repeats 15 times>}, sec_request = {bond = 1 '\001', mitm = 1 '\001', lesc = 0 '\000', keypress = 0 '\000'}, 
        conn_param_update_request = {conn_params = {min_conn_interval = 10515, max_conn_interval = 1, slave_latency = 1, conn_sup_timeout = 0}}, scan_req_report = {rssi = 19 '\023', peer_addr = {
        addr_type = 41 ')', addr = "\001\000\001\000\000"}}}}, l2cap_evt = {conn_handle = 0, params = {rx = {header = {len = 11, cid = 10515}, data = "\001"}}}, gattc_evt = {conn_handle = 0, 
      gatt_status = 11, error_handle = 10515, params = {prim_srvc_disc_rsp = {count = 1, services = {{uuid = {uuid = 0, type = 2 '\002'}, handle_range = {start_handle = 2, end_handle = 50623}}}}, 
    rel_disc_rsp = {count = 1, includes = {{handle = 0, included_srvc = {uuid = {uuid = 2, type = 2 '\002'}, handle_range = {start_handle = 50623, end_handle = 1}}}}}, char_disc_rsp = {count = 1, chars = {{
              uuid = {uuid = 0, type = 2 '\002'}, char_props = {broadcast = 0 '\000', read = 1 '\001', write_wo_resp = 0 '\000', write = 0 '\000', notify = 0 '\000', indicate = 0 '\000', 
            auth_signed_wr = 0 '\000'}, char_ext_props = 0 '\000', handle_decl = 50623, handle_value = 1}}}, desc_disc_rsp = {count = 1, descs = {{handle = 0, uuid = {uuid = 2, type = 2 '\002'}}}}, 
        char_val_by_uuid_read_rsp = {count = 1, value_len = 0, handle_value = {{handle = 2, p_value = 0x1c5bf <data_page_erase_state_run+10> "\264\214N\202\260\064h#B"}}}, read_rsp = {handle = 1, offset = 0, 
      len = 2, data = "\002"}, char_vals_read_rsp = {len = 1, values = ""}, write_rsp = {handle = 1, write_op = 0 '\000', offset = 2, len = 2, data = "\277"}, hvx = {handle = 1, type = 0 '\000', len = 2, 
          data = "\002"}, timeout = {src = 1 '\001'}, attr_info_disc_rsp = {count = 1, format = 0 '\000', attr_info = {{handle = 2, info = {uuid16 = {uuid = 2, type = 191 '\277'}, uuid128 = {
              uuid128 = "\002\000\277\305\001\000\060\000\060\000\000\000\300\003\000"}}}}}}}, gatts_evt = {conn_handle = 0, params = {write = {handle = 11, uuid = {uuid = 10515, type = 1 '\001'}, 
          op = 1 '\001', auth_required = 0 '\000', offset = 0, len = 2, data = "\002"}, authorize_request = {type = 11 '\v', request = {read = {handle = 10515, uuid = {uuid = 1, type = 1 '\001'}, offset = 0}, 
        write = {handle = 10515, uuid = {uuid = 1, type = 1 '\001'}, op = 0 '\000', auth_required = 0 '\000', offset = 2, len = 2, data = "\277"}}}, sys_attr_missing = {hint = 11 '\v'}, hvc = {handle = 11}, 
        timeout = {src = 11 '\v'}}}}}

Edit: After some investigating I'm starting to think this is a bonding/pairing issue on the peripheral side. It's not reconnecting to the Windows machine on startup after being paired.

If I try to pair the peripheral with my Android phone it disconnects after pairing (the Android side says it's paired, but the peripheral goes into advertising mode). This is in fact similar to what I'm seeing on the Windows host (only the Windows host doesn't terminate the connection until I try to access the services).

I'm using the Device Manager to handle the bonding information, and I've set the advertising mode to Direct Advertising (to make the peripheral instantly try to reconnect upon disconnect) but it seems to default down to Fast Advertising.

This is my advertising event handler:

void on_adv_evt(ble_adv_evt_t ble_adv_evt) {
    uint32_t err_code;

    switch (ble_adv_evt)
    {
        case BLE_ADV_EVT_FAST:
            err_code = app_timer_start(adv_led_timer_id, ADV_LED_INTERVAL, NULL);
            APP_ERROR_CHECK(err_code);

            break;
        case BLE_ADV_EVT_IDLE:
            // advertising_stop();

            err_code = app_timer_stop(adv_led_timer_id);
            APP_ERROR_CHECK(err_code);

            // sleep_mode_enter();
            shutdown_flag = 1;

            break;

        // Handle whitelist requests from the advertising module.
        // This happens when using FAST or SLOW advertising modes and 
        // whitelisting is enabled when ble_advertising_init() is called
        case BLE_ADV_EVT_WHITELIST_REQUEST: {
            ble_gap_whitelist_t* whitelist;
            err_code = dm_whitelist_create(&m_app_handle, &whitelist);

            if(err_code == NRF_SUCCESS) {
                err_code = ble_advertising_whitelist_reply(&whitelist);
                APP_ERROR_CHECK(err_code);                
            }

            break;
        }

        // Handle peer address requests from advertising module.
        // This happens when using DIRECTED or DIRECTED_SLOW advertising modes.
        // The device will then try to reconnect to the last connected peer.
        case BLE_ADV_EVT_PEER_ADDR_REQUEST: {
            ble_gap_addr_t* addr;
            err_code = dm_peer_addr_get(&m_app_handle, &addr);

            if(err_code == NRF_SUCCESS) {
                err_code = ble_advertising_peer_addr_reply(&addr);
                APP_ERROR_CHECK(err_code);
            }  

            break;
        }

        default:
            break;
    }
}

The problem is, that dm_peer_addr_get returns error code NRF_ERROR_NOT_FOUND, and the ble_advertising_peer_addr_reply isn't called. Are there some precautions I need to take that I'm overlooking?

The whitelist request case executes OK if I have paired the device, and in there the address is just 0.

Related