diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 62a823924f..2609ac01b9 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -48,6 +48,8 @@ Configuration - ``'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 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 incoming events. This buffer is global to the entire BLE driver and so diff --git a/extmod/btstack/btstack.mk b/extmod/btstack/btstack.mk index ecb1c84bd4..6c883578ee 100644 --- a/extmod/btstack/btstack.mk +++ b/extmod/btstack/btstack.mk @@ -2,6 +2,8 @@ ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +MICROPY_BLUETOOTH_BTSTACK_USB ?= 0 + BTSTACK_EXTMOD_DIR = extmod/btstack EXTMOD_SRC_C += extmod/btstack/modbluetooth_btstack.c @@ -24,9 +26,17 @@ INC += -I$(BTSTACK_DIR)/3rd-party/yxml SRC_BTSTACK = \ $(addprefix lib/btstack/src/, $(SRC_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 SRC_BTSTACK += \ $(addprefix lib/btstack/src/classic/, $(SRC_CLASSIC_FILES)) diff --git a/extmod/btstack/btstack_config.h b/extmod/btstack/btstack_config.h index 0976bbe728..f420f47a5b 100644 --- a/extmod/btstack/btstack_config.h +++ b/extmod/btstack/btstack_config.h @@ -41,4 +41,7 @@ // BTstack HAL configuration #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 diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 8cb95d08f9..eaaedab158 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -132,6 +132,19 @@ CFLAGS_MOD += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0 LDFLAGS_MOD += $(LIBPTHREAD) 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_STANDALONE),1) @@ -176,10 +189,11 @@ SRC_C = \ alloc.c \ coverage.c \ fatfs_port.c \ + btstack_usb.c \ $(SRC_MOD) \ $(wildcard $(VARIANT_DIR)/*.c) -LIB_SRC_C = $(addprefix lib/,\ +LIB_SRC_C += $(addprefix lib/,\ $(LIB_SRC_C_EXTRA) \ timeutils/timeutils.c \ ) @@ -187,9 +201,10 @@ LIB_SRC_C = $(addprefix lib/,\ OBJ = $(PY_O) OBJ += $(addprefix $(BUILD)/, $(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 -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 # SRC_QSTR SRC_QSTR_AUTO_DEPS += diff --git a/ports/unix/btstack_usb.c b/ports/unix/btstack_usb.c new file mode 100644 index 0000000000..dfd2b91dfd --- /dev/null +++ b/ports/unix/btstack_usb.c @@ -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 +#include + +#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 diff --git a/ports/unix/main.c b/ports/unix/main.c index 34847a6ff8..5251fe8ae2 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -683,6 +683,11 @@ MP_NOINLINE int main_(int argc, char **argv) { } #endif + #if MICROPY_PY_BLUETOOTH + void mp_bluetooth_deinit(void); + mp_bluetooth_deinit(); + #endif + #if MICROPY_PY_THREAD mp_thread_deinit(); #endif diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index b23b6ce475..5708ceab68 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -311,9 +311,17 @@ void mp_unix_mark_exec(void); #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 \ const char *readline_hist[50]; \ void *mmap_region_head; \ + MICROPY_BLUETOOTH_ROOT_POINTERS \ // We need to provide a declaration/definition of alloca() // unless support for it is disabled. diff --git a/ports/unix/mphalport.h b/ports/unix/mphalport.h index 185a2d76f2..95ad63221e 100644 --- a/ports/unix/mphalport.h +++ b/ports/unix/mphalport.h @@ -95,3 +95,11 @@ static inline void mp_hal_delay_us(mp_uint_t us) { #define RAISE_ERRNO(err_flag, error_val) \ { if (err_flag == -1) \ { 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