Skip to content

Commit 240a5fc

Browse files
committed
pbio/drv/bluetooth: Broadcast on the Move Hub while connected.
The BlueNRG-MS GAP layer refuses to enter any advertising mode while a computer is connected, so aci_gap_set_non_connectable() answered BLE_STATUS_FAILED and nothing was broadcast. The driver mapped that one error to success so that programs using broadcasting could at least run. Drive the link layer directly instead, with the set advertising parameters, set advertising data and set advertise enable sequence from UM1865 section 3.3. The chip is already configured for role mode 4, which supports advertising alongside connections and scanning, and it accepts all three commands while a computer is connected. Asking the chip first was no help: HCI_LE_Read_Supported_States answers with bit 20 clear, but it also claims not to support advertising while scanning and not to support scanning while a slave, both of which demonstrably work, so its answer understates what this stack can do. Advertising is now enabled behind the GAP layer's back, so stopping has to disable it the same way, and the discoverable mode has to be left before the parameters can be set. The link layer advertises exactly the payload it is given, so the service UUID and TX power AD types left over from discovery no longer have to be deleted to make room. Broadcasts therefore no longer carry the Flags AD type, which is what the BTstack hubs already send. The advertising interval has to be requested as a range. The chip fits a new activity to the anchor period it is already running for the connection and for any scanning, and answers 0x85 BLE_STATUS_INTERVAL_TOO_LARGE when no multiple of that period falls between the requested minimum and maximum. A fixed 100ms therefore failed the moment the hub was also observing or connected, which is precisely the case being fixed here. The GAP layer picked its own range internally and so never ran into it. 100ms is the fastest this Bluetooth 4.1 controller allows for non-connectable advertising and is the bottom of the range; 200ms at the top measures the same as the GAP layer managed, 2.45 against 2.53 broadcasts a second reaching a third hub, which is inside the run to run spread. Drop the note about broadcasting interfering with observing as well. Measured on a Move Hub with the 50% scan duty cycle, reception is unchanged by broadcasting at the same time: 13.4 advertisements a second from a reference transmitter either way, with the same distribution of gaps. That measurement depends on the duty cycle, so this belongs after that change. Fixes pybricks/support#2826
1 parent 8c861c1 commit 240a5fc

3 files changed

Lines changed: 88 additions & 36 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
- Fixed slow broadcasting while observing at the same time ([support#2822]).
1414
- Fixed slow observing while connected to a computer or app ([support#2822]).
1515
- Fixed connected remote preventing Move Hub from advertising.
16+
- Fixed Move Hub not broadcasting while connected to a PC ([support#2826]).
1617

1718
[support#2822]: https://github.com/orgs/pybricks/discussions/2822
19+
[support#2826]: https://github.com/pybricks/support/issues/2826
1820

1921
[Unreleased]: https://github.com/pybricks/pybricks-micropython/compare/v4.1.0b3...HEAD
2022

‎lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c‎

Lines changed: 86 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,25 @@ const char *pbdrv_bluetooth_get_fw_version(void) {
236236
return pbdrv_bluetooth_fw_version;
237237
}
238238

239+
/**
240+
* Sends a link layer command with constant parameters.
241+
*
242+
* The BlueNRG library's hci_le_*() functions for these commands wait for the
243+
* response, which this driver cannot do, so the command is packed here.
244+
*
245+
* @param [in] ocf The opcode command field.
246+
* @param [in] params The command parameters.
247+
* @param [in] plen The size of @p params in bytes.
248+
*/
249+
static void hci_send_le_command(uint16_t ocf, const uint8_t *params, uint8_t plen) {
250+
struct hci_request rq = {
251+
.opcode = cmd_opcode_pack(OGF_LE_CTL, ocf),
252+
.cparam = (void *)params,
253+
.clen = plen,
254+
};
255+
hci_send_req(&rq);
256+
}
257+
239258
/**
240259
* Sets advertising data and enables advertisements.
241260
*/
@@ -292,10 +311,21 @@ pbio_error_t pbdrv_bluetooth_stop_advertising_func(pbio_os_state_t *state, void
292311

293312
// REVISIT: might need to delete advertising data here
294313

295-
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
296-
aci_gap_set_non_discoverable_begin();
297-
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
298-
// aci_gap_set_non_discoverable_end();
314+
if (pbdrv_bluetooth_advertising_state == PBDRV_BLUETOOTH_ADVERTISING_STATE_BROADCASTING) {
315+
// The GAP layer does not know about advertising that was enabled with
316+
// a link layer command, so it has to be stopped the same way.
317+
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
318+
{
319+
static const uint8_t disable = 0;
320+
hci_send_le_command(OCF_LE_SET_ADVERTISE_ENABLE, &disable, sizeof(disable));
321+
}
322+
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
323+
} else {
324+
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
325+
aci_gap_set_non_discoverable_begin();
326+
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
327+
// aci_gap_set_non_discoverable_end();
328+
}
299329

300330
// This protothread is also shared with stop broadcasting. Either way,
301331
// nothing is advertising or broadcasting after this, so reset that state.
@@ -599,49 +629,72 @@ pbio_error_t pbdrv_bluetooth_start_broadcasting_func(pbio_os_state_t *state, voi
599629

600630
PBIO_OS_ASYNC_BEGIN(state);
601631

632+
// The GAP layer refuses to enter any advertising mode while a computer is
633+
// connected, so the link layer is driven directly instead.
602634
if (pbdrv_bluetooth_advertising_state != PBDRV_BLUETOOTH_ADVERTISING_STATE_BROADCASTING) {
603-
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
604-
// This chip cannot broadcast faster than every 100ms, so other hubs
605-
// receive from it more slowly than they do from each other.
606-
aci_gap_set_non_connectable_begin(ADV_NONCONN_IND, STATIC_RANDOM_ADDR);
607-
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
608-
status = aci_gap_set_non_connectable_end();
609635

610-
if (status != BLE_STATUS_SUCCESS) {
611-
pbio_error_t err = ble_error_to_pbio_error(status);
612-
// Broadcasting does not work while connected to the computer. But
613-
// returning an error means that Move Hub programs with
614-
// broadcasting can never run while connected, which makes it very
615-
// impractical to test any program. So mark as success.
616-
if (err == PBIO_ERROR_INVALID_OP) {
617-
return PBIO_SUCCESS;
618-
}
619-
return err;
636+
// Advertising parameters can only be set while advertising is stopped,
637+
// so leave the GAP discoverable mode if it is still active.
638+
if (pbdrv_bluetooth_advertising_state == PBDRV_BLUETOOTH_ADVERTISING_STATE_ADVERTISING_PYBRICKS) {
639+
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
640+
aci_gap_set_non_discoverable_begin();
641+
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
642+
// aci_gap_set_non_discoverable_end();
620643
}
621644

622-
// These AD types are left over from connectable discovery and need
623-
// to be deleted _after_ starting non-connectable advertising.
624-
625645
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
626-
aci_gap_delete_ad_type_begin(AD_TYPE_128_BIT_SERV_UUID);
646+
{
647+
static const uint8_t params[] = {
648+
// 100ms is the fastest this controller allows. The range has
649+
// to be wide, because the chip fits a new activity to the
650+
// anchor period it is already running for the connection and
651+
// any scanning, and rejects an interval that is not a multiple
652+
// of it.
653+
0xa0, 0x00, // Advertising_Interval_Min, 100ms
654+
0x40, 0x01, // Advertising_Interval_Max, 200ms
655+
ADV_NONCONN_IND,
656+
STATIC_RANDOM_ADDR,
657+
0, // Peer_Address_Type
658+
0, 0, 0, 0, 0, 0, // Peer_Address
659+
0x07, // Advertising_Channel_Map
660+
NO_WHITE_LIST_USE,
661+
};
662+
hci_send_le_command(OCF_LE_SET_ADV_PARAMETERS, params, sizeof(params));
663+
}
627664
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
665+
status = hci_le_command_end();
628666

629-
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
630-
aci_gap_delete_ad_type_begin(AD_TYPE_TX_POWER_LEVEL);
631-
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
667+
if (status != BLE_STATUS_SUCCESS) {
668+
return ble_error_to_pbio_error(status);
669+
}
670+
}
632671

633-
// Errors from deleting are ignored since we should only get an error
634-
// if the AD does not exist, which is OK.
672+
// The link layer advertises exactly this payload.
673+
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
674+
hci_le_set_advertising_data_begin(pbdrv_bluetooth_broadcast_data_size, pbdrv_bluetooth_broadcast_data);
675+
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
676+
status = hci_le_set_advertising_data_end();
635677

636-
pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_BROADCASTING;
678+
if (status != BLE_STATUS_SUCCESS) {
679+
return ble_error_to_pbio_error(status);
637680
}
638681

639-
// This has to be done _after_ other data is delete to make sure it fits.
682+
if (pbdrv_bluetooth_advertising_state == PBDRV_BLUETOOTH_ADVERTISING_STATE_BROADCASTING) {
683+
// Already advertising, so updating the data was all that was needed.
684+
return PBIO_SUCCESS;
685+
}
640686

641687
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
642-
aci_gap_update_adv_data_begin(pbdrv_bluetooth_broadcast_data_size, pbdrv_bluetooth_broadcast_data);
688+
{
689+
static const uint8_t enable = 1;
690+
hci_send_le_command(OCF_LE_SET_ADVERTISE_ENABLE, &enable, sizeof(enable));
691+
}
643692
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
644-
status = aci_gap_update_adv_data_end();
693+
status = hci_le_command_end();
694+
695+
if (status == BLE_STATUS_SUCCESS) {
696+
pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_BROADCASTING;
697+
}
645698

646699
PBIO_OS_ASYNC_END(ble_error_to_pbio_error(status));
647700
}

‎pybricks/messaging/pb_type_ble_radio.c‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,6 @@ static mp_obj_t pb_module_ble_broadcast(size_t n_args, const mp_obj_t *pos_args,
275275
PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
276276
pb_obj_BLE_t, self,
277277
PB_ARG_REQUIRED(data));
278-
// On Move Hub, nothing is broadcast if it is called while the
279-
// move hub is connected to Pybricks Code. Also, broadcasting interferes
280-
// with observing even when not connected to Pybricks Code.
281278

282279
mp_obj_t self_in = MP_OBJ_FROM_PTR(self);
283280

0 commit comments

Comments
 (0)