Skip to content

Connect-Trackers picks wrong CH340 device with no fallback to other ports #1894

Description

@WeberPyth

Hi, I'm from the Udcap VR Gloves development team. While running regression tests for Udcap VR Gloves compatibility with SlimeVR, we found an issue in the desktop connect-trackers flow.

Steps to Reproduce

Case 1: Udcap VR Gloves receiver connected, no other program occupying the serial port

  1. Plug in a Udcap VR Gloves receiver (CH340 chip, VID/PID = 0x1A86:0x7522) alongside a real SlimeVR tracker (also CH340)

  2. Open the SlimeVR desktop app, click to Connect Trackers

Result: The flow gets stuck at "unable to get logs from tracker" (NO_SERIAL_LOGS_ERROR), and never automatically tries the next CH340 device

Case 2: Udcap Driver already running and occupying the receiver's serial port

  1. Plug in a Udcap VR Gloves receiver

  2. Start the Udcap Driver, pair the gloves (the receiver's serial port is now occupied)

  3. Open the SlimeVR desktop app, click to Connect Trackers

Result: The flow gets stuck at "looking for tracker" — SlimeVR tries to open the already-occupied port, fails, and doesn't fall back to other ports

Root Cause

After reviewing the code, the issue traces to DesktopSerialHandler.openSerial() (DesktopSerialHandler.kt:96-100):

val newPort: SerialPort? = ports.find {
    (auto && isKnownBoard(SerialPortWrapper(it)))
    ...
}
  • find {} returns only the first port matching isKnownBoard
  • isKnownBoard only checks a VID/PID whitelist (CH340 = 0x1A86:0x7522), so it cannot distinguish a real SlimeVR tracker from another CH340-based device
  • When the first match is not a tracker (or its port is occupied), there is no fallback to try the next matching port

Additional Finding: Infinite Retry Loop When Port Is Occupied

The stuck-at-"looking for tracker" behavior in Case 2 is caused by a separate issue in ProvisioningHandler.java.

Both start() (line 53) and initSerial() (line 68) set provisioningStatus via direct field assignment instead of calling changeStatus():

// ProvisioningHandler.java:47-55
public void start(String ssid, String password, String port) {
    this.provisioningStatus = ProvisioningStatus.NONE;  // direct assignment
    ...
}

// ProvisioningHandler.java:67-68
public void initSerial(String port) {
    this.provisioningStatus = ProvisioningStatus.SERIAL_INIT;  // direct assignment
    ...
}

Because changeStatus() is the only method that updates lastStatusChange (line 222) and broadcasts status updates to GUI listeners, this causes two problems:

  1. lastStatusChange is never updated from its initial value of -1

    The timeout check in provisioningTick() (line 122) becomes a no-op guard:

    System.currentTimeMillis() - (-1) > 3000  // always true

    This causes initSerial() to be called every ~1 second in an infinite retry loop attempting to open the same occupied port.

  2. The GUI never receives the SERIAL_INIT status update

    The GUI defaults to NONE and shows "looking for tracker" indefinitely, while the server is actually stuck in a SERIAL_INIT → retry loop.

Furthermore, the NO_SERIAL_DEVICE_FOUND guard (lines 112-119) never triggers because:

vrServer.serialHandler.getKnownPorts().findAny().isEmpty()

returns false — the occupied CH340 device still appears in the OS port enumeration.

Suggestion

Are there plans for a fallback/retry mechanism so that when the first candidate device fails to connect or communicate, the system automatically tries the next isKnownBoard port? Or perhaps exposing port selection in the UI so users can manually choose the correct one when auto-selection picks the wrong device?

Addon

My English ability is limited, and all of the above content was translated with the aid of machine translation. If anything comes across as offensive or unclear, that is not my intention. I apologize.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions