fix(serial): list a COM port once, in the order a human reads it
An operator's keyer dropdown showed COM1 and COM3 twice, in the order COM1 COM1 COM3 COM8 COM9 COM4 COM3, with "COM3COM3" as the selected value — two entries sharing one value both counted as selected. Not our bug to start with, but ours to absorb. Windows enumerates serial ports from HARDWARE\DEVICEMAP\SERIALCOMM, which maps a DEVICE PATH to a COM name, so the same name legitimately appears twice when two devices claim it: a driver uninstalled without cleaning up after itself, or virtual-port software. go.bug.st/serial returns one entry per device, faithfully. Deduplicated and naturally sorted in the one binding every port dropdown in the app calls, so CAT, rotators, amplifiers and the keyer all get it. COM4 now sits between COM3 and COM8 rather than after COM9, which is how a port gets overlooked on a machine with a dozen. A dropped duplicate is logged rather than swallowed: two devices holding one COM name is also a good reason for one of them to report that port busy, and that is not something an operator can work out from a list.
This commit is contained in:
@@ -16538,7 +16538,72 @@ type WinkeyerSettings struct {
|
||||
|
||||
// ListSerialPorts returns the available COM ports for the keyer dropdown.
|
||||
func (a *App) ListSerialPorts() ([]string, error) {
|
||||
return winkeyer.ListPorts()
|
||||
ports, err := winkeyer.ListPorts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tidySerialPorts(ports), nil
|
||||
}
|
||||
|
||||
// tidySerialPorts removes duplicates and puts the list in the order a human
|
||||
// reads it.
|
||||
//
|
||||
// Windows enumerates serial ports from HARDWARE\DEVICEMAP\SERIALCOMM, which maps
|
||||
// a DEVICE path to a COM name — so the same name legitimately appears twice when
|
||||
// two devices claim it. That happens with a driver uninstalled without cleaning
|
||||
// up after itself, and with virtual-port software. An operator saw COM1 and COM3
|
||||
// listed twice, and the dropdown showed "COM3COM3" as its value, because two
|
||||
// entries with the same value both counted as selected.
|
||||
//
|
||||
// The order is natural, not lexical: COM4 belongs between COM3 and COM8, and
|
||||
// listing it after COM9 is how a port gets overlooked on a machine with a dozen.
|
||||
// Anything not named COMn keeps its own alphabetical order, after them.
|
||||
//
|
||||
// A duplicate is worth a log line rather than silence: two devices holding one
|
||||
// COM name is also why one of them is "busy" when the other is opened, and that
|
||||
// is not something an operator can work out from a dropdown.
|
||||
func tidySerialPorts(ports []string) []string {
|
||||
seen := make(map[string]bool, len(ports))
|
||||
out := make([]string, 0, len(ports))
|
||||
for _, p := range ports {
|
||||
name := strings.TrimSpace(p)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
key := strings.ToUpper(name)
|
||||
if seen[key] {
|
||||
applog.Printf("serial: %s is claimed by more than one device in the Windows port map — listing it once", name)
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
out = append(out, name)
|
||||
}
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
ni, oki := comPortNumber(out[i])
|
||||
nj, okj := comPortNumber(out[j])
|
||||
switch {
|
||||
case oki && okj:
|
||||
return ni < nj
|
||||
case oki:
|
||||
return true // COMn before anything else
|
||||
case okj:
|
||||
return false
|
||||
}
|
||||
return out[i] < out[j]
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
// comPortNumber extracts n from "COMn", false for any other shape.
|
||||
func comPortNumber(s string) (int, bool) {
|
||||
if len(s) <= 3 || !strings.EqualFold(s[:3], "COM") {
|
||||
return 0, false
|
||||
}
|
||||
n, err := strconv.Atoi(s[3:])
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
// GetWinkeyerSettings returns the persisted keyer config (with sane defaults).
|
||||
|
||||
Reference in New Issue
Block a user