Skip to content

fix(bt): don't idle-disconnect while audio is playing to the controller - #146

Open
gunCannoli wants to merge 1 commit into
SundayMoments:mainfrom
gunCannoli:pr/idle-disconnect-audio-aware
Open

fix(bt): don't idle-disconnect while audio is playing to the controller#146
gunCannoli wants to merge 1 commit into
SundayMoments:mainfrom
gunCannoli:pr/idle-disconnect-audio-aware

Conversation

@gunCannoli

@gunCannoli gunCannoli commented Sep 7, 2026

Copy link
Copy Markdown

Problem

Idle Disconnect drops the controller after its timeout based only on the
absence of meaningful HID input. Audio playback is not considered.

Using the controller as a headset -- a headset plugged into the 3.5 mm jack,
watching a movie or listening to music -- means no button input for long
stretches. The controller idle-disconnects mid-playback and the audio cuts
out. The only workarounds are disabling Idle Disconnect or setting an
impractically long timeout.

Change

One condition in src/bt.cpp, in the inactivity check in
l2cap_packet_handler_cold():

    // Inactivity detection.
    if (mute[1]) { // Microphone mute is enabled.
        return;
    }
    if (audio_output_route_protected()) {
        inactive_time = now_us;
        return;
    }
    if (!meaningful_input_activity && now_us - inactive_time > idle_disconnect_timeout_us()) {
        ...
    }

audio_output_route_protected() (audio_recent() || usb_speaker_streaming_active())
already exists in the same file and is already used for the RSSI idle gate
and output-route protection. This reuses it rather than adding a new signal.

The idle clock is reset (rather than simply returning) so the full timeout
restarts when playback stops -- matching how a button press resets it.
Returning without the reset would leave a long playback session about to trip
the timeout the moment audio ends.

Placement is after the existing mute[1] mic-mute carve-out and before the
timeout comparison. It mirrors that carve-out in location and shape.

Scope

  • No new state, no timer, no allocation.
  • No companion command, no protocol change, no COMMAND_ID, no
    PROTOCOL_MINOR bump.
  • No UI change. The existing Idle Disconnect enable toggle and timeout
    selector behave exactly as before; they simply stop firing during
    playback.
  • Runs only on HID interrupt packets, the same cadence as the existing
    check.

Testing

tests/firmware/usb_descriptor_migration_test.cpp -- the RSSI-idle source
assertion is extended to require the new guard, its exact body, and its
ordering between the mic-mute carve-out and the timeout comparison. All
firmware host-side suites pass.

Hardware (Waveshare RP2350B-Plus-W, Idle Disconnect set to 1 minute):

  1. Headset in the jack, continuous playback, no controller input -- stays
    connected well past the timeout, audio uninterrupted.
  2. Playback stopped, still no input -- disconnects roughly one timeout period
    after audio stopped, not immediately.
  3. No audio and no input -- disconnects at the timeout, unchanged from
    before.
  4. Mic mute still independently suppresses idle disconnect.

Idle Disconnect drops the controller after its timeout based only on the
absence of meaningful HID input. Using the controller as a headset -- a
headset in its 3.5 mm jack, watching a movie -- means no button input for
long stretches, so the controller idle-disconnects mid-playback and the
audio cuts out.

Treat audio actively routed to the controller as the device being in use.
audio_output_route_protected() (audio_recent() ||
usb_speaker_streaming_active()) already exists in bt.cpp and is already used
for the RSSI idle gate and output-route protection, so this reuses it rather
than adding a new signal.

The idle clock is reset rather than simply returning, so the full timeout
restarts when playback stops -- matching how a button press resets it.
Returning without the reset would leave a long playback session about to
trip the timeout the moment audio ends.

Placement mirrors the existing mute[1] mic-mute carve-out: after it, before
the timeout comparison. No new state, no companion command, no protocol
change, no UI change.

The RSSI-idle source assertion in usb_descriptor_migration_test.cpp is
extended to require the guard, its exact body, and its ordering between the
mic-mute carve-out and the timeout comparison.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant