rebase: Phase D — multi-slot pairing on top of state_mgr base

bt.h: add OLED accessors (bt_is_connected, bt_get_addr, bt_hci_err_count)
and 6 multi-slot accessors (bt_get_slot, bt_set_slot, bt_forget_slot,
bt_wipe_all_slots, bt_slot_occupied, bt_slot_get_addr).

bt.cpp:
- #include "slots.h"; add g_current_slot + update_discoverable()
  helper that gates gap_discoverable_control on slots_any_empty().
- 3 OLED accessor impls + 6 slot accessor impls.
- bt_init: slots_load() + g_current_slot from Config_body.current_slot,
  then update_discoverable() instead of bare gap_discoverable_control(1).
- HCI_EVENT_INQUIRY_RESULT: slot-ownership filter (skip devices owned
  by other slots; if our slot is occupied, only accept matching addr).
- L2CAP_EVENT_CHANNEL_OPENED HID_CONTROL: slot_assign on empty slot
  (Phase G auto-bond pattern).
- L2CAP_EVENT_CHANNEL_OPENED HID_INTERRUPT: replace upstream's
  gap_*_control(false) pair with update_discoverable() — they wanted
  "non-discoverable once connected"; we want "non-discoverable once
  all 4 slots are full". Strictly looser, which is correct since
  empty slots need to accept new pairings.
- Inquiry-complete + disconnection-complete handlers similarly
  swapped to update_discoverable().

Upstream's state_mgr hooks (state_init / state_set / state_update
calls in L2CAP_EVENT_CHANNEL_OPENED and elsewhere) are preserved
as-is — they're the upstream rumble-fix infrastructure.

Build still fails on oled.cpp references to audio_peak_* / counter
accessors that Phase E adds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MarcelineVPQ
2026-05-17 07:58:18 -06:00
co-authored by Claude Opus 4.7
parent 41c89ebcfd
commit 0593d79f5b
2 changed files with 139 additions and 5 deletions
+126 -5
View File
@@ -18,6 +18,7 @@
#include "config.h"
#include "state_mgr.h"
#include "pico/util/queue.h"
#include "slots.h"
#define MTU_CONTROL 672
#define MTU_INTERRUPT 672
@@ -50,10 +51,96 @@ struct send_element {
absolute_time_t inactive_time = 0; // 手柄长时间静默
// Multi-slot pairing state. Modeled on zurce/DS5Dongle-OLED.
static int g_current_slot = 0;
bool bt_disconnect(); // fwd decl — defined further down
// Keep the dongle discoverable while at least one slot is empty (covers
// initial setup + partial-wipe states). Once all 4 slots are full, go
// non-discoverable so a stray phone can't try to pair.
static void update_discoverable() {
if (slots_any_empty()) {
gap_discoverable_control(1);
} else {
gap_discoverable_control(0);
}
}
void bt_register_data_callback(bt_data_callback_t callback) {
bt_data_callback = callback;
}
// ---- OLED add-on + multi-slot accessors --------------------------------
bool bt_is_connected() { return hid_interrupt_cid != 0; }
void bt_get_addr(uint8_t out[6]) { memcpy(out, current_device_addr, 6); }
uint32_t bt_hci_err_count() { return 0; } // stub for OLED Diagnostics
int bt_get_slot() { return g_current_slot; }
void bt_set_slot(int slot) {
if (slot < 0 || slot >= kNumSlots) return;
if (slot == g_current_slot) return;
g_current_slot = slot;
Config_body cfg = get_config();
cfg.current_slot = (uint8_t)slot;
set_config(cfg);
config_save();
if (bt_is_connected()) {
// DISCONNECTION_COMPLETE will restart inquiry under the new filter.
bt_disconnect();
} else {
gap_inquiry_stop();
gap_inquiry_start(30);
}
update_discoverable();
}
bool bt_slot_occupied(int slot) { return slot_occupied(slot); }
void bt_slot_get_addr(int slot, uint8_t out[6]) { slot_get_addr(slot, out); }
void bt_forget_slot(int slot) {
if (slot < 0 || slot >= kNumSlots) return;
if (slot_occupied(slot)) {
uint8_t addr[6];
slot_get_addr(slot, addr);
gap_drop_link_key_for_bd_addr(addr);
}
slot_forget(slot);
update_discoverable();
if (slot == g_current_slot && bt_is_connected()) {
bt_disconnect();
}
}
void bt_wipe_all_slots() {
btstack_link_key_iterator_t it;
if (gap_link_key_iterator_init(&it)) {
bd_addr_t snapshot[16];
int n = 0;
bd_addr_t addr;
link_key_t key;
link_key_type_t type;
while (n < 16 && gap_link_key_iterator_get_next(&it, addr, key, &type)) {
bd_addr_copy(snapshot[n++], addr);
}
gap_link_key_iterator_done(&it);
for (int i = 0; i < n; i++) {
gap_drop_link_key_for_bd_addr(snapshot[i]);
}
}
slots_wipe_all();
update_discoverable();
if (bt_is_connected()) {
bt_disconnect();
}
}
void bt_send_packet(uint8_t *data, uint16_t len) {
if (hid_interrupt_cid != 0) {
l2cap_send(hid_interrupt_cid, data, len);
@@ -102,6 +189,13 @@ void bt_l2cap_init() {
int bt_init() {
queue_init(&send_fifo, sizeof(send_element), 10);
// Load persistent slot table BEFORE HCI comes up so the inquiry filter
// and discoverable-gating see the right state on the first event.
slots_load();
g_current_slot = get_config().current_slot;
if (g_current_slot < 0 || g_current_slot >= kNumSlots) g_current_slot = 0;
printf("[BT] Boot slot = %d\n", g_current_slot);
bt_l2cap_init();
// SSP (Secure Simple Pairing)
@@ -111,7 +205,7 @@ int bt_init() {
gap_ssp_set_authentication_requirement(SSP_IO_AUTHREQ_MITM_PROTECTION_NOT_REQUIRED_GENERAL_BONDING);
gap_connectable_control(1);
gap_discoverable_control(1);
update_discoverable();
hci_event_callback_registration.callback = &hci_packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
@@ -169,6 +263,24 @@ static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *p
// CoD 0x002508 = Gamepad (Major: Peripheral, Minor: Gamepad)
if ((cod & 0x000F00) == 0x000500) {
// Slot-ownership filter: skip devices owned by a different slot;
// if our slot is occupied, only accept its exact bd_addr.
// Unowned devices pair into the current slot if it's empty.
const int owner = slot_owner_of(addr);
if (owner >= 0 && owner != g_current_slot) {
printf("[HCI] Gamepad %s belongs to slot %d, skip (cur=%d)\n",
bd_addr_to_str(addr), owner, g_current_slot);
break;
}
if (slot_occupied(g_current_slot)) {
uint8_t want[6];
slot_get_addr(g_current_slot, want);
if (memcmp(want, addr, 6) != 0) {
printf("[HCI] Slot %d wants different addr, skip %s\n",
g_current_slot, bd_addr_to_str(addr));
break;
}
}
printf("[HCI] Gamepad found: %s (CoD: 0x%06x)\n", bd_addr_to_str(addr), (unsigned int) cod);
bd_addr_copy(current_device_addr, addr);
device_found = true;
@@ -191,7 +303,7 @@ static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *p
printf("[HCI] Restart inquiry\n");
gap_inquiry_start(30);
gap_connectable_control(1);
gap_discoverable_control(1);
update_discoverable();
}
break;
}
@@ -332,7 +444,7 @@ static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *p
tud_disconnect();
#endif
gap_connectable_control(1);
gap_discoverable_control(1);
update_discoverable();
const uint8_t reason = hci_event_disconnection_complete_get_reason(packet);
device_found = false;
new_pair = false;
@@ -432,6 +544,14 @@ static void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t
printf("[L2CAP] HID Control opened cid=0x%04X\n", local_cid);
hid_control_cid = local_cid;
// First-time pairing: assign this bd_addr to the current slot.
if (!slot_occupied(g_current_slot)) {
slot_assign(g_current_slot, current_device_addr);
printf("[Slots] Assigned %s to slot %d\n",
bd_addr_to_str(current_device_addr), g_current_slot);
update_discoverable();
}
const auto mtu = l2cap_get_remote_mtu_for_local_cid(hid_control_cid);
printf("[L2CAP] Remote Control MTU: %d\n",mtu);
} else if (psm == PSM_HID_INTERRUPT) {
@@ -458,8 +578,9 @@ static void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t
const auto mtu = l2cap_get_remote_mtu_for_local_cid(hid_interrupt_cid);
printf("[L2CAP] Remote Interrupt MTU: %d\n",mtu);
gap_connectable_control(false);
gap_discoverable_control(false);
// OLED Edition: keep discoverable rule centralized — discoverable
// when any slot is empty, dark otherwise.
update_discoverable();
// tud_connect();
} else {
printf("[L2CAP] Unknown Channel psm: 0x%02X", psm);
+13
View File
@@ -25,4 +25,17 @@ std::vector<uint8_t> get_feature_data(uint8_t reportId,uint16_t len);
void init_feature();
void set_feature_data(uint8_t reportId, uint8_t* data,uint16_t len);
// OLED add-on accessors.
bool bt_is_connected();
void bt_get_addr(uint8_t out[6]);
uint32_t bt_hci_err_count();
// Multi-slot persistent pairing (Phase G). Modeled on zurce/DS5Dongle-OLED.
int bt_get_slot();
void bt_set_slot(int slot);
void bt_forget_slot(int slot);
void bt_wipe_all_slots();
bool bt_slot_occupied(int slot);
void bt_slot_get_addr(int slot, uint8_t out[6]);
#endif //DS5_BRIDGE_BT_H