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

Receive L2CAP packets with bluez

I've been struggling for some time to get L2CAP transfer between an NRF51822 and a Linux machine using bluez.

I've successfully created a connection between the central (Linux) and peripheral (NRF51822) and created a socket, but I get no data through. I can confirm via LED indicators that the connection is established (i.e. hci_le_create_conn completes ok).

The code seems to block on the accept() call and executes no further. Even though the peripheral is transmitting periodically.

This is the bluez code (the channel I'm using is 0x41):

int main() {
    printf("init\n");
    bdaddr_t src_addr, dst_addr;
    struct sockaddr_l2 l2cap_address;

    // Get the destination address
    str2ba("e1:a3:1e:75:29:b0", &dst_addr);
    socklen_t opt = sizeof(dst_addr);

    // Get host address
    int hci_device_id = hci_get_route(NULL);
    int hci_socket = hci_open_dev(hci_device_id);
    hci_devba(hci_device_id, &src_addr);

    /* create L2CAP socket, and bind it to the local adapter */
    printf("Creating socket...\n");
    int l2cap_socket = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
    if(l2cap_socket < 0) {
        printf("Error creating socket\n");
        return -1;
    }

    memset(&l2cap_address, 0, sizeof(l2cap_address));
    l2cap_address.l2_family = AF_BLUETOOTH;
    l2cap_address.l2_bdaddr = src_addr;
    l2cap_address.l2_cid = htobs(ATT_CID);

    printf("Binding socket...\n");
    bind(l2cap_socket, (struct sockaddr*)&l2cap_address, sizeof(l2cap_address));
    listen(l2cap_socket, 1);

    // Connect to bluetooth peripheral
    printf("Creating connection... ");
    uint16_t hci_handle;
    int result = hci_le_create_conn(hci_socket,
        htobs(0x0004), htobs(0x0004), 0,
        LE_RANDOM_ADDRESS, dst_addr, LE_PUBLIC_ADDRESS,
        htobs(0x0006) /*min_interval*/, htobs(0x0020) /*max_interval*/,
        htobs(0) /*latency*/, htobs(200) /*supervision_timeout*/,
        htobs(0x0001), htobs(0x0001), &hci_handle, 25000);
    printf("%d\n", result);

    fd_set afds;
    struct timeval tv = {
        .tv_usec = 3000
    };
    int len;
    uint8_t buffer[1024];
    printf("Entering loop\n");
    while (1) {
        printf("Selecting...\n");
        /* now select and accept() client connections. */
        select(l2cap_socket + 1, &afds, NULL, NULL, &tv);   
        
        printf("Accepting...\n");
        bdaddr_t addr;
        len = sizeof(addr);
        int client_socket = accept(l2cap_socket, (struct sockaddr *)&addr, &len);

        printf("Reading...\n");
        /* you can now read() what the client sends you */
        int ret = read(client_socket, buffer, sizeof(buffer));
        printf("data len: %d\n", ret);
        for (int i = 0; i < ret; i++) {
        printf("%02x", ((int)buffer[i]) & 0xff);
        }
        printf("\n");
        close(client_socket);
    }

    return 0;
}

On the ARM side, I'm calling this function upon a BLE connection:

static void l2cap_init() {
    uint32_t err_code = sd_ble_l2cap_cid_register(CID);
    APP_ERROR_CHECK(err_code);
}

And on a fixed interval I'm calling this function:

static void l2cap_transmit() {
    uint32_t err_code;
    ble_l2cap_header_t tx_head;

    uint8_t tx_data[] = "Hello!";

    tx_head.cid = CID;
    tx_head.len = sizeof(tx_data);

    err_code = sd_ble_l2cap_tx(m_conn_handle, &tx_head,  tx_data);
    APP_ERROR_CHECK(err_code);
}

I've confirmed that sd_ble_l2cap_tx is returning NRF_SUCCESS and the BLE_EVT_TX_COMPLETE event occurs, so the problem seems to lie on the central side.

Can anyone guide me in the right direction here? I need to be able to send data from the peripheral and receive them on the central at a pretty fast rate.

I'm also aware of using sd_ble_gatts_hvx to send notifications to the central. But I've failed to find any info on how to set the central up to be able to receive the data. Would a GATT server be the way to go here?

Parents
  • The sd_ble_l2cap_tx function only sends raw L2CAP packets with CID >= 0x0040 (connection oriented channels). The bluez code you wrote is listening for an L2CAP connection request on CID 0x0005 (signaling channel) in order to establish a connection so it can receive data on the CoC (connection oriented channels). You can read more about this here: community.nxp.com/.../366041. Or in the Bluetooth spec v4.2, Vol 3, Part A, chapter 10.

    So the above is part of the L2CAP spec, and the L2CAP socket you listen to in bluez code follows this spec. The SD does not support CoC yet, so it is unable to do a connection request/response on the L2CAP signaling channel (even though you can send raw packets on CID >=0x0040). So, as far as I know, in order to receive the raw L2CAP packets you are sending (without the connection procedure), you need to communicate with the BLE dongle on the HCI layer.

    However, the IoT SD has added support for CoC, so you can try to use this SD instead. The API is slightly different so I would recommend you to start with one of the examples in the IoT SDK. In the on_ble_evt() function you can now receive the L2CAP connection request BLE_L2CAP_EVT_CH_CONN_REQUEST, and you can reply to it, something like this:

    case BLE_L2CAP_EVT_CH_CONN_REQUEST:
    {
        ble_l2cap_ch_conn_reply_params_t reply_param;
            
        uint16_t    tx_mps = 64;
        uint16_t    rx_mps = 50;
        uint16_t    cid    = 0x0064;
    
        reply_param.peer_cid = p_ble_evt->evt.l2cap_evt.params.ch_conn_request.peer_cid;
            
        reply_param.ch_conn_params.pub_mtu    = 64;
        reply_param.ch_conn_params.act_mtu    = 64;
        reply_param.ch_conn_params.rx_mem_len = RX_BUFFER_TOTAL_SIZE;
        reply_param.ch_conn_params.p_rx_mps   = &rx_mps;
        reply_param.ch_conn_params.p_tx_mps   = &tx_mps;
        reply_param.ch_conn_params.p_rx_mem   = &m_rx_buffer[0];
        reply_param.ch_conn_params.rx_creds   = INITIAL_CREDITS;
        
    
        reply_param.result   = BLE_L2CAP_CH_CONN_REPLY_RESULT_SUCCESS;
        int retval = sd_ble_l2cap_ch_conn_reply(p_ble_evt->evt.l2cap_evt.conn_handle,
                                                &reply_param,
                                                &cid);
        APP_ERROR_CHECK(retval);
    
        // When the connection is successful you can transmit some data:
        uint8_t data[5] = "hello";
        sd_ble_l2cap_ch_tx(p_ble_evt->evt.l2cap_evt.conn_handle,cid,data,5);
    }
    

    The above requires your bluez code to initiate the L2CAP conneciton, and you need to use connect() instead of bind() and listen().

    In linux you should use btmon to monitor the HCI interface and you can see all L2CAP packets. Great for debugging.

    You can also try the TFTP example in the IoT SDK to transfer large amounts of data. But this requires you to first create an 6LoWPAN connection between the BLE device and your linux box. See here, or here.

    Finally, you can also set up a GATT server on the BLE device using the regular SD and SDK (not IoT), and use notifications to send data. You can check out the UART example and use gatttool in linux to create a connection, write to the CCCD to enable notifications and listen for notifications. The drawback with this approach is that you will get some overhead in each packet transfer because of the ATT header, but this is probably the easiest way to go (depending on your bluez skills).

  • @dingari Are you using Bluez for this (GATT notifications)? If yes are you using any library? Thx!

Reply Children
No Data
Related