Skip to content

Commit

Permalink
Bluetooth: Userchan: Add support for TCP Connection
Browse files Browse the repository at this point in the history
Added support to connect to an HCI TCP Server. This
allows to do integration tests with other frameworks
that support a virtual hci interface.

Signed-off-by: Victor Chavez <[email protected]>
  • Loading branch information
Victor Chavez authored and aescolar committed Aug 22, 2023
1 parent 392769e commit 33c922a
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 37 deletions.
9 changes: 9 additions & 0 deletions boards/posix/native_posix/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,15 @@ The following peripherals are currently provided with this board:
must be powered down and support Bluetooth Low Energy (i.e. support the
Bluetooth specification version 4.0 or greater).

Another possibility is to use a HCI TCP server which acts as a
:ref:`virtual Bluetooth controller<bluetooth_virtual_posix>` over TCP.
To connect to a HCI TCP server its IP address and port number must
be specified. For example, to connect to a HCI TCP server with IP
address 127.0.0.0 and port number 1020 use ``zephyr.exe --bt-dev=127.0.0.1:1020``.
This alternative option is mainly aimed for testing Bluetooth connectivity over
a virtual Bluetooth controller that does not depend on the Linux Bluetooth
stack and its HCI interface.

**USB controller**:
It's possible to use the Virtual USB controller working over USB/IP
protocol. More information can be found in
Expand Down
10 changes: 6 additions & 4 deletions doc/connectivity/bluetooth/bluetooth-dev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@ which is comprised of the following devices:
#. A :ref:`Host-only <bluetooth-build-types>` application running in the
:ref:`QEMU <application_run_qemu>` emulator or the ``native_posix`` native
port of Zephyr
#. A Controller, which can be one of two types:
#. A Controller, which can be one of the following types:

* A commercially available Controller
* A :ref:`Controller-only <bluetooth-build-types>` build of Zephyr
* A Virtual controller

.. warning::
Certain external Controllers are either unable to accept the Host to
Expand Down Expand Up @@ -129,11 +130,12 @@ The :ref:`Native POSIX <native_posix>` target builds your Zephyr application
with the Zephyr kernel, and some minimal HW emulation as a native Linux
executable.
This executable is a normal Linux program, which can be debugged and
instrumented like any other, and it communicates with a physical external
Controller.
instrumented like any other, and it communicates with a physical or virtual
external Controller.

Refer to :ref:`bluetooth_qemu_posix` for full instructions on how to build and
run an application in this setup.
run an application with a physical controller. For the virtual controller refer
to :ref:`bluetooth_virtual_posix`.

Simulated nRF52 with BabbleSim
==============================
Expand Down
68 changes: 68 additions & 0 deletions doc/connectivity/bluetooth/bluetooth-tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,69 @@ In order to see those logs, you can use the built-in ``btmon`` tool from BlueZ:
$ btmon
.. _bluetooth_virtual_posix:

Running on a Virtual Controller and Native POSIX
*************************************************

An alternative to a Bluetooth physical controller is the use of a virtual
controller. This controller can be connected over an HCI TCP server.
This TCP server must support the HCI H4 protocol. In comparison to the physical controller
variant, the virtual controller allows to test a Zephyr application running on the native
boards without a physical Bluetooth controller.

The main use case for a virtual controller is to do Bluetooth connectivity tests without
the need of Bluetooth hardware. This allows to automate Bluetooth integration tests with
external applications such as a Bluetooth gateway or a mobile application.

To demonstrate this functionality an example is given to interact with a virtual controller.
For this purpose, the experimental python module `Bumble`_ from Google is used as it allows to create
a TCP Bluetooth virtual controller and connect with the Zephyr Bluetooth host. To install
bumble follow the `Bumble Getting Started Guide`_.

.. note::
If your Zephyr application requires the use of the HCI LE Set extended commands, install
the branch ``controller-extended-advertising`` from Bumble.

Android Emulator
=================

You can test the virtual controller by connecting a Bluetooth Zephyr application
to the `Android Emulator`_.

To connect your application to the Android Emulator follow the next steps:

#. Build your Zephyr application and disable the HCI ACL flow
control (i.e. ``CONFIG_BT_HCI_ACL_FLOW_CONTROL=n``) as the
the virtual controller from android does not support it at the moment.

#. Install Android Emulator version >= 33.1.4.0. The easiest way to do this is by installing
the latest `Android Studio Preview`_ version.

#. Create a new Android Virtual Device (AVD) with the `Android Device Manager`_. The AVD should use at least SDK API 34.

#. Run the Android Emulator via terminal as follows:

``emulator avd YOUR_AVD -packet-streamer-endpoint default``

#. Create a Bluetooth bridge between the Zephyr application and
the virtual controller from Android Emulator with the `Bumble`_ utility ``hci-bridge``.

``bumble-hci-bridge tcp-server:_:1234 android-netsim``

This command will create a TCP server bridge on the local host IP address ``127.0.0.1``
and port number ``1234``.

#. Run the Zephyr application and connect to the TCP server created in the last step.

``./zephyr.exe --bt-dev=127.0.0.1:1234``

After following these steps the Zephyr application will be available to the Android Emulator
over the virtual Bluetooth controller that was bridged with Bumble. You can verify that the
Zephyr application can communicate over Bluetooth by opening the Bluetooth settings in your
AVD and scanning for your Zephyr application device. To test this you can build the Bluetooth
peripheral samples such as :ref:`Peripheral HR <peripheral_hr>` or :ref:`Peripheral DIS <peripheral_dis>`

.. _bluetooth_ctlr_bluez:

Using Zephyr-based Controllers with BlueZ
Expand Down Expand Up @@ -205,3 +268,8 @@ Additional information about :file:`btmgmt` can be found in its manual pages.
.. _LightBlue for iOS: https://itunes.apple.com/us/app/lightblue-explorer/id557428110
.. _nRF Mesh for Android: https://play.google.com/store/apps/details?id=no.nordicsemi.android.nrfmeshprovisioner&hl=en
.. _nRF Mesh for iOS: https://itunes.apple.com/us/app/nrf-mesh/id1380726771
.. _Bumble: https://github.com/google/bumble
.. _Bumble Getting Started Guide: https://google.github.io/bumble/getting_started.html
.. _Android Emulator: https://developer.android.com/studio/run/emulator
.. _Android Device Manager: https://developer.android.com/studio/run/managing-avds
.. _Android Studio Preview: https://developer.android.com/studio/preview
122 changes: 89 additions & 33 deletions drivers/bluetooth/hci/userchan.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

/*
* Copyright (c) 2018 Intel Corporation
*
* Copyright (c) 2023 Victor Chavez
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/byteorder.h>

#include <errno.h>
#include <stddef.h>
Expand All @@ -20,6 +19,10 @@
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <limits.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "soc.h"
#include "cmdline.h" /* native_posix command line options header */
Expand Down Expand Up @@ -54,7 +57,13 @@ static struct k_thread rx_thread_data;

static int uc_fd = -1;

static int bt_dev_index = -1;
static unsigned short bt_dev_index;

#define TCP_ADDR_BUFF_SIZE 16
static bool hci_socket;
static char ip_addr[TCP_ADDR_BUFF_SIZE];
static unsigned int port;
static bool arg_found;

static struct net_buf *get_rx(const uint8_t *buf)
{
Expand Down Expand Up @@ -185,42 +194,68 @@ static int uc_send(struct net_buf *buf)
return 0;
}

static int user_chan_open(uint16_t index)
static int user_chan_open(void)
{
struct sockaddr_hci addr;
int fd;

fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
BTPROTO_HCI);
if (fd < 0) {
return -errno;
}
if (hci_socket) {
struct sockaddr_hci addr;

fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
BTPROTO_HCI);
if (fd < 0) {
return -errno;
}

(void)memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = index;
addr.hci_channel = HCI_CHANNEL_USER;
(void)memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = bt_dev_index;
addr.hci_channel = HCI_CHANNEL_USER;

if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
int err = -errno;

close(fd);
return err;
}
} else {
struct sockaddr_in addr;

fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
return -errno;
}

if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
int err = -errno;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, ip_addr, &(addr.sin_addr)) <= 0) {
int err = -errno;

close(fd);
return err;
close(fd);
return err;
}

if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
int err = -errno;

close(fd);
return err;
}
}

return fd;
}

static int uc_open(void)
{
if (bt_dev_index < 0) {
LOG_ERR("No Bluetooth device specified");
return -ENODEV;
if (hci_socket) {
LOG_DBG("hci%d", bt_dev_index);
} else {
LOG_DBG("hci %s:%d", ip_addr, port);
}

LOG_DBG("hci%d", bt_dev_index);

uc_fd = user_chan_open(bt_dev_index);
uc_fd = user_chan_open();
if (uc_fd < 0) {
return uc_fd;
}
Expand Down Expand Up @@ -257,14 +292,33 @@ SYS_INIT(bt_uc_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);

static void cmd_bt_dev_found(char *argv, int offset)
{
if (strncmp(&argv[offset], "hci", 3) || strlen(&argv[offset]) < 4) {
posix_print_error_and_exit("Error: Invalid Bluetooth device "
"name '%s' (should be e.g. hci0)\n",
arg_found = true;
if (strncmp(&argv[offset], "hci", 3) == 0 && strlen(&argv[offset]) >= 4) {
long arg_hci_idx = strtol(&argv[offset + 3], NULL, 10);

if (arg_hci_idx >= 0 && arg_hci_idx <= USHRT_MAX) {
bt_dev_index = arg_hci_idx;
hci_socket = true;
} else {
posix_print_error_and_exit("Invalid argument value for --bt-dev. "
"hci idx must be within range 0 to 65536.\n");
}
} else if (sscanf(&argv[offset], "%15[^:]:%d", ip_addr, &port) == 2) {
if (port > USHRT_MAX) {
posix_print_error_and_exit("Error: IP port for bluetooth "
"hci tcp server is out of range.\n");
}
struct in_addr addr;

if (inet_pton(AF_INET, ip_addr, &addr) != 1) {
posix_print_error_and_exit("Error: IP address for bluetooth "
"hci tcp server is incorrect.\n");
}
} else {
posix_print_error_and_exit("Invalid option %s for --bt-dev. "
"An hci interface or hci tcp server is expected.\n",
&argv[offset]);
return;
}

bt_dev_index = strtol(&argv[offset + 3], NULL, 10);
}

static void add_btuserchan_arg(void)
Expand All @@ -280,7 +334,8 @@ static void add_btuserchan_arg(void)
{ false, true, false,
"bt-dev", "hciX", 's',
NULL, cmd_bt_dev_found,
"A local HCI device to be used for Bluetooth (e.g. hci0)" },
"A local HCI device to be used for Bluetooth (e.g. hci0) "
"or an HCI TCP Server (e.g. 127.0.0.1:9000)"},
ARG_TABLE_ENDMARKER
};

Expand All @@ -289,9 +344,10 @@ static void add_btuserchan_arg(void)

static void btuserchan_check_arg(void)
{
if (bt_dev_index < 0) {
posix_print_error_and_exit("Error: Bluetooth device missing. "
"Specify one using --bt-dev=hciN\n");
if (!arg_found) {
posix_print_error_and_exit("Error: Bluetooth device missing.\n"
"Specify either a local hci interface --bt-dev=hciN\n"
"or a valid hci tcp server --bt-dev=ip_address:port\n");
}
}

Expand Down

0 comments on commit 33c922a

Please sign in to comment.