fix: reconnect Joy-Con mates and balance mixed Bluetooth links
This commit is contained in:
parent
006b573ee8
commit
8dd11c17a6
15 changed files with 780 additions and 75 deletions
|
|
@ -19,11 +19,9 @@ namespace {
|
|||
|
||||
|
||||
bool incoming_connections = false;
|
||||
int scan_starts = 0;
|
||||
int scan_stops = 0;
|
||||
bool scanning_enabled = false;
|
||||
int classic_scan_starts = 0;
|
||||
int classic_scan_stops = 0;
|
||||
bool aggregate_scanning_enabled = false;
|
||||
bool background_scan_parameters = false;
|
||||
bool classic_scanning_enabled = false;
|
||||
uni_platform* installed_platform = nullptr;
|
||||
bool observed_status_led_on = false;
|
||||
|
|
@ -67,6 +65,11 @@ void require_clear_completion_pending();
|
|||
void require_clear_snapshot_published();
|
||||
void request_repeated_clear_during_disconnect();
|
||||
gap_connection_type_t gap_connection_types[256]{};
|
||||
uint16_t negotiated_intervals[256]{};
|
||||
uint16_t pending_intervals[256]{};
|
||||
unsigned interval_requests[256]{};
|
||||
bool defer_interval_updates = false;
|
||||
bool reject_interval_updates = false;
|
||||
uint8_t xbox_left_trigger = 0;
|
||||
uint8_t xbox_right_trigger = 0;
|
||||
unsigned xbox_quad_calls = 0;
|
||||
|
|
@ -127,6 +130,8 @@ uni_hid_device_t device(
|
|||
result.gamepad = gamepad;
|
||||
result.conn.protocol = protocol;
|
||||
result.conn.handle = static_cast<hci_con_handle_t>(0x40 + idx);
|
||||
negotiated_intervals[result.conn.handle] = 6;
|
||||
pending_intervals[result.conn.handle] = 0;
|
||||
result.conn.btaddr[5] = static_cast<uint8_t>(idx + 1);
|
||||
result.vendor_id = static_cast<uint16_t>(0x1000 + idx);
|
||||
result.product_id = static_cast<uint16_t>(0x2000 + idx);
|
||||
|
|
@ -179,6 +184,17 @@ extern "C" bool uni_switch2_pairing_get(
|
|||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool uni_switch2_pairing_known(
|
||||
uint8_t address_type, const uint8_t address[6]) {
|
||||
for (uint8_t index = 0; index < switch2_pairing_count; ++index) {
|
||||
if (switch2_pairing_types[index] == address_type &&
|
||||
memcmp(switch2_pairings[index], address, sizeof(bd_addr_t)) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" bool uni_switch2_pairing_clear(void) {
|
||||
if (!switch2_clear_succeeds) {
|
||||
return false;
|
||||
|
|
@ -268,6 +284,15 @@ uni_hid_device_t* uni_hid_device_get_instance_for_connection_handle(
|
|||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uni_hid_device_t* uni_hid_device_get_instance_for_address(const bd_addr_t address) {
|
||||
for (size_t index = 0; index < lookup_device_count; ++index) {
|
||||
if (memcmp(lookup_devices[index]->conn.btaddr, address, sizeof(bd_addr_t)) == 0) {
|
||||
return lookup_devices[index];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
void uni_hid_device_disconnect(uni_hid_device_t* device) {
|
||||
require_clear_completion_pending();
|
||||
request_repeated_clear_during_disconnect();
|
||||
|
|
@ -281,37 +306,42 @@ void uni_bt_allow_incoming_connections(bool enabled) {
|
|||
|
||||
|
||||
void uni_bt_bredr_scan_start() {
|
||||
++classic_scan_starts;
|
||||
classic_scanning_enabled = true;
|
||||
}
|
||||
|
||||
void uni_bt_bredr_scan_stop() {
|
||||
if (classic_scanning_enabled) {
|
||||
++classic_scan_stops;
|
||||
}
|
||||
classic_scanning_enabled = false;
|
||||
}
|
||||
|
||||
void uni_bt_le_scan_start() {
|
||||
++scan_starts;
|
||||
scanning_enabled = true;
|
||||
}
|
||||
|
||||
void uni_bt_le_scan_stop() {
|
||||
if (scanning_enabled) {
|
||||
++scan_stops;
|
||||
}
|
||||
scanning_enabled = false;
|
||||
}
|
||||
|
||||
void uni_bt_le_set_background_scan(bool enabled) {
|
||||
require(!scanning_enabled, "LE scan timing must change only while scanning is stopped");
|
||||
background_scan_parameters = enabled;
|
||||
}
|
||||
|
||||
void uni_bt_start_scanning_and_autoconnect_unsafe() {
|
||||
require_clear_completion_pending();
|
||||
require_clear_snapshot_published();
|
||||
if (aggregate_scanning_enabled) {
|
||||
return;
|
||||
}
|
||||
aggregate_scanning_enabled = true;
|
||||
uni_bt_bredr_scan_start();
|
||||
uni_bt_le_scan_start();
|
||||
}
|
||||
|
||||
void uni_bt_stop_scanning_unsafe() {
|
||||
if (!aggregate_scanning_enabled) {
|
||||
return;
|
||||
}
|
||||
aggregate_scanning_enabled = false;
|
||||
uni_bt_bredr_scan_stop();
|
||||
uni_bt_le_scan_stop();
|
||||
}
|
||||
|
|
@ -331,6 +361,25 @@ gap_connection_type_t gap_get_connection_type(
|
|||
: GAP_CONNECTION_INVALID;
|
||||
}
|
||||
|
||||
uint16_t gap_le_connection_interval(hci_con_handle_t handle) {
|
||||
require(handle < 256, "interval read must target a valid fake connection");
|
||||
return negotiated_intervals[handle];
|
||||
}
|
||||
|
||||
int gap_update_connection_parameters(hci_con_handle_t handle, uint16_t minimum,
|
||||
uint16_t maximum, uint16_t latency,
|
||||
uint16_t supervision_timeout) {
|
||||
require(handle < 256 && gap_get_connection_type(handle) == GAP_CONNECTION_LE,
|
||||
"interval policy must not update a Classic or invalid link");
|
||||
require(minimum == maximum && latency == 0 && supervision_timeout >= 100,
|
||||
"interval request must retain valid supervision and peripheral latency");
|
||||
++interval_requests[handle];
|
||||
if (reject_interval_updates) return 0x0c;
|
||||
if (defer_interval_updates) pending_intervals[handle] = minimum;
|
||||
else negotiated_intervals[handle] = minimum;
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
int gap_link_key_iterator_init(btstack_link_key_iterator_t* iterator) {
|
||||
iterator->index = 0;
|
||||
return 1;
|
||||
|
|
@ -749,7 +798,7 @@ void start_backend() {
|
|||
bluepad32_input_backend_init();
|
||||
platform_on_init_complete();
|
||||
require(incoming_connections && scanning_enabled &&
|
||||
classic_scanning_enabled && scan_starts == 1 &&
|
||||
classic_scanning_enabled &&
|
||||
link_supervision_timeout ==
|
||||
kClassicLinkSupervisionTimeout &&
|
||||
!bondable && accepted_stk_methods == 0 &&
|
||||
|
|
@ -905,6 +954,14 @@ void ready_switch2(uni_hid_device_t& controller) {
|
|||
"Switch2 physical device must become ready");
|
||||
}
|
||||
|
||||
void remember_switch2(const uni_hid_device_t& controller) {
|
||||
require(switch2_pairing_count < UNI_SWITCH2_PAIRING_CAPACITY,
|
||||
"test proprietary trust inventory overflow");
|
||||
switch2_pairing_types[switch2_pairing_count] = controller.switch2_identity_address_type;
|
||||
memcpy(switch2_pairings[switch2_pairing_count], controller.conn.btaddr, sizeof(bd_addr_t));
|
||||
++switch2_pairing_count;
|
||||
}
|
||||
|
||||
Bluepad32SlotSnapshot slot_snapshot(uint8_t index) {
|
||||
Bluepad32SlotSnapshot result{};
|
||||
bluepad32_input_backend_snapshot(index, &result);
|
||||
|
|
@ -1534,6 +1591,256 @@ void test_switch2_admission() {
|
|||
"Pro2 must preserve vertical normal controls and ingest remappable extras");
|
||||
}
|
||||
|
||||
void test_switch2_radio_policy() {
|
||||
start_backend();
|
||||
auto left = switch2_device(0, UNI_SW2_JOYCON_L_PID);
|
||||
auto right = switch2_device(1, UNI_SW2_JOYCON_R_PID);
|
||||
auto classic = device(2, true, UNI_BT_CONN_PROTOCOL_BR_EDR);
|
||||
auto other_ble = device(3, true, UNI_BT_CONN_PROTOCOL_BLE);
|
||||
ready_switch2(left);
|
||||
ready_switch2(right);
|
||||
platform_on_device_connected(&other_ble);
|
||||
require(platform_on_device_ready(&other_ble) == UNI_ERROR_SUCCESS,
|
||||
"unrelated BLE controller must join");
|
||||
require(negotiated_intervals[left.conn.handle] == 6 &&
|
||||
negotiated_intervals[right.conn.handle] == 6,
|
||||
"a pair without Classic must retain fast intervals");
|
||||
platform_on_device_connected(&classic);
|
||||
require(negotiated_intervals[left.conn.handle] == 24 &&
|
||||
negotiated_intervals[right.conn.handle] == 24 &&
|
||||
negotiated_intervals[other_ble.conn.handle] == 6,
|
||||
"Classic setup must relax both physical halves before native attachment, not unrelated BLE");
|
||||
require(platform_on_device_ready(&classic) == UNI_ERROR_SUCCESS,
|
||||
"Classic controller must complete setup alongside the pair");
|
||||
const auto pair = slot_snapshot(0);
|
||||
platform_on_device_disconnected(&classic);
|
||||
require(negotiated_intervals[left.conn.handle] == 6 &&
|
||||
negotiated_intervals[right.conn.handle] == 6 &&
|
||||
slot_snapshot(0).connection_generation == pair.connection_generation,
|
||||
"Classic departure must restore fast intervals without rebinding the pair");
|
||||
platform_on_device_connected(&classic);
|
||||
require(platform_on_device_ready(&classic) == UNI_ERROR_SUCCESS,
|
||||
"Classic reconnect must complete");
|
||||
platform_on_device_disconnected(&left);
|
||||
require(negotiated_intervals[right.conn.handle] == 6,
|
||||
"a single surviving Switch2 link must return to fast scheduling even with Classic");
|
||||
left = switch2_device(0, UNI_SW2_JOYCON_L_PID);
|
||||
ready_switch2(left);
|
||||
require(negotiated_intervals[left.conn.handle] == 24 &&
|
||||
negotiated_intervals[right.conn.handle] == 24,
|
||||
"physical index and handle reuse must negotiate mixed intervals on the new connection");
|
||||
}
|
||||
|
||||
void test_switch2_radio_settling() {
|
||||
start_backend();
|
||||
auto classic = device(2, true, UNI_BT_CONN_PROTOCOL_BR_EDR);
|
||||
auto left = switch2_device(0, UNI_SW2_JOYCON_L_PID);
|
||||
auto right = switch2_device(1, UNI_SW2_JOYCON_R_PID);
|
||||
platform_on_device_connected(&classic);
|
||||
require(platform_on_device_ready(&classic) == UNI_ERROR_SUCCESS,
|
||||
"Classic-first connection must become ready");
|
||||
ready_switch2(left);
|
||||
defer_interval_updates = true;
|
||||
now_ms = UINT32_MAX - 10;
|
||||
platform_on_device_connected(&right);
|
||||
require(interval_requests[right.conn.handle] == 0,
|
||||
"pending Switch2 setup must retain ownership of its initial interval");
|
||||
require(platform_on_device_ready(&right) == UNI_ERROR_SUCCESS,
|
||||
"second Switch2 connection must become ready");
|
||||
platform_on_device_disconnected(&classic);
|
||||
// The controller completes the old update after the topology reversed.
|
||||
for (auto* half : {&left, &right}) {
|
||||
negotiated_intervals[half->conn.handle] = pending_intervals[half->conn.handle];
|
||||
}
|
||||
now_ms += 50;
|
||||
process_configuration_timer(&g_configuration_timer);
|
||||
for (auto* half : {&left, &right}) {
|
||||
negotiated_intervals[half->conn.handle] = pending_intervals[half->conn.handle];
|
||||
require(negotiated_intervals[half->conn.handle] == 6,
|
||||
"late mixed-mode completion must not strand a pair at slow intervals across clock wrap");
|
||||
}
|
||||
process_configuration_timer(&g_configuration_timer);
|
||||
defer_interval_updates = false;
|
||||
reject_interval_updates = true;
|
||||
platform_on_device_connected(&classic);
|
||||
const unsigned attempts = interval_requests[left.conn.handle];
|
||||
now_ms += 999;
|
||||
process_configuration_timer(&g_configuration_timer);
|
||||
require(interval_requests[left.conn.handle] == attempts,
|
||||
"rejected negotiation must not flood the HCI command queue");
|
||||
reject_interval_updates = false;
|
||||
++now_ms;
|
||||
process_configuration_timer(&g_configuration_timer);
|
||||
require(negotiated_intervals[left.conn.handle] == 24 &&
|
||||
negotiated_intervals[right.conn.handle] == 24,
|
||||
"transiently rejected negotiation must recover without reconnect or pairing");
|
||||
}
|
||||
|
||||
void test_switch2_mate_reconnect() {
|
||||
start_backend();
|
||||
auto right = switch2_device(0, UNI_SW2_JOYCON_R_PID);
|
||||
auto left = switch2_device(1, UNI_SW2_JOYCON_L_PID);
|
||||
left.conn.btaddr[0] = 0xc1;
|
||||
left.switch2_identity_address_type = BD_ADDR_TYPE_LE_RANDOM;
|
||||
remember_switch2(right);
|
||||
remember_switch2(left);
|
||||
register_lookup_device(&right);
|
||||
register_lookup_device(&left);
|
||||
ready_switch2(right);
|
||||
require(scanning_enabled && background_scan_parameters &&
|
||||
!classic_scanning_enabled && incoming_connections &&
|
||||
!bondable && accepted_stk_methods == 0 &&
|
||||
!switch_pico_switch2_pairing_allowed(),
|
||||
"first remembered half must seek its mate with low-duty BLE and authentication closed");
|
||||
dispatch_pairing_event(HCI_EVENT_USER_CONFIRMATION_REQUEST);
|
||||
dispatch_pairing_event(HCI_EVENT_USER_PASSKEY_REQUEST);
|
||||
require(confirmation_accepts == 0 && passkey_accepts == 0 &&
|
||||
confirmation_rejections == 1 && passkey_rejections == 1,
|
||||
"mate scanning must not authorize new Classic authentication");
|
||||
|
||||
bd_addr_t missing = {1, 2, 3, 4, 5, 6};
|
||||
require(platform_on_device_discovered(missing, "Joy-Con 2 (L)", 0, 0) ==
|
||||
UNI_ERROR_IGNORE_DEVICE,
|
||||
"a device name without a prepared parser candidate cannot authorize passive discovery");
|
||||
const auto reject_left = [&]() {
|
||||
require(platform_on_device_discovered(left.conn.btaddr, "Joy-Con 2 (L)", 0, 0) ==
|
||||
UNI_ERROR_IGNORE_DEVICE &&
|
||||
scanning_enabled && !classic_scanning_enabled &&
|
||||
!switch_pico_switch2_pairing_allowed(),
|
||||
"rejected mate candidates must leave the bounded passive policy unchanged");
|
||||
};
|
||||
++left.conn.btaddr[5];
|
||||
reject_left();
|
||||
--left.conn.btaddr[5];
|
||||
left.product_id = UNI_SW2_JOYCON_R_PID;
|
||||
reject_left();
|
||||
left.product_id = UNI_SW2_PRO_PID;
|
||||
reject_left();
|
||||
left.product_id = UNI_SW2_JOYCON_L_PID;
|
||||
left.conn.protocol = UNI_BT_CONN_PROTOCOL_BR_EDR;
|
||||
reject_left();
|
||||
left.conn.protocol = UNI_BT_CONN_PROTOCOL_BLE;
|
||||
++left.vendor_id;
|
||||
reject_left();
|
||||
--left.vendor_id;
|
||||
left.switch2_identity_valid = false;
|
||||
reject_left();
|
||||
left.switch2_identity_valid = true;
|
||||
left.switch2_identity_address_type = BD_ADDR_TYPE_LE_PUBLIC;
|
||||
reject_left();
|
||||
left.switch2_identity_address_type = BD_ADDR_TYPE_LE_RANDOM;
|
||||
require(platform_on_device_discovered(left.conn.btaddr, nullptr, 0, 0) ==
|
||||
UNI_ERROR_SUCCESS,
|
||||
"remembered opposite static-random half must be admitted outside the pairing window");
|
||||
|
||||
// The parser stops GAP scan before it has a backend slot to reserve.
|
||||
uni_bt_le_scan_stop();
|
||||
platform_on_device_connected(&left);
|
||||
require(!scanning_enabled && !classic_scanning_enabled,
|
||||
"remembered mate setup must pause scan even when GAP already stopped it");
|
||||
platform_on_device_disconnected(&left);
|
||||
require(scanning_enabled && background_scan_parameters && !classic_scanning_enabled,
|
||||
"failed setup must resume remembered mate scanning");
|
||||
uni_bt_le_scan_stop();
|
||||
platform_on_device_connected(&left);
|
||||
require(!scanning_enabled && !classic_scanning_enabled && incoming_connections,
|
||||
"mate setup must pause background scanning while reserving physical capacity");
|
||||
require(platform_on_device_ready(&left) == UNI_ERROR_SUCCESS &&
|
||||
slot_snapshot(0).active && !slot_snapshot(1).active &&
|
||||
controller_identity_equal(slot_snapshot(0).identity, identity_for_device(&left)) &&
|
||||
!scanning_enabled && !classic_scanning_enabled,
|
||||
"ready remembered mate must merge without a pairing window and stop background scanning");
|
||||
auto ordinary = device(2, true, UNI_BT_CONN_PROTOCOL_BR_EDR);
|
||||
platform_on_device_connected(&ordinary);
|
||||
require(platform_on_device_ready(&ordinary) == UNI_ERROR_SUCCESS &&
|
||||
!scanning_enabled && !classic_scanning_enabled,
|
||||
"a complete pair plus incoming Classic controller must not keep LE scanning");
|
||||
platform_on_device_disconnected(&left);
|
||||
require(scanning_enabled && background_scan_parameters &&
|
||||
!classic_scanning_enabled && slot_snapshot(2).active,
|
||||
"losing a half must resume its mate scan without disturbing an ordinary controller");
|
||||
}
|
||||
|
||||
void test_switch2_mate_pending() {
|
||||
start_backend();
|
||||
auto left = switch2_device(0, UNI_SW2_JOYCON_L_PID);
|
||||
auto right = switch2_device(3, UNI_SW2_JOYCON_R_PID);
|
||||
remember_switch2(left);
|
||||
remember_switch2(right);
|
||||
register_lookup_device(&right);
|
||||
ready_switch2(left);
|
||||
auto first = device(1, true, UNI_BT_CONN_PROTOCOL_BR_EDR);
|
||||
auto second = device(2, true, UNI_BT_CONN_PROTOCOL_BR_EDR);
|
||||
auto third = device(3, true, UNI_BT_CONN_PROTOCOL_BR_EDR);
|
||||
platform_on_device_connected(&first);
|
||||
platform_on_device_connected(&second);
|
||||
require(!scanning_enabled && !classic_scanning_enabled && incoming_connections &&
|
||||
platform_on_device_discovered(right.conn.btaddr, nullptr, 0, 0) ==
|
||||
UNI_ERROR_IGNORE_DEVICE,
|
||||
"any pending setup must pause scanning and reject otherwise valid mates");
|
||||
require(platform_on_device_ready(&first) == UNI_ERROR_SUCCESS &&
|
||||
!scanning_enabled,
|
||||
"one resolved setup must not resume mate scanning while another is pending");
|
||||
platform_on_device_disconnected(&second);
|
||||
require(scanning_enabled && background_scan_parameters &&
|
||||
!classic_scanning_enabled &&
|
||||
platform_on_device_discovered(right.conn.btaddr, nullptr, 0, 0) ==
|
||||
UNI_ERROR_SUCCESS,
|
||||
"last pending setup failure must resume passive mate discovery and admission");
|
||||
platform_on_device_connected(&second);
|
||||
require(!scanning_enabled &&
|
||||
platform_on_device_ready(&second) == UNI_ERROR_SUCCESS &&
|
||||
scanning_enabled && !classic_scanning_enabled,
|
||||
"last pending setup becoming ready must resume the unmatched half scan");
|
||||
platform_on_device_connected(&third);
|
||||
require(platform_on_device_ready(&third) == UNI_ERROR_SUCCESS &&
|
||||
!scanning_enabled && !classic_scanning_enabled && !incoming_connections &&
|
||||
platform_on_device_discovered(right.conn.btaddr, nullptr, 0, 0) ==
|
||||
UNI_ERROR_IGNORE_DEVICE,
|
||||
"four physical controllers must stop mate scanning even with an unmatched half");
|
||||
platform_on_device_disconnected(&third);
|
||||
require(scanning_enabled && background_scan_parameters && incoming_connections,
|
||||
"free physical capacity must restore the waiting half scan");
|
||||
|
||||
switch2_clear_succeeds = false;
|
||||
bluepad32_input_backend_clear_pairings();
|
||||
process_rumble_timer(&g_rumble_timer);
|
||||
require(!scanning_enabled && !classic_scanning_enabled && !incoming_connections,
|
||||
"failed trust deletion must explicitly stop a direct background LE scan");
|
||||
platform_on_device_disconnected(&right);
|
||||
platform_on_device_disconnected(&left);
|
||||
bluepad32_input_backend_open_pairing_window();
|
||||
process_rumble_timer(&g_rumble_timer);
|
||||
require(!scanning_enabled && !classic_scanning_enabled && !incoming_connections &&
|
||||
!switch_pico_switch2_pairing_allowed() &&
|
||||
platform_on_device_discovered(right.conn.btaddr, nullptr, 0, 0) ==
|
||||
UNI_ERROR_IGNORE_DEVICE,
|
||||
"late disconnects and pairing requests must not clear the failed-closed latch");
|
||||
}
|
||||
|
||||
void test_switch2_mate_pairing_window() {
|
||||
start_backend();
|
||||
auto left = switch2_device(0, UNI_SW2_JOYCON_L_PID);
|
||||
ready_switch2(left);
|
||||
require(scanning_enabled && background_scan_parameters && !classic_scanning_enabled,
|
||||
"solo half must enter background discovery before opening pairing");
|
||||
bluepad32_input_backend_open_pairing_window();
|
||||
process_rumble_timer(&g_rumble_timer);
|
||||
require(scanning_enabled && !background_scan_parameters && classic_scanning_enabled &&
|
||||
bondable && accepted_stk_methods == kAllBlePairingMethods,
|
||||
"explicit pairing must restore full discovery timing and the existing authentication window");
|
||||
now_ms = g_pairing_window_deadline_ms;
|
||||
process_rumble_timer(&g_rumble_timer);
|
||||
require(scanning_enabled && background_scan_parameters && !classic_scanning_enabled &&
|
||||
!bondable && accepted_stk_methods == 0 &&
|
||||
!switch_pico_switch2_pairing_allowed(),
|
||||
"pairing expiry must restore low-duty mate discovery without extending authentication");
|
||||
platform_on_device_disconnected(&left);
|
||||
require(scanning_enabled && !background_scan_parameters && classic_scanning_enabled &&
|
||||
!bondable && accepted_stk_methods == 0,
|
||||
"last controller loss must restore idle discovery defaults without opening pairing");
|
||||
}
|
||||
|
||||
void test_switch2_pairing_inventory() {
|
||||
start_pairing_backend();
|
||||
switch2_pairing_count = 2;
|
||||
|
|
@ -1630,23 +1937,21 @@ void test_ready_order(bool reverse) {
|
|||
}
|
||||
|
||||
if (position + 1 < kSlotCount) {
|
||||
require(scan_stops == 0,
|
||||
"scanning must continue while any slot remains free");
|
||||
require(scanning_enabled && classic_scanning_enabled,
|
||||
"pairing-window discovery must continue while a physical slot remains free");
|
||||
require(incoming_connections,
|
||||
"incoming connections must remain enabled before all slots are ready");
|
||||
}
|
||||
}
|
||||
|
||||
require(scan_stops == 1,
|
||||
"scanning must stop exactly when all four slots are ready");
|
||||
require(!scanning_enabled && !classic_scanning_enabled,
|
||||
"discovery must stop when all four physical slots are full");
|
||||
require(!incoming_connections,
|
||||
"incoming connections must be disabled only when all slots are full");
|
||||
|
||||
for (int slot = 0; slot < kSlotCount; ++slot) {
|
||||
const int starts_before_disconnect = scan_starts;
|
||||
platform_on_device_disconnected(&devices[slot]);
|
||||
require(scan_starts == starts_before_disconnect + 1 &&
|
||||
scanning_enabled && incoming_connections,
|
||||
require(scanning_enabled && classic_scanning_enabled && incoming_connections,
|
||||
"disconnecting any slot must resume connection policy");
|
||||
|
||||
for (int candidate = 0; candidate < kSlotCount; ++candidate) {
|
||||
|
|
@ -1768,12 +2073,6 @@ void test_independent_lifecycle() {
|
|||
require(g_slots[0].device == &aborted && !g_slots[0].active,
|
||||
"connected device must remain identifiable while becoming ready");
|
||||
|
||||
const int starts_before_aborted_disconnect = scan_starts;
|
||||
const int classic_starts_before_aborted_disconnect =
|
||||
classic_scan_starts;
|
||||
const int stops_before_aborted_disconnect = scan_stops;
|
||||
const int classic_stops_before_aborted_disconnect =
|
||||
classic_scan_stops;
|
||||
platform_on_device_disconnected(&aborted);
|
||||
require(g_slots[0].device == nullptr && !g_slots[0].active,
|
||||
"pre-ready disconnect must clear its pending slot identity");
|
||||
|
|
@ -1781,13 +2080,7 @@ void test_independent_lifecycle() {
|
|||
"pre-ready disconnect must invalidate its connection generation");
|
||||
require(g_connection_status == ConnectionStatus::Scanning &&
|
||||
scanning_enabled && classic_scanning_enabled &&
|
||||
incoming_connections &&
|
||||
scan_starts == starts_before_aborted_disconnect + 1 &&
|
||||
classic_scan_starts ==
|
||||
classic_starts_before_aborted_disconnect + 1 &&
|
||||
scan_stops == stops_before_aborted_disconnect + 1 &&
|
||||
classic_scan_stops ==
|
||||
classic_stops_before_aborted_disconnect + 1,
|
||||
incoming_connections,
|
||||
"pre-ready disconnect must restart Classic and BLE scans");
|
||||
|
||||
uni_hid_device_t devices[kSlotCount] = {
|
||||
|
|
@ -1850,7 +2143,6 @@ void test_independent_lifecycle() {
|
|||
|
||||
const uint32_t first_pending_generation =
|
||||
g_slots[0].connection_generation;
|
||||
const int starts_before_first_pending_disconnect = scan_starts;
|
||||
platform_on_device_disconnected(&devices[0]);
|
||||
require(g_slots[0].device == nullptr && !g_slots[0].active,
|
||||
"pre-ready disconnect must clear only its own pending identity");
|
||||
|
|
@ -1864,8 +2156,7 @@ void test_independent_lifecycle() {
|
|||
"pending disconnect beside peers must invalidate its generation");
|
||||
require(g_connection_status == ConnectionStatus::Connecting &&
|
||||
scanning_enabled && classic_scanning_enabled &&
|
||||
incoming_connections &&
|
||||
scan_starts == starts_before_first_pending_disconnect + 1,
|
||||
incoming_connections,
|
||||
"open pairing slot must preserve pending peers and resume scanning");
|
||||
|
||||
for (int slot = 1; slot < kSlotCount; ++slot) {
|
||||
|
|
@ -2025,10 +2316,8 @@ void test_independent_lifecycle() {
|
|||
bluepad32_input_backend_queue_rumble(3, ControllerRumbleOutput{55, 66});
|
||||
const uint32_t disconnected_generation =
|
||||
baseline_connection_generations[3];
|
||||
const int starts_before_slot_three_disconnect = scan_starts;
|
||||
platform_on_device_disconnected(&devices[3]);
|
||||
require(scan_starts == starts_before_slot_three_disconnect + 1 &&
|
||||
scanning_enabled && incoming_connections,
|
||||
require(scanning_enabled && classic_scanning_enabled && incoming_connections,
|
||||
"slot 3 disconnect must resume scanning and incoming connections");
|
||||
require(!read_controller_state(3, &states[3]) &&
|
||||
!states[3].button_north && states[3].left_stick_x == 0,
|
||||
|
|
@ -2162,10 +2451,8 @@ void test_independent_lifecycle() {
|
|||
uni_hid_device_t replacements[kSlotCount] = {
|
||||
device(0), device(1), device(2), device(3)};
|
||||
for (int slot = 0; slot < 3; ++slot) {
|
||||
const int starts_before_disconnect = scan_starts;
|
||||
platform_on_device_disconnected(&devices[slot]);
|
||||
require(scan_starts == starts_before_disconnect + 1 &&
|
||||
scanning_enabled && incoming_connections,
|
||||
require(scanning_enabled && classic_scanning_enabled && incoming_connections,
|
||||
"disconnecting slots 0-2 must resume connection policy");
|
||||
require(!read_controller_state(slot, &states[slot]) &&
|
||||
states[slot].left_stick_x == 0,
|
||||
|
|
@ -3369,14 +3656,14 @@ void test_wake_identity_gates_connections() {
|
|||
require(switch2_wake_initializations == 1 &&
|
||||
g_connection_policy_state ==
|
||||
ConnectionPolicyState::Uninitialized &&
|
||||
scan_starts == 0 && classic_scan_starts == 0 &&
|
||||
!scanning_enabled && !classic_scanning_enabled &&
|
||||
!incoming_connections,
|
||||
"controller discovery started before wake identity was ready");
|
||||
|
||||
switch2_connections_ready = true;
|
||||
process_rumble_timer(&g_rumble_timer);
|
||||
require(g_connection_policy_state == ConnectionPolicyState::Open &&
|
||||
scan_starts == 1 && classic_scan_starts == 1 &&
|
||||
scanning_enabled && classic_scanning_enabled &&
|
||||
incoming_connections,
|
||||
"controller discovery did not start after wake identity setup");
|
||||
}
|
||||
|
|
@ -3677,6 +3964,16 @@ int main(int argc, char** argv) {
|
|||
test_switch2_multiple_pairs();
|
||||
} else if (scenario == "switch2-admission") {
|
||||
test_switch2_admission();
|
||||
} else if (scenario == "switch2-radio-policy") {
|
||||
test_switch2_radio_policy();
|
||||
} else if (scenario == "switch2-radio-settling") {
|
||||
test_switch2_radio_settling();
|
||||
} else if (scenario == "switch2-mate-reconnect") {
|
||||
test_switch2_mate_reconnect();
|
||||
} else if (scenario == "switch2-mate-pending") {
|
||||
test_switch2_mate_pending();
|
||||
} else if (scenario == "switch2-mate-pairing-window") {
|
||||
test_switch2_mate_pairing_window();
|
||||
} else if (scenario == "switch2-pairing-inventory") {
|
||||
test_switch2_pairing_inventory();
|
||||
} else if (scenario == "ready-forward") {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ struct btstack_link_key_iterator_t {
|
|||
|
||||
enum {
|
||||
ERROR_CODE_SUCCESS = 0,
|
||||
HCI_CON_HANDLE_INVALID = 0xffff,
|
||||
HCI_EVENT_PACKET = 4,
|
||||
HCI_EVENT_USER_CONFIRMATION_REQUEST = 0x33,
|
||||
HCI_EVENT_USER_PASSKEY_REQUEST = 0x34,
|
||||
|
|
@ -198,6 +199,7 @@ int uni_hid_device_get_idx_for_instance(const uni_hid_device_t* device);
|
|||
void uni_hid_device_disconnect(uni_hid_device_t* device);
|
||||
uni_hid_device_t* uni_hid_device_get_instance_for_connection_handle(
|
||||
hci_con_handle_t handle);
|
||||
uni_hid_device_t* uni_hid_device_get_instance_for_address(const bd_addr_t address);
|
||||
void uni_bt_allow_incoming_connections(bool enabled);
|
||||
void uni_bt_start_scanning_and_autoconnect_unsafe();
|
||||
void uni_bt_stop_scanning_unsafe();
|
||||
|
|
@ -205,9 +207,14 @@ void uni_bt_bredr_scan_start();
|
|||
void uni_bt_bredr_scan_stop();
|
||||
void uni_bt_le_scan_start();
|
||||
void uni_bt_le_scan_stop();
|
||||
void uni_bt_le_set_background_scan(bool enabled);
|
||||
void uni_bt_del_keys_unsafe();
|
||||
gap_connection_type_t gap_get_connection_type(
|
||||
hci_con_handle_t connection_handle);
|
||||
uint16_t gap_le_connection_interval(hci_con_handle_t handle);
|
||||
int gap_update_connection_parameters(hci_con_handle_t handle, uint16_t minimum,
|
||||
uint16_t maximum, uint16_t latency,
|
||||
uint16_t supervision_timeout);
|
||||
int gap_link_key_iterator_init(btstack_link_key_iterator_t* iterator);
|
||||
int gap_link_key_iterator_get_next(
|
||||
btstack_link_key_iterator_t* iterator, bd_addr_t address,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
#include "protocol_fixture.h"
|
||||
#include "parser/uni_hid_parser_switch2.h"
|
||||
#include "parser/uni_switch2_pairing.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define PEERS 4
|
||||
#define PEERS CONFIG_BLUEPAD32_MAX_DEVICES
|
||||
#define SERVICE_START 0x100
|
||||
#define INPUT_HANDLE 0x104
|
||||
#define RESPONSE_HANDLE 0x114
|
||||
|
|
@ -17,9 +18,14 @@ static struct fixture_peer peers[PEERS];
|
|||
static btstack_timer_source_t* timers[16];
|
||||
static unsigned timer_count, connected, ready, disconnected, emitted, remembered, listeners;
|
||||
static unsigned connected_events, disconnected_events;
|
||||
static unsigned scan_stops, discovery_calls;
|
||||
static uint16_t expected_discovery_pid;
|
||||
static uint8_t expected_discovery_address_type;
|
||||
static uint32_t now_ms;
|
||||
static bool pairing_allowed, trusted, storage_ok, request_writes, admit, reject_connected;
|
||||
static uint8_t next_write_error;
|
||||
static uint8_t next_connect_error;
|
||||
static bool scan_enabled, scan_running;
|
||||
static const bd_addr_t host_address = {0x10, 0x21, 0x32, 0x43, 0x54, 0x65};
|
||||
static const bd_addr_t controller_address = {0xc0, 0x22, 0x33, 0x44, 0x55, 0x66};
|
||||
static const uint8_t service_uuid[16] = {0xab,0x7d,0xe9,0xbe,0x89,0xfe,0x49,0xad,0x82,0x8f,0x11,0x8f,0x09,0xdf,0x7f,0xd0};
|
||||
|
|
@ -87,8 +93,14 @@ bool uni_switch2_pairing_remember(uint8_t type, const uint8_t address[6]) {
|
|||
return storage_ok;
|
||||
}
|
||||
void gap_local_bd_addr(bd_addr_t address) { memcpy(address, host_address, 6); }
|
||||
void gap_stop_scan(void) {}
|
||||
uint8_t gap_connect(const bd_addr_t address, bd_addr_type_t type) { (void)address; (void)type; ++connected; return 0; }
|
||||
void gap_stop_scan(void) { ++scan_stops; scan_running = false; }
|
||||
void uni_bt_le_resume_scanning_if_enabled(void) { if (scan_enabled) scan_running = true; }
|
||||
uint8_t gap_connect(const bd_addr_t address, bd_addr_type_t type) {
|
||||
(void)address; (void)type; ++connected;
|
||||
uint8_t status = next_connect_error;
|
||||
next_connect_error = 0;
|
||||
return status;
|
||||
}
|
||||
int gap_update_connection_parameters(hci_con_handle_t handle, uint16_t min, uint16_t max, uint16_t latency, uint16_t timeout) {
|
||||
(void)handle; (void)min; (void)max; (void)latency; (void)timeout; return 0;
|
||||
}
|
||||
|
|
@ -121,7 +133,17 @@ uni_hid_device_t* uni_hid_device_get_instance_for_connection_handle(hci_con_hand
|
|||
return peer ? &peer->device : NULL;
|
||||
}
|
||||
uni_error_t uni_hid_device_on_device_discovered(bd_addr_t address, const char* name, uint16_t cod, uint8_t rssi) {
|
||||
(void)address; (void)name; (void)cod; (void)rssi;
|
||||
++discovery_calls;
|
||||
uni_hid_device_t* d = uni_hid_device_get_instance_for_address(address);
|
||||
uint8_t type = 0xff;
|
||||
assert(d && uni_hid_parser_switch2_is_ble_device(d));
|
||||
assert(uni_hid_parser_switch2_identity_address_type(d, &type));
|
||||
(void)name;
|
||||
assert(d->cod == cod && d->conn.rssi == rssi);
|
||||
if (expected_discovery_pid) {
|
||||
assert(d->product_id == expected_discovery_pid);
|
||||
assert(type == expected_discovery_address_type);
|
||||
}
|
||||
return admit ? UNI_ERROR_SUCCESS : (uni_error_t)1;
|
||||
}
|
||||
void uni_hid_device_set_vendor_id(uni_hid_device_t* d, uint16_t value) { d->vendor_id = value; }
|
||||
|
|
@ -419,8 +441,13 @@ static void reset(void) {
|
|||
memset(peers, 0, sizeof(peers));
|
||||
connected = ready = disconnected = emitted = remembered = 0;
|
||||
connected_events = disconnected_events = 0;
|
||||
scan_stops = discovery_calls = 0;
|
||||
expected_discovery_pid = 0;
|
||||
expected_discovery_address_type = BD_ADDR_TYPE_LE_PUBLIC;
|
||||
now_ms = 0;
|
||||
next_write_error = 0;
|
||||
next_connect_error = 0;
|
||||
scan_enabled = scan_running = true;
|
||||
pairing_allowed = trusted = storage_ok = admit = true;
|
||||
request_writes = false;
|
||||
reject_connected = false;
|
||||
|
|
@ -457,6 +484,7 @@ static void test_advertisement_bounds_and_admission(void) {
|
|||
packet[3] = BD_ADDR_TYPE_LE_RANDOM;
|
||||
packet[9] = 0x40; // Resolving private address, not static identity.
|
||||
assert(uni_bt_le_switch2_handle_advertisement(packet, size) && connected == 0);
|
||||
assert(discovery_calls == 0 && scan_stops == 0);
|
||||
advertisement(packet, UNI_SW2_PRO_PID, false);
|
||||
admit = false;
|
||||
assert(uni_bt_le_switch2_handle_advertisement(packet, size) && connected == 0);
|
||||
|
|
@ -466,6 +494,62 @@ static void test_advertisement_bounds_and_admission(void) {
|
|||
assert(uni_hid_parser_switch2_identity_address_type(&peers[0].device, &type) && type == BD_ADDR_TYPE_LE_PUBLIC);
|
||||
}
|
||||
|
||||
static void test_discovery_metadata_and_rejection_isolation(void) {
|
||||
reset();
|
||||
expected_discovery_pid = UNI_SW2_JOYCON_L_PID;
|
||||
struct fixture_peer* mate = connect_peer(UNI_SW2_JOYCON_L_PID, false);
|
||||
discover(mate);
|
||||
subscribe_response(mate);
|
||||
finish_setup(mate);
|
||||
assert(ready == 1 && discovery_calls == 1);
|
||||
const unsigned mate_timers = timer_count;
|
||||
const unsigned mate_listeners = listeners;
|
||||
|
||||
uint8_t packet[64];
|
||||
size_t size = advertisement(packet, UNI_SW2_JOYCON_R_PID, false);
|
||||
packet[3] = BD_ADDR_TYPE_LE_RANDOM;
|
||||
packet[4] ^= 1; // A different static identity from the active mate.
|
||||
bd_addr_t candidate_address;
|
||||
gap_event_advertising_report_get_address(packet, candidate_address);
|
||||
expected_discovery_pid = UNI_SW2_JOYCON_R_PID;
|
||||
expected_discovery_address_type = BD_ADDR_TYPE_LE_RANDOM;
|
||||
admit = false;
|
||||
// More rejections than slots must not exhaust either device or parser storage.
|
||||
for (unsigned i = 0; i <= CONFIG_BLUEPAD32_MAX_DEVICES; ++i) {
|
||||
assert(uni_bt_le_switch2_handle_advertisement(packet, size));
|
||||
assert(discovery_calls == i + 2);
|
||||
assert(!uni_hid_device_get_instance_for_address(candidate_address));
|
||||
for (unsigned slot = 1; slot < PEERS; ++slot) {
|
||||
uint8_t type;
|
||||
assert(!peers[slot].used);
|
||||
assert(!uni_hid_parser_switch2_identity_address_type(&peers[slot].device, &type));
|
||||
}
|
||||
assert(connected == 1 && scan_stops == 1 && disconnected == 0);
|
||||
assert(timer_count == mate_timers && listeners == mate_listeners);
|
||||
assert(mate->used && mate->link_alive && mate->device.conn.state == UNI_BT_CONN_STATE_DEVICE_READY);
|
||||
}
|
||||
|
||||
// A rejected candidate must remain discoverable once policy permits it.
|
||||
admit = true;
|
||||
assert(uni_bt_le_switch2_handle_advertisement(packet, size));
|
||||
assert(uni_hid_device_get_instance_for_address(candidate_address));
|
||||
assert(connected == 2 && scan_stops == 2 && disconnected == 0);
|
||||
unsigned admitted_calls = discovery_calls;
|
||||
assert(uni_bt_le_switch2_handle_advertisement(packet, size));
|
||||
assert(discovery_calls == admitted_calls && connected == 2 && scan_stops == 2);
|
||||
assert(mate->used && mate->link_alive);
|
||||
|
||||
reset();
|
||||
size = advertisement(packet, UNI_SW2_JOYCON_R_PID, false);
|
||||
admit = false;
|
||||
assert(uni_bt_le_switch2_handle_advertisement(packet, size));
|
||||
advance(3000);
|
||||
assert(discovery_calls == 1 && connected == 0 && scan_stops == 0 && disconnected == 0);
|
||||
assert(timer_count == 0 && listeners == 0);
|
||||
for (unsigned slot = 0; slot < PEERS; ++slot)
|
||||
assert(!peers[slot].used);
|
||||
}
|
||||
|
||||
static void test_missing_descriptor_and_setup_timeout(void) {
|
||||
reset();
|
||||
struct fixture_peer* peer = connect_peer(UNI_SW2_PRO_PID, false);
|
||||
|
|
@ -1024,9 +1108,52 @@ static void test_expired_history_does_not_starve_fresh_sequences(void) {
|
|||
assert(uni_hid_parser_switch2_haptics_dropped() == drops + 1);
|
||||
}
|
||||
|
||||
static void test_gatt_busy_retries_without_disconnect_or_sequence_loss(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_PRO_PID, false);
|
||||
uni_switch2_haptics_frame_t frame = native_frame(3, 2, 24);
|
||||
uint32_t drops = uni_hid_parser_switch2_haptics_dropped();
|
||||
next_write_error = GATT_CLIENT_BUSY;
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &frame, now_ms));
|
||||
advance(1);
|
||||
assert(!disconnected && peer->rumbles == 0);
|
||||
advance(13);
|
||||
assert(!disconnected && peer->rumbles == 1);
|
||||
assert((peer->rumble[1] & 15) == 0);
|
||||
assert_block(peer, 0, &frame.sides[0]);
|
||||
assert_block(peer, 1, &frame.sides[1]);
|
||||
|
||||
// The SDK uses the same busy status on command writes such as player LEDs.
|
||||
next_write_error = GATT_CLIENT_BUSY;
|
||||
uni_hid_parser_switch2_set_player_leds(&peer->device, 3);
|
||||
assert(!disconnected);
|
||||
advance(13);
|
||||
assert(peer->command[0] == 0x09 && peer->command[8] == 3);
|
||||
acknowledge(peer, false);
|
||||
assert(!disconnected && uni_hid_parser_switch2_haptics_dropped() == drops);
|
||||
}
|
||||
|
||||
static void test_immediate_connection_failure_preserves_reconnect_discovery(void) {
|
||||
reset();
|
||||
uint8_t packet[64];
|
||||
size_t size = advertisement(packet, UNI_SW2_JOYCON_R_PID, false);
|
||||
next_connect_error = ERROR_CODE_COMMAND_DISALLOWED;
|
||||
assert(uni_bt_le_switch2_handle_advertisement(packet, size));
|
||||
assert(scan_running && !peers[0].used && !timer_count && !listeners);
|
||||
assert(uni_bt_le_switch2_handle_advertisement(packet, size));
|
||||
assert(peers[0].used && connected == 2); // The same remembered peer can retry.
|
||||
|
||||
reset();
|
||||
scan_enabled = scan_running = false;
|
||||
next_connect_error = ERROR_CODE_COMMAND_DISALLOWED;
|
||||
assert(uni_bt_le_switch2_handle_advertisement(packet, size));
|
||||
assert(!scan_running && !peers[0].used); // Explicit stop must not be overridden.
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_connected_callback_rejection();
|
||||
test_advertisement_bounds_and_admission();
|
||||
test_discovery_metadata_and_rejection_isolation();
|
||||
test_immediate_connection_failure_preserves_reconnect_discovery();
|
||||
test_missing_descriptor_and_setup_timeout();
|
||||
test_pairing_gate_and_write_ack_order();
|
||||
test_calibration_physical_inputs_and_sensor_units();
|
||||
|
|
@ -1043,6 +1170,7 @@ int main(void) {
|
|||
test_identical_hold_coalescing_refreshes_borrowed_head();
|
||||
test_hold_coalescing_requires_identical_samples_and_side_masks();
|
||||
test_expired_history_does_not_starve_fresh_sequences();
|
||||
test_gatt_busy_retries_without_disconnect_or_sequence_loss();
|
||||
reset();
|
||||
puts("Switch2 protocol boundaries, setup failure, pairing, calibration, physical input, motion and rumble passed");
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -98,6 +98,11 @@ def test_bluepad32_backend_lifecycle_native(tmp_path: Path) -> None:
|
|||
"switch2-reverse",
|
||||
"switch2-multiple-pairs",
|
||||
"switch2-admission",
|
||||
"switch2-radio-policy",
|
||||
"switch2-radio-settling",
|
||||
"switch2-mate-reconnect",
|
||||
"switch2-mate-pending",
|
||||
"switch2-mate-pairing-window",
|
||||
"switch2-pairing-inventory",
|
||||
"switch2-hd-pro",
|
||||
"switch2-hd-solo-left",
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@ from __future__ import annotations
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
|
||||
|
||||
from prepare_bluepad32 import prepare_bluepad32
|
||||
|
||||
|
||||
def test_switch2_parser_protocol_and_lifecycle(tmp_path: Path) -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
|
|
@ -25,6 +30,12 @@ def test_switch2_parser_protocol_and_lifecycle(tmp_path: Path) -> None:
|
|||
)
|
||||
if btstack is None:
|
||||
pytest.skip("Pico SDK BTstack headers required; configure firmware or set PICO_SDK_PATH")
|
||||
prepared = prepare_bluepad32(
|
||||
root / "external" / "bluepad32",
|
||||
root / "patches" / "bluepad32-sdl3-imu.patch",
|
||||
tmp_path / "bluepad32-src",
|
||||
)
|
||||
component = prepared / "src" / "components" / "bluepad32"
|
||||
executable = tmp_path / "switch2_parser_native_test"
|
||||
subprocess.run(
|
||||
[
|
||||
|
|
@ -39,7 +50,7 @@ def test_switch2_parser_protocol_and_lifecycle(tmp_path: Path) -> None:
|
|||
"-DENABLE_CLASSIC",
|
||||
f"-I{root / 'tests' / 'switch2_parser_native_stubs'}",
|
||||
f"-I{root / 'bluepad32_config'}",
|
||||
f"-I{root / 'external' / 'bluepad32' / 'src' / 'components' / 'bluepad32' / 'include'}",
|
||||
f"-I{component / 'include'}",
|
||||
f"-I{btstack}",
|
||||
f"-I{btstack.parent / '3rd-party' / 'bluedroid' / 'encoder' / 'include'}",
|
||||
f"-I{btstack.parent / '3rd-party' / 'bluedroid' / 'decoder' / 'include'}",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue