diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5ddcf64c4..d0d6894e0 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -386,6 +386,10 @@ "${workspaceFolder}/bricks/virtualhub", "${workspaceFolder}/bricks/virtualhub/build", "${workspaceFolder}/bricks/virtualhub/build-debug", + "${workspaceFolder}/lib/btstack/chipset/realtek", + "${workspaceFolder}/lib/btstack/platform/libusb", + "${workspaceFolder}/lib/btstack/platform/posix", + "${workspaceFolder}/lib/btstack/src", "${workspaceFolder}/lib/lego", "${workspaceFolder}/lib/lwrb/src/include", "${workspaceFolder}/lib/pbio", @@ -394,7 +398,8 @@ "${workspaceFolder}/micropython", "${workspaceFolder}", "${workspaceFolder}/lib/umm_malloc/src", - "/usr/include/python3.10" + "/usr/include/python3.10", + "/usr/include/libusb-1.0" ], "defines": [ "MICROPY_MODULE_FROZEN_MPY", diff --git a/.vscode/launch.json b/.vscode/launch.json index db4dd7df7..35bb7342b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -75,6 +75,10 @@ "name": "PBIO_TEST_CONNECT_SOCKET", "value": "true" }, + { + "name": "UB500_INDEX", + "value": "0" + } ], "externalConsole": false, "MIMode": "gdb", diff --git a/bricks/_common/common.mk b/bricks/_common/common.mk index dd23d9063..0dc297d00 100644 --- a/bricks/_common/common.mk +++ b/bricks/_common/common.mk @@ -124,6 +124,18 @@ endif ifneq ($(strip $(PB_LIB_BTSTACK)),) INC += -I$(PBTOP)/lib/btstack/chipset/cc256x INC += -I$(PBTOP)/lib/btstack/src +ifeq ($(PBIO_PLATFORM),virtual_hub) +INC += -I$(PBTOP)/lib/btstack/platform/posix +INC += -I$(PBTOP)/lib/btstack/platform/embedded +INC += -I$(PBTOP)/lib/btstack/3rd-party/tinydir +INC += -I$(PBTOP)/lib/btstack/3rd-party/rijndael +INC += -I$(PBTOP)/lib/btstack/3rd-party/micro-ecc +INC += -I$(PBTOP)/lib/btstack/chipset/bcm +INC += -I$(PBTOP)/lib/btstack/chipset/intel +INC += -I$(PBTOP)/lib/btstack/chipset/realtek +INC += -I$(PBTOP)/lib/btstack/chipset/zephyr +INC += $(shell pkg-config libusb-1.0 --cflags) +endif endif ifeq ($(PB_LIB_LSM6DS3TR_C),1) INC += -I$(PBTOP)/lib/lsm6ds3tr_c_STdC/driver @@ -162,6 +174,9 @@ else ifeq ($(UNAME_S),Darwin) LDFLAGS += -Wl,-map,$@.map -Wl,-dead_strip endif LIBS = -lm +ifeq ($(PB_LIB_BTSTACK),lowenergy) +LIBS += $(shell pkg-config libusb-1.0 --libs) +endif else # end native, begin embedded CROSS_COMPILE ?= arm-none-eabi- ifeq ($(PB_MCU_FAMILY),STM32) @@ -395,6 +410,25 @@ BTSTACK_SRC_C += $(addprefix lib/btstack/chipset/cc256x/,\ btstack_chipset_cc256x.c \ ) +# libusb-specific BTStack sources for virtual_hub +ifeq ($(PBIO_PLATFORM),virtual_hub) +BTSTACK_SRC_C += $(addprefix lib/btstack/,\ + platform/libusb/hci_transport_h2_libusb.c \ + platform/posix/hci_dump_posix_stdout.c \ + platform/posix/btstack_tlv_posix.c \ + src/classic/btstack_link_key_db_tlv.c \ + src/ble/le_device_db_tlv.c \ + chipset/zephyr/btstack_chipset_zephyr.c \ + chipset/realtek/btstack_chipset_realtek.c \ + chipset/bcm/btstack_chipset_bcm.c \ + chipset/intel/btstack_chipset_intel_firmware.c \ + 3rd-party/rijndael/rijndael.c \ + 3rd-party/micro-ecc/uECC.c \ + ) +# Suppress unused variable warning for this file +$(BUILD)/lib/btstack/platform/libusb/hci_transport_h2_libusb.o: CFLAGS += -Wno-unused-variable +endif + # STM32 HAL COPT += -DUSE_FULL_LL_DRIVER @@ -529,11 +563,13 @@ endif ifeq ($(PB_LIB_BTSTACK),classic) OBJ += $(addprefix $(BUILD)/, $(BTSTACK_SRC_C:.c=.o)) +$(BUILD)/lib/btstack/%.o: CFLAGS += -Wno-error endif ifeq ($(PB_LIB_BTSTACK),lowenergy) OBJ += $(addprefix $(BUILD)/, $(BTSTACK_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(BTSTACK_BLE_SRC_C:.c=.o)) +$(BUILD)/lib/btstack/%.o: CFLAGS += -Wno-error endif ifeq ($(PB_LIB_STM32_HAL),1) diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index c127d63e0..8443f8444 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -115,9 +115,10 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\ drv/block_device/block_device_test.c \ drv/block_device/block_device_w25qxx_stm32.c \ drv/bluetooth/bluetooth.c \ - drv/bluetooth/bluetooth_btstack_stm32_hal.c \ drv/bluetooth/bluetooth_btstack.c \ drv/bluetooth/bluetooth_btstack_ev3.c \ + drv/bluetooth/bluetooth_btstack_posix.c \ + drv/bluetooth/bluetooth_btstack_stm32_hal.c \ drv/bluetooth/bluetooth_simulation.c \ drv/bluetooth/bluetooth_stm32_bluenrg.c \ drv/bluetooth/bluetooth_stm32_cc2640.c \ diff --git a/bricks/virtualhub/Makefile b/bricks/virtualhub/Makefile index bc24a90a4..357ac52d9 100644 --- a/bricks/virtualhub/Makefile +++ b/bricks/virtualhub/Makefile @@ -6,5 +6,6 @@ PB_MCU_FAMILY = native PB_FROZEN_MODULES = 1 MICROPY_ROM_TEXT_COMPRESSION = 1 PB_LIB_UMM_MALLOC = 1 +PB_LIB_BTSTACK = lowenergy include ../_common/common.mk diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.c b/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.c new file mode 100644 index 000000000..403437578 --- /dev/null +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.c @@ -0,0 +1,295 @@ +#include + +#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX + +#include "btstack_config.h" + +#include "bluetooth_btstack.h" +#include "bluetooth_btstack_posix.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "btstack.h" +#include "ble/le_device_db_tlv.h" +#include "btstack_chipset_realtek.h" +#include "btstack_tlv_posix.h" +#include "classic/btstack_link_key_db_tlv.h" +#include "hci.h" +#include "hci_transport_usb.h" +#include "hci_dump_posix_stdout.h" + +// This application only supports TP-Link UB500. +#define USB_MANUFACTURER_ID (BLUETOOTH_COMPANY_ID_REALTEK_SEMICONDUCTOR_CORPORATION) +#define USB_VENDOR_ID (0x2357) +#define USB_PRODUCT_ID (0x0604) + +#define TLV_DB_PATH_PREFIX "/tmp/btstack_" +#define TLV_DB_PATH_POSTFIX ".tlv" +static char tlv_db_path[100]; +static const btstack_tlv_t *tlv_impl; +static btstack_tlv_posix_t tlv_context; +static bd_addr_t local_addr; + +static const uint8_t read_static_address_command_complete_prefix[] = { 0x0e, 0x1b, 0x01, 0x09, 0xfc }; +static bd_addr_t static_address; +static int using_static_address; + +// We should not get here since we filtered device earlier, but this +// asserts that BTstack has discovered the same device from the port ID. +static void assert_vendor_and_product_id(uint16_t vendor_id, uint16_t product_id) { + if (vendor_id != USB_VENDOR_ID || product_id != USB_PRODUCT_ID) { + printf("Unexpected USB device: vendor ID 0x%04x, product ID 0x%04x\n", vendor_id, product_id); + exit(1); + } +} + +// As above, but for Bluetooth manufacturer ID from HCI local version info. +static void assert_manufacturer_id(uint16_t manufacturer_id) { + if (manufacturer_id != USB_MANUFACTURER_ID) { + printf("Unexpected Bluetooth manufacturer ID: 0x%04x\n", manufacturer_id); + exit(1); + } +} + +// Set by BTstack's packet handler when we get USB info and local version info. +static uint16_t usb_product_id; +static uint16_t usb_vendor_id; + +void pbdrv_bluetooth_btstack_set_chipset(pbdrv_bluetooth_btstack_local_version_info_t *device_info) { + + assert_manufacturer_id(device_info->manufacturer); + assert_vendor_and_product_id(usb_vendor_id, usb_product_id); + + btstack_chipset_realtek_set_lmp_subversion(device_info->lmp_pal_subversion); + btstack_chipset_realtek_set_product_id(usb_product_id); + + btstack_chipset_realtek_set_firmware_file_path("/lib/firmware/rtl_bt/rtl8761bu_fw.bin"); + btstack_chipset_realtek_set_config_file_path("/lib/firmware/rtl_bt/rtl8761bu_config.bin"); + + hci_set_chipset(btstack_chipset_realtek_instance()); +} + +static void noop_voidstararg(const void *) { +} + +static int noop_returnint(void) { + return 0; +} + +static const btstack_control_t noop_btstack_control = { + .init = noop_voidstararg, + .on = noop_returnint, + .off = noop_returnint, + .sleep = noop_returnint, + .wake = noop_returnint, + .register_for_power_notifications = NULL, +}; + +const btstack_control_t *pbdrv_bluetooth_btstack_posix_control_instance(void) { + return &noop_btstack_control; +} + +const hci_transport_t *pbdrv_bluetooth_btstack_posix_transport_instance(void) { + return hci_transport_usb_instance(); +} + +const void *pbdrv_bluetooth_btstack_posix_transport_config(void) { + return NULL; +} + +/** + * Attempts to find the specified UB500 device among connected USB devices. + * + * BTstack has several ways to specify which USB device to use, but none are + * suitable for our use case: we need to be able to specify multiple identical + * devices on the same system. + * + * This function uses libusb to find the correct device based on an index + * specified in the UB500_INDEX environment variable, and then passes the port + * numbers to BTstack. + * + * @return ::PBIO_SUCCESS on success, or an ::ERROR_NO_DEV error if the device + * could not be found, so it can continue without Bluetooth. + */ +pbio_error_t pbdrv_bluetooth_btstack_platform_init(void) { + + const char *env_index = getenv("UB500_INDEX"); + if (!env_index) { + // Silently continue without Bluetooth if not specified. + return PBIO_ERROR_NO_DEV; + } + + printf("Looking for UB500 with index %s.\n", env_index); + + int target_index = atoi(env_index); + + libusb_context *ctx = NULL; + libusb_device **list = NULL; + + if (libusb_init(&ctx) < 0) { + return PBIO_ERROR_NO_DEV; + } + + ssize_t count = libusb_get_device_list(ctx, &list); + if (count < 0) { + libusb_exit(ctx); + return PBIO_ERROR_NO_DEV; + } + + libusb_device *match = NULL; + int match_count = 0; + + for (ssize_t i = 0; i < count; i++) { + libusb_device *dev = list[i]; + struct libusb_device_descriptor desc; + if (libusb_get_device_descriptor(dev, &desc) != 0) { + continue; + } + if (desc.idVendor == USB_VENDOR_ID && desc.idProduct == USB_PRODUCT_ID) { + if (match_count == target_index) { + match = dev; + break; + } + match_count++; + } + } + + pbio_error_t err = PBIO_SUCCESS; + + if (!match) { + err = PBIO_ERROR_NO_DEV; + goto exit; + } + + printf("USB device found.\n"); + + uint8_t ports[16]; + int port_count = libusb_get_port_numbers(match, ports, sizeof(ports)); + + if (port_count) { + // Tell BTstack to use this port path. + hci_transport_usb_set_path(port_count, ports); + printf("Using port: "); + for (int i = 0; i < port_count; i++) { + printf("%u%s", ports[i], i == port_count - 1 ? "" : "."); + } + printf("\n"); + } else { + err = PBIO_ERROR_NO_DEV; + } + +exit: + libusb_free_device_list(list, 1); + libusb_exit(ctx); + + if (err != PBIO_SUCCESS) { + printf("Could not find specified device or port.\n"); + } + return err; +} + +void pbdrv_bluetooth_btstack_platform_poll(void) { + + btstack_run_loop_base_poll_data_sources(); + + int nfds = btstack_linked_list_count(&btstack_run_loop_base_data_sources); + struct pollfd fds[nfds]; + + btstack_linked_list_iterator_t it; + int i; + for (i = 0, btstack_linked_list_iterator_init(&it, &btstack_run_loop_base_data_sources); + btstack_linked_list_iterator_has_next(&it); ++i) { + // cache pointer to next data_source to allow data source to remove itself + btstack_data_source_t *ds = (void *)btstack_linked_list_iterator_next(&it); + + // Identify data source FDs that are ready for reading or writing. + struct pollfd *pfd = &fds[i]; + pfd->fd = ds->source.fd; + pfd->events = 0; + if (ds->flags & DATA_SOURCE_CALLBACK_READ) { + pfd->events |= POLLIN; + } + if (ds->flags & DATA_SOURCE_CALLBACK_WRITE) { + pfd->events |= POLLOUT; + } + + } + + int err = poll(fds, nfds, 0); + if (err < 0) { + printf("btstack: poll() returned %d, ignoring\n", errno); + } else if (err > 0) { + // Some fd was ready. + for (i = 0, btstack_linked_list_iterator_init(&it, &btstack_run_loop_base_data_sources); + btstack_linked_list_iterator_has_next(&it); ++i) { + btstack_data_source_t *ds = (void *)btstack_linked_list_iterator_next(&it); + struct pollfd *pfd = &fds[i]; + if (pfd->revents & POLLIN) { + ds->process(ds, DATA_SOURCE_CALLBACK_READ); + } else if (pfd->revents & POLLOUT) { + ds->process(ds, DATA_SOURCE_CALLBACK_WRITE); + } else if (pfd->revents & POLLERR) { + printf("btstack: poll() error on fd %d\n", pfd->fd); + } + } + } +} + +void pbdrv_bluetooth_btstack_platform_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_TRANSPORT_USB_INFO: { + usb_vendor_id = hci_event_transport_usb_info_get_vendor_id(packet); + usb_product_id = hci_event_transport_usb_info_get_product_id(packet); + assert_vendor_and_product_id(usb_vendor_id, usb_product_id); + 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 = 1; + } + break; + case BTSTACK_EVENT_STATE: + switch (btstack_event_state_get_state(packet)) { + case HCI_STATE_WORKING: + gap_local_bd_addr(local_addr); + btstack_strcpy(tlv_db_path, sizeof(tlv_db_path), TLV_DB_PATH_PREFIX); + btstack_strcat(tlv_db_path, sizeof(tlv_db_path), bd_addr_to_str_with_delimiter(local_addr, '-')); + btstack_strcat(tlv_db_path, sizeof(tlv_db_path), TLV_DB_PATH_POSTFIX); + printf("\n"); + tlv_impl = btstack_tlv_posix_init_instance(&tlv_context, tlv_db_path); + btstack_tlv_set_instance(tlv_impl, &tlv_context); + #ifdef ENABLE_CLASSIC + hci_set_link_key_db(btstack_link_key_db_tlv_get_instance(tlv_impl, &tlv_context)); + #endif + #ifdef ENABLE_BLE + le_device_db_tlv_configure(tlv_impl, &tlv_context); + #endif + printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr)); + break; + case HCI_STATE_OFF: + btstack_tlv_posix_deinit(&tlv_context); + break; + default: + break; + } + break; + default: + break; + } +} + +#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.h b/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.h new file mode 100644 index 000000000..85c73adb9 --- /dev/null +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack_posix.h @@ -0,0 +1,12 @@ +#ifndef PBDRV_BLUETOOTH_BLUETOOTH_BTSTACK_POSIX_H +#define PBDRV_BLUETOOTH_BLUETOOTH_BTSTACK_POSIX_H + +#include + +const btstack_control_t *pbdrv_bluetooth_btstack_posix_control_instance(void); + +const hci_transport_t *pbdrv_bluetooth_btstack_posix_transport_instance(void); + +const void *pbdrv_bluetooth_btstack_posix_transport_config(void); + +#endif diff --git a/lib/pbio/platform/virtual_hub/btstack_config.h b/lib/pbio/platform/virtual_hub/btstack_config.h new file mode 100644 index 000000000..75531363a --- /dev/null +++ b/lib/pbio/platform/virtual_hub/btstack_config.h @@ -0,0 +1,66 @@ +// +// btstack_config.h for libusb port +// +// Documentation: https://bluekitchen-gmbh.com/btstack/#how_to/ +// + +#ifndef BTSTACK_CONFIG_H +#define BTSTACK_CONFIG_H + +// Port related features +#define HAVE_ASSERT +#define HAVE_BTSTACK_STDIN +#define HAVE_MALLOC +#define HAVE_POSIX_FILE_IO +#define HAVE_POSIX_TIME + +#define ENABLE_LOG_INFO +#define ENABLE_LOG_ERROR +#define ENABLE_LOG_DEBUG + +// BTstack features that can be enabled +#define ENABLE_ATT_DELAYED_RESPONSE +#define ENABLE_BLE +#define ENABLE_BTSTACK_STDIN_LOGGING +#define ENABLE_CLASSIC +#define ENABLE_CROSS_TRANSPORT_KEY_DERIVATION +#define ENABLE_HFP_WIDE_BAND_SPEECH +#define ENABLE_L2CAP_ENHANCED_RETRANSMISSION_MODE +#define ENABLE_LE_CENTRAL +#define ENABLE_L2CAP_LE_CREDIT_BASED_FLOW_CONTROL_MODE +#define ENABLE_LE_DATA_LENGTH_EXTENSION +#define ENABLE_LE_PERIPHERAL +#define ENABLE_LE_PRIVACY_ADDRESS_RESOLUTION +#define ENABLE_LE_SECURE_CONNECTIONS +#define ENABLE_LOG_ERROR +#define ENABLE_LOG_INFO +#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS +#define ENABLE_PRINTF_HEXDUMP +#define ENABLE_SCO_OVER_HCI +#define ENABLE_SDP_DES_DUMP +#define ENABLE_SOFTWARE_AES128 + +// BTstack configuration. buffers, sizes, ... +#define HCI_ACL_PAYLOAD_SIZE (1691 + 4) +#define HCI_INCOMING_PRE_BUFFER_SIZE 14 // sizeof BNEP header, avoid memcpy + +#define NVM_NUM_DEVICE_DB_ENTRIES 16 +#define NVM_NUM_LINK_KEYS 16 + +// Mesh Configuration +#define ENABLE_MESH +#define ENABLE_MESH_ADV_BEARER +#define ENABLE_MESH_GATT_BEARER +#define ENABLE_MESH_PB_ADV +#define ENABLE_MESH_PB_GATT +#define ENABLE_MESH_PROVISIONER +#define ENABLE_MESH_PROXY_SERVER + +#define MAX_NR_MESH_SUBNETS 2 +#define MAX_NR_MESH_TRANSPORT_KEYS 16 +#define MAX_NR_MESH_VIRTUAL_ADDRESSES 16 + +// allow for one NetKey update +#define MAX_NR_MESH_NETWORK_KEYS (MAX_NR_MESH_SUBNETS + 1) + +#endif diff --git a/lib/pbio/platform/virtual_hub/pbdrvconfig.h b/lib/pbio/platform/virtual_hub/pbdrvconfig.h index 1130ebcf6..c618a4587 100644 --- a/lib/pbio/platform/virtual_hub/pbdrvconfig.h +++ b/lib/pbio/platform/virtual_hub/pbdrvconfig.h @@ -11,7 +11,10 @@ // Use Bluetooth simulation locally. #ifndef PBDRV_CONFIG_RUN_ON_CI #define PBDRV_CONFIG_BLUETOOTH (1) -#define PBDRV_CONFIG_BLUETOOTH_SIMULATION (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND (LWP3_HUB_KIND_TECHNIC_LARGE) #endif // PBDRV_CONFIG_RUN_ON_CI #define PBDRV_CONFIG_BUTTON (1) diff --git a/lib/pbio/platform/virtual_hub/platform.c b/lib/pbio/platform/virtual_hub/platform.c index d2b3a1b85..b05da3b81 100644 --- a/lib/pbio/platform/virtual_hub/platform.c +++ b/lib/pbio/platform/virtual_hub/platform.c @@ -11,6 +11,8 @@ #include #include "../../drv/motor_driver/motor_driver_virtual_simulation.h" +#include "../../drv/bluetooth/bluetooth_btstack.h" +#include "../../drv/bluetooth/bluetooth_btstack_posix.h" #include "pbio_os_config.h" @@ -142,6 +144,15 @@ const pbdrv_motor_driver_virtual_simulation_platform_data_t }, }; +const pbdrv_bluetooth_btstack_platform_data_t pbdrv_bluetooth_btstack_platform_data = { + .transport_instance = pbdrv_bluetooth_btstack_posix_transport_instance, + .transport_config = pbdrv_bluetooth_btstack_posix_transport_config, + .chipset_instance = NULL, + .control_instance = pbdrv_bluetooth_btstack_posix_control_instance, + .er_key = (const uint8_t *)"placeholderplaceholder", + .ir_key = (const uint8_t *)"placeholderplaceholder", +}; + // Socket used to send data to Python animation. static int data_socket = -1; static struct sockaddr_in serv_addr;