unix: Add support for modbluetooth and BLE using btstack.

This commit adds full support to the unix port for Bluetooth using the
common extmod/modbluetooth Python bindings.  This uses the libusb HCI
transport, which supports many common USB BT adaptors.
This commit is contained in:
Jim Mussared 2020-04-07 15:02:52 +10:00 committed by Damien George
parent c987adb9e9
commit 7563d58210
8 changed files with 225 additions and 4 deletions

View File

@ -48,6 +48,8 @@ Configuration
- ``'mac'``: Returns the device MAC address. If a device has a fixed address - ``'mac'``: Returns the device MAC address. If a device has a fixed address
(e.g. PYBD) then it will be returned. Otherwise (e.g. ESP32) a random (e.g. PYBD) then it will be returned. Otherwise (e.g. ESP32) a random
address will be generated when the BLE interface is made active. address will be generated when the BLE interface is made active.
Note: on some ports, accessing this value requires that the interface is
active (so that the MAC address can be queried from the controller).
- ``'rxbuf'``: Get/set the size in bytes of the internal buffer used to store - ``'rxbuf'``: Get/set the size in bytes of the internal buffer used to store
incoming events. This buffer is global to the entire BLE driver and so incoming events. This buffer is global to the entire BLE driver and so

View File

@ -2,6 +2,8 @@
ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1)
MICROPY_BLUETOOTH_BTSTACK_USB ?= 0
BTSTACK_EXTMOD_DIR = extmod/btstack BTSTACK_EXTMOD_DIR = extmod/btstack
EXTMOD_SRC_C += extmod/btstack/modbluetooth_btstack.c EXTMOD_SRC_C += extmod/btstack/modbluetooth_btstack.c
@ -24,9 +26,17 @@ INC += -I$(BTSTACK_DIR)/3rd-party/yxml
SRC_BTSTACK = \ SRC_BTSTACK = \
$(addprefix lib/btstack/src/, $(SRC_FILES)) \ $(addprefix lib/btstack/src/, $(SRC_FILES)) \
$(addprefix lib/btstack/src/ble/, $(filter-out %_tlv.c, $(SRC_BLE_FILES))) \ $(addprefix lib/btstack/src/ble/, $(filter-out %_tlv.c, $(SRC_BLE_FILES))) \
lib/btstack/platform/embedded/btstack_run_loop_embedded.c \ lib/btstack/platform/embedded/btstack_run_loop_embedded.c
ifeq ($MICROPY_BLUETOOTH_BTSTACK_ENABLE_CLASSIC,1) ifeq ($(MICROPY_BLUETOOTH_BTSTACK_USB),1)
SRC_BTSTACK += \
lib/btstack/platform/libusb/hci_transport_h2_libusb.c
CFLAGS += $(shell pkg-config libusb-1.0 --cflags)
LDFLAGS += $(shell pkg-config libusb-1.0 --libs)
endif
ifeq ($(MICROPY_BLUETOOTH_BTSTACK_ENABLE_CLASSIC),1)
include $(BTSTACK_DIR)/src/classic/Makefile.inc include $(BTSTACK_DIR)/src/classic/Makefile.inc
SRC_BTSTACK += \ SRC_BTSTACK += \
$(addprefix lib/btstack/src/classic/, $(SRC_CLASSIC_FILES)) $(addprefix lib/btstack/src/classic/, $(SRC_CLASSIC_FILES))

View File

@ -41,4 +41,7 @@
// BTstack HAL configuration // BTstack HAL configuration
#define HAVE_EMBEDDED_TIME_MS #define HAVE_EMBEDDED_TIME_MS
// Some USB dongles take longer to respond to HCI reset (e.g. BCM20702A).
#define HCI_RESET_RESEND_TIMEOUT_MS 1000
#endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_BTSTACK_CONFIG_H #endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_BTSTACK_CONFIG_H

View File

@ -132,6 +132,19 @@ CFLAGS_MOD += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0
LDFLAGS_MOD += $(LIBPTHREAD) LDFLAGS_MOD += $(LIBPTHREAD)
endif endif
ifeq ($(MICROPY_PY_BLUETOOTH),1)
CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1
CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1
CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK=1
MICROPY_BLUETOOTH_BTSTACK ?= 1
MICROPY_BLUETOOTH_BTSTACK_USB ?= 1
endif
ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1)
include $(TOP)/extmod/btstack/btstack.mk
endif
ifeq ($(MICROPY_PY_FFI),1) ifeq ($(MICROPY_PY_FFI),1)
ifeq ($(MICROPY_STANDALONE),1) ifeq ($(MICROPY_STANDALONE),1)
@ -176,10 +189,11 @@ SRC_C = \
alloc.c \ alloc.c \
coverage.c \ coverage.c \
fatfs_port.c \ fatfs_port.c \
btstack_usb.c \
$(SRC_MOD) \ $(SRC_MOD) \
$(wildcard $(VARIANT_DIR)/*.c) $(wildcard $(VARIANT_DIR)/*.c)
LIB_SRC_C = $(addprefix lib/,\ LIB_SRC_C += $(addprefix lib/,\
$(LIB_SRC_C_EXTRA) \ $(LIB_SRC_C_EXTRA) \
timeutils/timeutils.c \ timeutils/timeutils.c \
) )
@ -187,9 +201,10 @@ LIB_SRC_C = $(addprefix lib/,\
OBJ = $(PY_O) OBJ = $(PY_O)
OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o))
OBJ += $(addprefix $(BUILD)/, $(EXTMOD_SRC_C:.c=.o))
# List of sources for qstr extraction # List of sources for qstr extraction
SRC_QSTR += $(SRC_C) $(LIB_SRC_C) SRC_QSTR += $(SRC_C) $(LIB_SRC_C) $(EXTMOD_SRC_C)
# Append any auto-generated sources that are needed by sources listed in # Append any auto-generated sources that are needed by sources listed in
# SRC_QSTR # SRC_QSTR
SRC_QSTR_AUTO_DEPS += SRC_QSTR_AUTO_DEPS +=

170
ports/unix/btstack_usb.c Normal file
View File

@ -0,0 +1,170 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jim Mussared
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <pthread.h>
#include <unistd.h>
#include "py/runtime.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK
#include "lib/btstack/src/btstack.h"
#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h"
#include "extmod/btstack/modbluetooth_btstack.h"
#if !MICROPY_PY_THREAD
#error Unix btstack requires MICROPY_PY_THREAD
#endif
STATIC const useconds_t USB_POLL_INTERVAL_US = 1000;
STATIC const uint8_t read_static_address_command_complete_prefix[] = { 0x0e, 0x1b, 0x01, 0x09, 0xfc };
STATIC uint8_t local_addr[6] = {0};
STATIC uint8_t static_address[6] = {0};
STATIC volatile bool have_addr = false;
STATIC bool using_static_address = false;
STATIC btstack_packet_callback_registration_t hci_event_callback_registration;
STATIC void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
(void)channel;
(void)size;
if (packet_type != HCI_EVENT_PACKET) {
return;
}
switch (hci_event_packet_get_type(packet)) {
case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) {
return;
}
gap_local_bd_addr(local_addr);
if (using_static_address) {
memcpy(local_addr, static_address, sizeof(local_addr));
}
have_addr = true;
break;
case HCI_EVENT_COMMAND_COMPLETE:
if (memcmp(packet, read_static_address_command_complete_prefix, sizeof(read_static_address_command_complete_prefix)) == 0) {
reverse_48(&packet[7], static_address);
gap_random_address_set(static_address);
using_static_address = true;
have_addr = true;
}
break;
default:
break;
}
}
// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the
// following three functions are empty.
void hal_cpu_disable_irqs(void) {
}
void hal_cpu_enable_irqs(void) {
}
void hal_cpu_enable_irqs_and_sleep(void) {
}
uint32_t hal_time_ms(void) {
return mp_hal_ticks_ms();
}
void mp_bluetooth_btstack_port_init(void) {
static bool run_loop_init = false;
if (!run_loop_init) {
run_loop_init = true;
btstack_run_loop_init(btstack_run_loop_embedded_get_instance());
} else {
btstack_run_loop_embedded_get_instance()->init();
}
// TODO: allow setting USB device path via cmdline/env var.
// hci_dump_open(NULL, HCI_DUMP_STDOUT);
hci_init(hci_transport_usb_instance(), NULL);
hci_event_callback_registration.callback = &packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
}
STATIC pthread_t bstack_thread_id;
void mp_bluetooth_btstack_port_deinit(void) {
hci_power_control(HCI_POWER_OFF);
// Wait for the poll loop to terminate when the state is set to OFF.
pthread_join(bstack_thread_id, NULL);
have_addr = false;
}
STATIC void *btstack_thread(void *arg) {
(void)arg;
hci_power_control(HCI_POWER_ON);
// modbluetooth_btstack.c will have set the state to STARTING before
// calling mp_bluetooth_btstack_port_start.
// This loop will terminate when the HCI_POWER_OFF above results
// in modbluetooth_btstack.c setting the state back to OFF.
// Or, if a timeout results in it being set to TIMEOUT.
while (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) {
btstack_run_loop_embedded_execute_once();
// The USB transport schedules events to the run loop at 1ms intervals,
// and the implementation currently polls rather than selects.
usleep(USB_POLL_INTERVAL_US);
}
hci_close();
return NULL;
}
void mp_bluetooth_btstack_port_start(void) {
// Create a thread to run the btstack loop.
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&bstack_thread_id, &attr, &btstack_thread, NULL);
}
void mp_hal_get_mac(int idx, uint8_t buf[6]) {
if (idx == MP_HAL_MAC_BDADDR) {
if (!have_addr) {
mp_raise_OSError(MP_ENODEV);
}
memcpy(buf, local_addr, sizeof(local_addr));
}
}
#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK

View File

@ -683,6 +683,11 @@ MP_NOINLINE int main_(int argc, char **argv) {
} }
#endif #endif
#if MICROPY_PY_BLUETOOTH
void mp_bluetooth_deinit(void);
mp_bluetooth_deinit();
#endif
#if MICROPY_PY_THREAD #if MICROPY_PY_THREAD
mp_thread_deinit(); mp_thread_deinit();
#endif #endif

View File

@ -311,9 +311,17 @@ void mp_unix_mark_exec(void);
#define MP_STATE_PORT MP_STATE_VM #define MP_STATE_PORT MP_STATE_VM
#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK
struct _mp_bluetooth_btstack_root_pointers_t;
#define MICROPY_BLUETOOTH_ROOT_POINTERS struct _mp_bluetooth_btstack_root_pointers_t *bluetooth_btstack_root_pointers;
#else
#define MICROPY_BLUETOOTH_ROOT_POINTERS
#endif
#define MICROPY_PORT_ROOT_POINTERS \ #define MICROPY_PORT_ROOT_POINTERS \
const char *readline_hist[50]; \ const char *readline_hist[50]; \
void *mmap_region_head; \ void *mmap_region_head; \
MICROPY_BLUETOOTH_ROOT_POINTERS \
// We need to provide a declaration/definition of alloca() // We need to provide a declaration/definition of alloca()
// unless support for it is disabled. // unless support for it is disabled.

View File

@ -95,3 +95,11 @@ static inline void mp_hal_delay_us(mp_uint_t us) {
#define RAISE_ERRNO(err_flag, error_val) \ #define RAISE_ERRNO(err_flag, error_val) \
{ if (err_flag == -1) \ { if (err_flag == -1) \
{ mp_raise_OSError(error_val); } } { mp_raise_OSError(error_val); } }
#if MICROPY_PY_BLUETOOTH
enum {
MP_HAL_MAC_BDADDR,
};
void mp_hal_get_mac(int idx, uint8_t buf[6]);
#endif