Skip to content

Stale joystick input keeps driving the vehicle after a Bluetooth controller reconnect (Electron/SDL) #2863

Description

@Neptune-Ryan

Bug description

Safety-relevant. After a Bluetooth game controller briefly disconnects and reconnects while the operator is holding a stick deflected, the standalone Electron app (SDL controller path) keeps sending MANUAL_CONTROL messages containing the pre-disconnect stick values even after the operator releases the stick. The vehicle keeps moving (e.g. keeps yawing) although the physical stick is centered. Because MANUAL_CONTROL messages keep flowing at the normal rate, the autopilot's pilot-input-loss failsafe never triggers, so nothing stops the vehicle.

What we traced internally:

  1. When the Bluetooth link drops, the OS can keep the HID node alive for a while, so the SDL controller instance is not reported as closed. The main-process polling loop (src/electron/services/joystick.ts) keeps reading the instance every 50 ms and keeps re-sending the last (now frozen) axis values to the renderer — the cleanup path only runs once instance.closed becomes true, which for Bluetooth can take a very long time or not happen at all during the session.
  2. When the controller reconnects, SDL enumerates it as a new device with a new device id, so two device streams now reach the renderer: the live one, and a "zombie" one still repeating the frozen pre-disconnect axes at 20 Hz.
  3. The renderer-side SDL state handler (onElectronSDLControllerJoystickStateChange in src/libs/joystick/manager.ts) forwards every incoming frame unconditionally — unlike the browser Gamepad API path, which compares each frame against the previous state before emitting. The two streams therefore alternately overwrite the manual-control state, and the frozen deflected values win roughly half the time.
  4. Result: after the stick is released, the transmitted MANUAL_CONTROL alternates between the real value (0) and the frozen deflection, and the vehicle keeps moving until the zombie device eventually disappears.

Steps to reproduce

  1. Standalone Electron app (SDL path active), Bluetooth gamepad, vehicle connected and armed, joystick forwarding enabled.
  2. Hold a stick deflected (e.g. full yaw) and keep holding it through the next steps.
  3. Turn Bluetooth off (or walk out of range).
  4. Turn Bluetooth back on shortly after, so the controller reconnects.
  5. Keep holding the stick for a moment, then release it.

Expected: the vehicle stops when the stick is released.
Actual: the vehicle keeps moving. Observing the mavlink2rest websocket shows MANUAL_CONTROL messages alternating between the released value (0) and the pre-disconnect deflection.

Primary pain point(s)

  • The vehicle continues to move with no operator input — effectively uncommanded motion that is hard to stop without disarming.
  • The autopilot's lost-manual-control failsafe never triggers, because messages keep flowing at the normal rate.
  • Nothing in the UI indicates that a stale device stream is still being consumed; the joystick screen looks normal.

Additional context

  • Windows 11, standalone Electron build, Bluetooth Xbox-style controller.
  • Observed on a downstream build; the relevant code paths (unconditional 50 ms state re-emit and instance.closed-only cleanup in src/electron/services/joystick.ts, no per-frame change comparison in the SDL handler in src/libs/joystick/manager.ts) are unchanged on current master.
  • The browser version does not reproduce this, consistent with the Gamepad API path having per-frame change comparison while the SDL path does not.
  • Related but distinct from MANUAL_CONTROL messages are sent after the joystick disconnects #215 (there the joystick was unplugged via USB, messages stopped and the failsafe worked; here messages keep flowing, so the failsafe never engages).

Prerequisites

  • I have checked to make sure that a similar issue has not already been filed or fixed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

Fields

Priority

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions