diff --git a/docs/library/bluetooth.rst b/docs/library/bluetooth.rst index b09c370abd46d..92647c862d80b 100644 --- a/docs/library/bluetooth.rst +++ b/docs/library/bluetooth.rst @@ -765,3 +765,119 @@ Constructor - A 16-bit integer. e.g. ``0x2908``. - A 128-bit UUID string. e.g. ``'6E400001-B5A3-F393-E0A9-E50E24DCCA9E'``. + + +HCI Commands +----------- + +Some ports optionally support direct access to the Bluetooth controller via Host Controller Interface (HCI) commands. +This is an advanced feature that allows for more direct manipulation of the Bluetooth stack. + +.. method:: BLE.hci_cmd(ogf, ocf, request_data, response_data, /) + + Send a raw HCI command to the Bluetooth controller and receive a response. + + This function is only available when the ``MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD`` + define is enabled during build. + + **Parameters:** + + - *ogf* (int): Opcode Group Field, indicating the command group. + - *ocf* (int): Opcode Command Field, indicating the specific command within the group. + - *request_data* (buffer): Data to send to the controller (must implement the buffer protocol). + - *response_data* (buffer): Buffer to receive the response into (must implement the buffer protocol). + + **Return Value:** + + Returns the HCI status code from the command (0 for success, or a specific error code). + + **Example:** + + .. code-block:: python + + import bluetooth + import struct + + # Initialize the BLE interface + ble = bluetooth.BLE() + ble.active(True) + + # Read Local Version Information + cmd_buf = bytearray(0) # Empty payload + resp_buf = bytearray(20) # Buffer for response + + # OGF=0x04 (Information Parameters) + # OCF=0x0001 (Read Local Version Information) + status = ble.hci_cmd(0x04, 0x0001, cmd_buf, resp_buf) + + if status == 0: + # Parse the response + hci_version = resp_buf[0] + hci_revision = struct.unpack(" BLE_ACK_TIMEOUT_MS) { @@ -513,8 +513,7 @@ static int tl_ble_wait_resp(void) { } // C2 set IPCC flag -- process the data, clear the flag, and re-enable IRQs. - tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); - return 0; + return tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, parse); } // Synchronously send a BLE command. @@ -522,7 +521,7 @@ static void tl_ble_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len) // Poll for completion rather than wait for IRQ->scheduler. LL_C1_IPCC_DisableReceiveChannel(IPCC, IPCC_CH_BLE); tl_hci_cmd(ipcc_membuf_ble_cmd_buf, IPCC_CH_BLE, HCI_KIND_BT_CMD, opcode, buf, len); - tl_ble_wait_resp(); + tl_ble_wait_resp(NULL); } /******************************************************************************/ @@ -797,4 +796,49 @@ static mp_obj_t rfcore_sys_hci(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_sys_hci_obj, 3, 4, rfcore_sys_hci); +static void rfcore_ble_hci_response_to_buffer(void *env, const uint8_t *buf, size_t len) { + DEBUG_printf("rfcore_ble_hci_response_to_buffer len 0x%x\n", len); + mp_obj_t *rsp = (mp_obj_t *)env; + if (*rsp == mp_const_none) { + *rsp = mp_obj_new_bytes(buf, len); + } else { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(*rsp, &bufinfo, MP_BUFFER_WRITE); + if (bufinfo.len < len) { + mp_raise_OSError(-len); + } + memcpy(bufinfo.buf, buf, len); + } +} + +static mp_obj_t rfcore_ble_hci(size_t n_args, const mp_obj_t *args) { + if (ipcc_mem_dev_info_tab.fus.table_state == MAGIC_IPCC_MEM_INCORRECT) { + mp_raise_OSError(MP_EINVAL); + } + mp_obj_t cmd = args[0]; + mp_obj_t rsp = mp_const_none; + bool return_len = false; + if (n_args == 2) { + rsp = args[1]; + // response buffer passed in, so return rsp length. + return_len = true; + } + + mp_buffer_info_t bufinfo = {0}; + mp_get_buffer_raise(cmd, &bufinfo, MP_BUFFER_READ); + + // Poll for completion rather than wait for IRQ->scheduler. Is re-enabled in tl_check_msg. + LL_C1_IPCC_DisableReceiveChannel(IPCC, IPCC_CH_BLE); + + rfcore_ble_hci_cmd(bufinfo.len, bufinfo.buf); + + parse_hci_info_t parse = { rfcore_ble_hci_response_to_buffer, &rsp, false }; + size_t ret_len = tl_ble_wait_resp(&parse); + if (return_len) { + return MP_OBJ_NEW_SMALL_INT(ret_len); + } + return rsp; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_ble_hci_obj, 1, 2, rfcore_ble_hci); + #endif // defined(STM32WB) diff --git a/ports/stm32/rfcore.h b/ports/stm32/rfcore.h index 39267b325ec4d..bac14a843ffc4 100644 --- a/ports/stm32/rfcore.h +++ b/ports/stm32/rfcore.h @@ -44,5 +44,6 @@ void rfcore_end_flash_erase(void); MP_DECLARE_CONST_FUN_OBJ_0(rfcore_status_obj); MP_DECLARE_CONST_FUN_OBJ_1(rfcore_fw_version_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_sys_hci_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_ble_hci_obj); #endif // MICROPY_INCLUDED_STM32_RFCORE_H diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c index 765146677e8c5..59e732761eb5d 100644 --- a/ports/unix/mpbthciport.c +++ b/ports/unix/mpbthciport.c @@ -44,6 +44,7 @@ #include #include #include +#include #define DEBUG_printf(...) // printf(__VA_ARGS__) @@ -147,7 +148,12 @@ static int configure_uart(void) { toptions.c_cflag |= CRTSCTS; // 1Mbit (TODO: make this configurable). + #ifdef B1000000 speed_t brate = B1000000; + #else + // macOS doesn't have B1000000, use B230400 as fallback + speed_t brate = B230400; + #endif cfsetospeed(&toptions, brate); cfsetispeed(&toptions, brate);