@@ -44,15 +44,18 @@ var DefaultAdapter = &Adapter{
4444
4545// Enable configures the BLE stack. It must be called before any
4646// Bluetooth-related calls (unless otherwise indicated).
47+ //
48+ // poweredChan is cleared on both success and timeout paths so
49+ // a subsequent Enable() on the same Adapter can run again.
4750func (a * Adapter ) Enable () error {
4851 if a .poweredChan != nil {
4952 return errors .New ("already calling Enable function" )
5053 }
5154
5255 a .poweredChan = make (chan error , 1 )
5356
54- // Set the delegate before checking State so we don't miss an
55- // async DidUpdateState that fires between construction and now .
57+ // Set delegate before checking state — a fresh CBCentralManager
58+ // can fire DidUpdateState before SetDelegate, losing the event .
5659 a .cmd = & centralManagerDelegate {a : a }
5760 a .cm .SetDelegate (a .cmd )
5861
@@ -82,6 +85,42 @@ func (a *Adapter) Enable() error {
8285 return nil
8386}
8487
88+ // Reset tears down CoreBluetooth managers so a subsequent Enable()
89+ // rebuilds them from scratch. Useful for recovering from stale
90+ // CBPeripheral handles, adapter switching, and test cleanup.
91+ //
92+ // Caller must ensure no Scan/Connect/DiscoverServices is in flight.
93+ // After Reset, call Enable() to create fresh managers.
94+ //
95+ // Note: process-level CoreBluetooth state (e.g. the advertisement
96+ // deduplication table) survives Reset — only process exit clears it.
97+ func (a * Adapter ) Reset () error {
98+ if a .scanChan != nil {
99+ _ = a .StopScan ()
100+ }
101+
102+ // Unblock goroutines parked in Connect — closing the chan
103+ // yields a zero Peripheral so Connect returns an error.
104+ a .connectMap .Range (func (key , value any ) bool {
105+ a .connectMap .Delete (key )
106+ if ch , ok := value .(chan cbgo.Peripheral ); ok {
107+ defer func () { _ = recover () }()
108+ close (ch )
109+ }
110+ return true
111+ })
112+
113+ a .cm = cbgo .NewCentralManager (nil )
114+ a .pm = cbgo .NewPeripheralManager (nil )
115+ a .cmd = nil
116+ a .pmd = nil
117+ a .poweredChan = nil
118+ a .scanChan = nil
119+ a .peripheralFoundHandler = nil
120+
121+ return nil
122+ }
123+
85124// CentralManager delegate functions
86125
87126type centralManagerDelegate struct {
@@ -103,12 +142,10 @@ func (cmd *centralManagerDelegate) CentralManagerDidUpdateState(cmgr cbgo.Centra
103142 case cbgo .ManagerStateUnauthorized :
104143 event = errors .New ("bluetooth is not authorized for this app" )
105144 default :
106- // Unknown / Resetting are intermediate; wait for the next update.
107145 return
108146 }
109- // Non-blocking; select handles a nil poweredChan correctly (the
110- // case is never ready, default fires) so a late or repeated
111- // update never parks the delegate goroutine.
147+ // Non-blocking send: poweredChan may be nil after Enable
148+ // completes, or already buffered.
112149 select {
113150 case cmd .a .poweredChan <- event :
114151 default :
0 commit comments