Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 60 additions & 40 deletions devices/ble_hci/common-hal/_bleio/Adapter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}

Expand All @@ -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 };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude wrote this and then I rewrote it for clarity:

There are two ways to end a timed extended advertisement.

  • A. The controller does it. The host gives LE Set Extended Advertising Enable a nonzero duration and the controller reports the timeout expiry with the LE Advertising Set Terminated event.
  • B. The host does it, by passing duration 0 (which means "advertise until the host disables it", Vol 4 Part E §7.8.56) and calling the disable command itself when its own timer expires.

This PR uses B, the same one the legacy path already uses via advertising_timeout_msecs, so both paths share one mechanism. A needs the LE event mask extended to include the terminated event, and needs a handler for subevent 0x12. Without those the adapter never learns that advertising stopped and adapter.advertising stays True.

Also in the A version the uint16 10 ms duration tops out at 655 s, less than MAX_ANONYMOUS_ADV_TIMEOUT_SECS. But moving to A is the natural follow-on when anonymous=True is implemented properly, because that implementation would restart the set with a fresh random address each time the controller reports it terminated.

I added comments explaining the zero timeouts.

uint8_t max_ext_adv_evts[1] = { 0 };
hci_check_error(
hci_le_set_extended_advertising_enable(
Expand All @@ -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).

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}

Expand Down
2 changes: 0 additions & 2 deletions devices/ble_hci/common-hal/_bleio/Adapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down
19 changes: 18 additions & 1 deletion devices/ble_hci/common-hal/_bleio/hci.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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), &params);
}
Expand Down Expand Up @@ -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), &params);
}

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), &params);
}

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 = {
Expand Down
3 changes: 3 additions & 0 deletions devices/ble_hci/common-hal/_bleio/hci.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading