diff --git a/devices/ble_hci/common-hal/_bleio/Adapter.c b/devices/ble_hci/common-hal/_bleio/Adapter.c index 005dd0aeb03..0ea05c44e5c 100644 --- a/devices/ble_hci/common-hal/_bleio/Adapter.c +++ b/devices/ble_hci/common-hal/_bleio/Adapter.c @@ -160,6 +160,13 @@ static void check_enabled(bleio_adapter_obj_t *adapter) { } } +// A controller that supports extended advertising refuses legacy advertising +// commands (Command Disallowed) once any extended command has been sent, so +// use the extended command set exclusively on such controllers. +static bool adapter_uses_extended_advertising(bleio_adapter_obj_t *adapter) { + return BT_FEAT_LE_EXT_ADV(adapter->features); +} + // static bool adapter_on_ble_evt(ble_evt_t *ble_evt, void *self_in) { // bleio_adapter_obj_t *self = (bleio_adapter_obj_t*)self_in; @@ -296,17 +303,6 @@ static void bleio_adapter_hci_init(bleio_adapter_obj_t *self) { self->max_acl_buffer_len = acl_max_len; self->max_acl_num_buffers = acl_max_num; } - - // Get max advertising length if extended advertising is supported. - if (BT_FEAT_LE_EXT_ADV(self->features)) { - uint16_t max_adv_data_len; - if (hci_le_read_maximum_advertising_data_length(&max_adv_data_len) != HCI_OK) { - mp_raise_bleio_BluetoothError(MP_ERROR_TEXT("Could not get max advertising length")); - } - self->max_adv_data_len = max_adv_data_len; - } else { - self->max_adv_data_len = MAX_ADVERTISEMENT_SIZE; - } } void common_hal_bleio_adapter_construct_hci_uart(bleio_adapter_obj_t *self, busio_uart_obj_t *uart, digitalio_digitalinout_obj_t *rts, digitalio_digitalinout_obj_t *cts) { @@ -343,7 +339,6 @@ void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enable // Enabling or disabling: stop any current activity; reset to known state. hci_reset(); self->now_advertising = false; - self->extended_advertising = false; self->circuitpython_advertising = false; self->advertising_timeout_msecs = 0; @@ -633,19 +628,17 @@ uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, memcpy(&peer_addr.a.val, directed_to->bytes, sizeof(peer_addr.a.val)); } - bool extended = - advertising_data_len > self->max_adv_data_len || scan_response_data_len > self->max_adv_data_len; - - if (extended) { - if (!BT_FEAT_LE_EXT_ADV(self->features)) { - mp_raise_bleio_BluetoothError(MP_ERROR_TEXT("Data length needs extended advertising, but this adapter does not support it")); - } - - uint16_t props = 0; + if (adapter_uses_extended_advertising(self)) { + // Use legacy PDUs (ADV_IND etc.) so that 4.x centrals still see us and the + // 31-byte data limit applies unchanged; only the HCI command set is extended. + uint16_t props = BT_HCI_LE_ADV_PROP_LEGACY; if (connectable) { - props |= BT_HCI_LE_ADV_PROP_CONN; - } - if (scan_response_data_len > 0) { + if (directed_to) { + props |= BT_HCI_LE_ADV_PROP_CONN | BT_HCI_LE_ADV_PROP_DIRECT; + } else { + props |= BT_HCI_LE_ADV_PROP_CONN | BT_HCI_LE_ADV_PROP_SCAN; + } + } else if (scan_response_data_len > 0) { props |= BT_HCI_LE_ADV_PROP_SCAN; } @@ -660,21 +653,44 @@ uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, interval_units, // max interval 0b111, // channel map: channels 37, 38, 39 anonymous ? BT_ADDR_LE_RANDOM : BT_ADDR_LE_PUBLIC, - &peer_addr, // peer_addr, + &peer_addr, 0x00, // filter policy: no filter - DEFAULT_TX_POWER, - BT_HCI_LE_EXT_SCAN_PHY_1M, // Secondary PHY to use - 0x00, // AUX_ADV_IND shall be sent prior to next adv event - BT_HCI_LE_EXT_SCAN_PHY_1M, // Secondary PHY to use - 0x00, // Advertising SID - 0x00 // Scan req notify disable + BT_HCI_LE_ADV_TX_POWER_NO_PREF, + BT_HCI_LE_EXT_SCAN_PHY_1M, // primary PHY + 0x00, // secondary max skip (unused with legacy PDUs) + BT_HCI_LE_EXT_SCAN_PHY_1M, // secondary PHY (unused with legacy PDUs) + 0x00, // advertising SID + 0x00 // scan request notification disabled )); - // We can use the duration mechanism provided, instead of our own. - self->advertising_timeout_msecs = 0; + hci_check_error( + hci_le_set_extended_advertising_data( + 0, // handle + BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA, + BT_HCI_LE_EXT_ADV_FRAG_DISABLED, + advertising_data_len, + (uint8_t *)advertising_data)); + + // A scannable set must have scan response data defined before it is enabled, + // even if that data is empty. + if (props & BT_HCI_LE_ADV_PROP_SCAN) { + hci_check_error( + hci_le_set_extended_scan_response_data( + 0, // handle + BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA, + BT_HCI_LE_EXT_ADV_FRAG_DISABLED, + scan_response_data_len, + (uint8_t *)scan_response_data)); + } + + // Use our own timeout rather than the controller's duration, so that we don't + // depend on receiving the LE Advertising Set Terminated event. + self->advertising_timeout_msecs = timeout * 1000; + self->advertising_start_ticks = supervisor_ticks_ms64(); + // Duration 0 and max events 0 mean "advertise until disabled by the host". uint8_t handle[1] = { 0 }; - uint16_t duration_10msec[1] = { timeout * 100 }; + uint16_t duration_10msec[1] = { 0 }; uint8_t max_ext_adv_evts[1] = { 0 }; hci_check_error( hci_le_set_extended_advertising_enable( @@ -684,8 +700,6 @@ uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, duration_10msec, max_ext_adv_evts )); - - self->extended_advertising = true; } else { // Legacy advertising (not extended). @@ -732,7 +746,6 @@ uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, // Start advertising. hci_check_error(hci_le_set_advertising_enable(BT_HCI_LE_ADV_ENABLE)); - self->extended_advertising = false; } // end legacy advertising setup vm_used_ble = true; @@ -790,10 +803,18 @@ void common_hal_bleio_adapter_stop_advertising(bleio_adapter_obj_t *self) { check_enabled(self); self->now_advertising = false; - self->extended_advertising = false; self->circuitpython_advertising = false; - int result = hci_le_set_advertising_enable(BT_HCI_LE_ADV_DISABLE); + int result; + if (adapter_uses_extended_advertising(self)) { + // Duration and max events are ignored when disabling. + uint8_t handle[1] = { 0 }; + uint16_t duration_10msec[1] = { 0 }; + uint8_t max_ext_adv_evts[1] = { 0 }; + result = hci_le_set_extended_advertising_enable(BT_HCI_LE_ADV_DISABLE, 1, handle, duration_10msec, max_ext_adv_evts); + } else { + result = hci_le_set_advertising_enable(BT_HCI_LE_ADV_DISABLE); + } // OK if we're already stopped. There seems to be an ESP32 HCI bug: // If advertising is already off, then LE_SET_ADV_ENABLE does not return a response. if (result != HCI_RESPONSE_TIMEOUT) { @@ -807,7 +828,6 @@ void common_hal_bleio_adapter_stop_advertising(bleio_adapter_obj_t *self) { // Don't ask the adapter to stop. void bleio_adapter_advertising_was_stopped(bleio_adapter_obj_t *self) { self->now_advertising = false; - self->extended_advertising = false; self->circuitpython_advertising = false; } diff --git a/devices/ble_hci/common-hal/_bleio/Adapter.h b/devices/ble_hci/common-hal/_bleio/Adapter.h index c018c676171..ee7672649dd 100644 --- a/devices/ble_hci/common-hal/_bleio/Adapter.h +++ b/devices/ble_hci/common-hal/_bleio/Adapter.h @@ -34,7 +34,6 @@ typedef struct _bleio_adapter_obj_t { digitalio_digitalinout_obj_t *cts_digitalinout; bool allocated; // True when in use. bool now_advertising; - bool extended_advertising; bool circuitpython_advertising; bool enabled; @@ -56,7 +55,6 @@ typedef struct _bleio_adapter_obj_t { uint16_t max_acl_buffer_len; uint16_t max_acl_num_buffers; - uint16_t max_adv_data_len; uint8_t features[8]; // Supported BLE features. // All the local attributes for this device. The index into the list diff --git a/devices/ble_hci/common-hal/_bleio/hci.c b/devices/ble_hci/common-hal/_bleio/hci.c index 126f05fbae2..a5d63e9015c 100644 --- a/devices/ble_hci/common-hal/_bleio/hci.c +++ b/devices/ble_hci/common-hal/_bleio/hci.c @@ -489,7 +489,9 @@ hci_result_t hci_send_acl_pkt(uint16_t handle, uint8_t cid, uint16_t data_len, u acl_data_t *acl_data = (acl_data_t *)acl_pkt->data; acl_pkt->pkt_type = H4_ACL; acl_pkt->handle = handle; - acl_pkt->pb = ACL_DATA_PB_FIRST_FLUSH; + // Host-to-controller LE data must use the non-flushable start flag; NimBLE + // controllers (ESP32-C6) silently discard packets with ACL_DATA_PB_FIRST_FLUSH. + acl_pkt->pb = ACL_DATA_PB_FIRST_NON_FLUSH; acl_pkt->bc = 0; acl_pkt->data_len = (uint16_t)(sizeof(acl_data_t) + data_len); acl_data->acl_data_len = data_len; @@ -608,7 +610,9 @@ hci_result_t hci_le_set_extended_advertising_parameters(uint8_t handle, uint16_t .prim_channel_map = prim_channel_map, .own_addr_type = own_addr_type, // .peer_addr set below. + .filter_policy = filter_policy, .tx_power = tx_power, + .prim_adv_phy = prim_adv_phy, .sec_adv_max_skip = sec_adv_max_skip, .sec_adv_phy = sec_adv_phy, .sid = sid, @@ -619,6 +623,7 @@ hci_result_t hci_le_set_extended_advertising_parameters(uint8_t handle, uint16_t sizeof_field(struct bt_hci_cp_le_set_ext_adv_param, prim_min_interval)); memcpy(params.prim_max_interval, (void *)&prim_max_interval, sizeof_field(struct bt_hci_cp_le_set_ext_adv_param, prim_max_interval)); + params.peer_addr.type = peer_addr->type; memcpy(params.peer_addr.a.val, peer_addr->a.val, sizeof_field(bt_addr_le_t, a.val)); return send_command(BT_HCI_OP_LE_SET_EXT_ADV_PARAM, sizeof(params), ¶ms); } @@ -672,6 +677,18 @@ hci_result_t hci_le_set_extended_advertising_data(uint8_t handle, uint8_t op, ui return send_command(BT_HCI_OP_LE_SET_EXT_ADV_DATA, sizeof(params) - (max_len - valid_len), ¶ms); } +hci_result_t hci_le_set_extended_scan_response_data(uint8_t handle, uint8_t op, uint8_t frag_pref, uint8_t len, uint8_t data[]) { + const uint8_t max_len = sizeof_field(struct bt_hci_cp_le_set_ext_scan_rsp_data, data); + uint8_t valid_len = MIN(len, max_len); + struct bt_hci_cp_le_set_ext_scan_rsp_data params = { + .handle = handle, + .op = op, + .frag_pref = frag_pref, + .len = valid_len, + }; + memcpy(params.data, data, valid_len); + return send_command(BT_HCI_OP_LE_SET_EXT_SCAN_RSP_DATA, sizeof(params) - (max_len - valid_len), ¶ms); +} hci_result_t hci_le_set_scan_response_data(uint8_t len, uint8_t data[]) { struct bt_hci_cp_le_set_scan_rsp_data params = { diff --git a/devices/ble_hci/common-hal/_bleio/hci.h b/devices/ble_hci/common-hal/_bleio/hci.h index fffed7ee151..52da0e246a0 100644 --- a/devices/ble_hci/common-hal/_bleio/hci.h +++ b/devices/ble_hci/common-hal/_bleio/hci.h @@ -68,6 +68,9 @@ hci_result_t hci_le_set_advertising_parameters(uint16_t min_interval, uint16_t m hci_result_t hci_le_set_extended_advertising_data(uint8_t handle, uint8_t op, uint8_t frag_pref, uint8_t len, uint8_t data[]); hci_result_t hci_le_set_extended_advertising_enable(uint8_t enable, uint8_t set_num, uint8_t handle[], uint16_t duration[], uint8_t max_ext_adv_evts[]); hci_result_t hci_le_set_extended_advertising_parameters(uint8_t handle, uint16_t props, uint32_t prim_min_interval, uint32_t prim_max_interval, uint8_t prim_channel_map, uint8_t own_addr_type, bt_addr_le_t *peer_addr, uint8_t filter_policy, int8_t tx_power, uint8_t prim_adv_phy, uint8_t sec_adv_max_skip, uint8_t sec_adv_phy, uint8_t sid, uint8_t scan_req_notify_enable); +// Sends at most one fragment (251 bytes); longer data is truncated. +// Returns HCI_OK or the HCI error status; no output values. +hci_result_t hci_le_set_extended_scan_response_data(uint8_t handle, uint8_t op, uint8_t frag_pref, uint8_t len, uint8_t data[]); hci_result_t hci_le_set_random_address(uint8_t addr[6]); hci_result_t hci_le_set_scan_enable(uint8_t enable, uint8_t filter_dup);