You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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.
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.
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.
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
Standalone Electron app (SDL path active), Bluetooth gamepad, vehicle connected and armed, joystick forwarding enabled.
Hold a stick deflected (e.g. full yaw) and keep holding it through the next steps.
Turn Bluetooth off (or walk out of range).
Turn Bluetooth back on shortly after, so the controller reconnects.
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.
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_CONTROLmessages 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. BecauseMANUAL_CONTROLmessages keep flowing at the normal rate, the autopilot's pilot-input-loss failsafe never triggers, so nothing stops the vehicle.What we traced internally:
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 onceinstance.closedbecomes true, which for Bluetooth can take a very long time or not happen at all during the session.onElectronSDLControllerJoystickStateChangeinsrc/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.MANUAL_CONTROLalternates between the real value (0) and the frozen deflection, and the vehicle keeps moving until the zombie device eventually disappears.Steps to reproduce
Expected: the vehicle stops when the stick is released.
Actual: the vehicle keeps moving. Observing the mavlink2rest websocket shows
MANUAL_CONTROLmessages alternating between the released value (0) and the pre-disconnect deflection.Primary pain point(s)
Additional context
instance.closed-only cleanup insrc/electron/services/joystick.ts, no per-frame change comparison in the SDL handler insrc/libs/joystick/manager.ts) are unchanged on currentmaster.Prerequisites