@@ -95,6 +95,63 @@ static mp_obj_bluetooth_uuid_t create_mp_uuid(uint16_t uuid16, const uint8_t *uu
95
95
}
96
96
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
97
97
98
+ // Encryption state cache for all connections to prevent duplicate events
99
+ typedef struct _mp_btstack_encryption_state_t {
100
+ btstack_linked_item_t * next ; // Must be first field to match btstack_linked_item.
101
+ uint16_t conn_handle ;
102
+ bool encrypted ;
103
+ bool authenticated ;
104
+ bool bonded ;
105
+ uint8_t key_size ;
106
+ } mp_btstack_encryption_state_t ;
107
+
108
+ static mp_btstack_encryption_state_t * create_encryption_state (uint16_t conn_handle ) {
109
+ DEBUG_printf ("create_encryption_state: conn_handle=%d\n" , conn_handle );
110
+ mp_btstack_encryption_state_t * state = m_new (mp_btstack_encryption_state_t , 1 );
111
+ state -> conn_handle = conn_handle ;
112
+ state -> encrypted = false;
113
+ state -> authenticated = false;
114
+ state -> bonded = false;
115
+ state -> key_size = 0 ;
116
+ bool added = btstack_linked_list_add (& MP_STATE_PORT (bluetooth_btstack_root_pointers )-> encryption_states , (btstack_linked_item_t * )state );
117
+ (void )added ;
118
+ assert (added );
119
+ return state ;
120
+ }
121
+
122
+ static mp_btstack_encryption_state_t * find_encryption_state (uint16_t conn_handle ) {
123
+ DEBUG_printf ("find_encryption_state: conn_handle=%d\n" , conn_handle );
124
+ btstack_linked_list_iterator_t it ;
125
+ btstack_linked_list_iterator_init (& it , & MP_STATE_PORT (bluetooth_btstack_root_pointers )-> encryption_states );
126
+ mp_btstack_encryption_state_t * state = NULL ;
127
+ while (btstack_linked_list_iterator_has_next (& it )) {
128
+ state = (mp_btstack_encryption_state_t * )btstack_linked_list_iterator_next (& it );
129
+ DEBUG_printf (" --> iter state %d\n" , state -> conn_handle );
130
+ if (state -> conn_handle == conn_handle ) {
131
+ break ;
132
+ }
133
+ }
134
+ return state ;
135
+ }
136
+
137
+ static void remove_encryption_state (uint16_t conn_handle ) {
138
+ DEBUG_printf ("remove_encryption_state: conn_handle=%d\n" , conn_handle );
139
+ mp_btstack_encryption_state_t * state = find_encryption_state (conn_handle );
140
+ if (state ) {
141
+ bool removed = btstack_linked_list_remove (& MP_STATE_PORT (bluetooth_btstack_root_pointers )-> encryption_states , (btstack_linked_item_t * )state );
142
+ (void )removed ;
143
+ assert (removed );
144
+ m_del (mp_btstack_encryption_state_t , state , 1 );
145
+ }
146
+ }
147
+
148
+ static void update_encryption_state (mp_btstack_encryption_state_t * state , bool encrypted , bool authenticated , bool bonded , uint8_t key_size ) {
149
+ state -> encrypted = encrypted ;
150
+ state -> authenticated = authenticated ;
151
+ state -> bonded = bonded ;
152
+ state -> key_size = key_size ;
153
+ }
154
+
98
155
#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT
99
156
typedef struct _mp_btstack_active_connection_t {
100
157
btstack_linked_item_t * next ; // Must be first field to match btstack_linked_item.
@@ -232,6 +289,7 @@ static void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel
232
289
// Slave role.
233
290
irq_event = MP_BLUETOOTH_IRQ_CENTRAL_CONNECT ;
234
291
}
292
+ // Encryption state will be created on first encryption event
235
293
#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT
236
294
create_active_connection (conn_handle );
237
295
#endif
@@ -318,8 +376,7 @@ static void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel
318
376
hci_connection_t * hci_con = hci_connection_for_handle (conn_handle );
319
377
if (!hci_con ) {
320
378
DEBUG_printf (" --> warning: no HCI connection found for handle %d\n" , conn_handle );
321
- // Try to create a minimal encryption update with default values
322
- mp_bluetooth_gatts_on_encryption_update (conn_handle , false, false, false, 0 );
379
+ // Don't send fallback events - only send real pairing completion events
323
380
return ;
324
381
}
325
382
@@ -339,15 +396,51 @@ static void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel
339
396
}
340
397
#endif
341
398
342
- mp_bluetooth_gatts_on_encryption_update (conn_handle ,
343
- encrypted , authenticated , bonded , desc -> sm_actual_encryption_key_size );
399
+ // Only forward encryption updates for PAIRING_COMPLETE and REENCRYPTION_COMPLETE
400
+ // This matches NimBLE behavior which only reports final pairing result
401
+ bool should_forward = false;
402
+
403
+ if (event_type == SM_EVENT_PAIRING_COMPLETE || event_type == SM_EVENT_REENCRYPTION_COMPLETE ) {
404
+ // Check our cache to see if this is a duplicate
405
+ mp_btstack_encryption_state_t * enc_state = find_encryption_state (conn_handle );
406
+ if (!enc_state ) {
407
+ enc_state = create_encryption_state (conn_handle );
408
+ should_forward = true; // First time
409
+ } else {
410
+ // Check if state actually changed
411
+ should_forward = (enc_state -> encrypted != encrypted ) ||
412
+ (enc_state -> authenticated != authenticated ) ||
413
+ (enc_state -> bonded != bonded ) ||
414
+ (enc_state -> key_size != desc -> sm_actual_encryption_key_size );
415
+ }
416
+
417
+ // Update cache
418
+ if (enc_state ) {
419
+ update_encryption_state (enc_state , encrypted , authenticated , bonded , desc -> sm_actual_encryption_key_size );
420
+ }
421
+ }
422
+
423
+ if (should_forward ) {
424
+ DEBUG_printf (" --> pairing complete, forwarding final encryption state to Python\n" );
425
+ mp_bluetooth_gatts_on_encryption_update (conn_handle ,
426
+ encrypted , authenticated , bonded , desc -> sm_actual_encryption_key_size );
427
+ } else {
428
+ DEBUG_printf (" --> skipping intermediate encryption event (event_type=%d)\n" , event_type );
429
+ }
430
+ #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
431
+ } else if (event_type == SM_EVENT_JUST_WORKS_REQUEST ) {
432
+ #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
433
+ DEBUG_printf (" --> just works request, auto-accepting\n" );
434
+ // Just Works pairing requires automatic acceptance
435
+ uint16_t conn_handle = sm_event_just_works_request_get_handle (packet );
436
+ sm_just_works_confirm (conn_handle );
437
+ #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
344
438
} else if (event_type == SM_EVENT_PASSKEY_DISPLAY_NUMBER ) {
345
439
mp_bluetooth_gap_on_passkey_action (sm_event_passkey_display_number_get_handle (packet ), MP_BLUETOOTH_PASSKEY_ACTION_DISPLAY , sm_event_passkey_display_number_get_passkey (packet ));
346
440
} else if (event_type == SM_EVENT_PASSKEY_INPUT_NUMBER ) {
347
441
mp_bluetooth_gap_on_passkey_action (sm_event_passkey_input_number_get_handle (packet ), MP_BLUETOOTH_PASSKEY_ACTION_INPUT , 0 );
348
442
} else if (event_type == SM_EVENT_NUMERIC_COMPARISON_REQUEST ) {
349
443
mp_bluetooth_gap_on_passkey_action (sm_event_numeric_comparison_request_get_handle (packet ), MP_BLUETOOTH_PASSKEY_ACTION_NUMERIC_COMPARISON , sm_event_numeric_comparison_request_get_passkey (packet ));
350
- #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
351
444
} else if (event_type == HCI_EVENT_DISCONNECTION_COMPLETE ) {
352
445
DEBUG_printf (" --> hci disconnect complete\n" );
353
446
uint16_t conn_handle = hci_event_disconnection_complete_get_connection_handle (packet );
@@ -362,6 +455,12 @@ static void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel
362
455
}
363
456
uint8_t addr [6 ] = {0 };
364
457
mp_bluetooth_gap_on_connected_disconnected (irq_event , conn_handle , 0xff , addr );
458
+
459
+ #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
460
+ // Remove encryption state cache for this connection
461
+ remove_encryption_state (conn_handle );
462
+ #endif
463
+
365
464
#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT
366
465
remove_active_connection (conn_handle );
367
466
#endif
@@ -697,6 +796,9 @@ int mp_bluetooth_init(void) {
697
796
MP_STATE_PORT (bluetooth_btstack_root_pointers ) = m_new0 (mp_bluetooth_btstack_root_pointers_t , 1 );
698
797
mp_bluetooth_gatts_db_create (& MP_STATE_PORT (bluetooth_btstack_root_pointers )-> gatts_db );
699
798
799
+ // Initialize encryption state cache
800
+ MP_STATE_PORT (bluetooth_btstack_root_pointers )-> encryption_states = NULL ;
801
+
700
802
// Set the default GAP device name.
701
803
const char * gap_name = MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME ;
702
804
size_t gap_len = strlen (gap_name );
0 commit comments