stop_scan_() logs an error for STARTING, which the settle right after it
makes moot; only ask the controller to stop a scan that is running. Fold the
two adjacent client-count guards and fix the services_released_ comment.
The before-disabled callbacks can run user automations (on_scan_end fires
when the tracker settles the scanner), so an enable issued there was flipped
to ACTIVE and then overwritten by the DISABLED write after the teardown.
Turn it into a bring-up instead. ble_client also skips advertisements until
its app is registered, so the tracker does not stop the scan for a connect
that would be rejected.
The tracker now learns about a disable from the before-disabled handler
instead of polling is_active(), so a cancelled disable never reaches it and
the scan restart no longer needs an IDLE gate. enable() and disable() are
inline wrappers over a per-direction chain, ble_client skips the cache
clean on a stack that is going down, and a stale comment and doc are
brought up to date.
enable() while a disable was still pending was ignored, so a disable followed
by an enable before the next loop pass left BLE off. Cancel the pending
transition in both directions; nothing has been torn down or brought up yet.
The tracker only restarts its scan after a re-enable from IDLE, since a
cancelled disable never stopped it.
Without the replayed close events a connected ble_client kept its services
across a ble.disable cycle and grew a second copy on reconnect. Free them and
report the disconnect from the hook, forget the old interface, and refuse an
open before the app is registered on the new stack, matching the proxy backend.
The loop backstop only ran one pass earlier than the tracker hook and forced
the reset into a helper with extra branches; the hook alone now does the
reset. The unregistered-interface check moves to connect(), where it covers
both the INIT state and the window before REG_EVT with one predicate.
Events the old stack queued while going down were replayed after the next
ble.enable against a fresh stack that hands out the same interface ids, and
the scanner relied on that replay to leave STOPPING. Drain the queue after
the teardown and put the scanner back to IDLE from the before-disabled
handler, since no completion can arrive anymore.
An idle GATT client slot has its loop disabled, so the stack-down reset in
loop() never runs across a ble.disable/ble.enable cycle. The slot keeps the
gattc interface of the torn-down stack and never registers again; the next
esp_ble_gattc_open on that interface is dropped by Bluedroid without any
event and the slot stays in CONNECTING forever, which also blocks scanning.
Give ESPBTClient a before-disabled hook, fan it out from the tracker's
existing handler, and have both client implementations settle any link,
go back to INIT and enable their loop so they register on the new stack.
The Bluedroid backend also refuses to open on an unregistered interface
instead of waiting for an event that cannot come.