Robustness for the USB connection.
ant: replace usb.GetDevice with direct gousb usage Remove dependency on antgo/driver/usb to fix libusb context leaks on device-not-found errors. The previous code called usb.GetDevice which creates a gousb.Context via libusb_init but leaks it on every error path (device not found, interface claim failure, etc.). Calling ListenForTrainer repeatedly (e.g. starting with no dongle and plugging it in later) triggered a libusb assertion in pthread_key_create because the leaked contexts were never torn down. Replace with direct gousb calls in ListenForTrainer, managing the gousb.Context lifecycle explicitly. Cleanup is ordered correctly on all paths: release interface, close device, close context, cancel the ANT message goroutine context. Also convert panics to returned errors for graceful retry.
This commit is contained in:
parent
c75c2ea0e1
commit
9efdfc9b83
@ -1,16 +1,25 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/gousb"
|
"github.com/google/gousb"
|
||||||
"github.com/half2me/antgo/ant"
|
"github.com/half2me/antgo/ant"
|
||||||
"github.com/half2me/antgo/device"
|
"github.com/half2me/antgo/device"
|
||||||
"github.com/half2me/antgo/driver/usb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type antUSB struct {
|
||||||
|
in *bufio.Reader
|
||||||
|
out *gousb.OutEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *antUSB) Read(p []byte) (int, error) { return d.in.Read(p) }
|
||||||
|
func (d *antUSB) Write(p []byte) (int, error) { return d.out.Write(p) }
|
||||||
|
|
||||||
const (
|
const (
|
||||||
antDataPageMask = 0x7F // Mask for data page number (7 bits, clears toggle bit)
|
antDataPageMask = 0x7F // Mask for data page number (7 bits, clears toggle bit)
|
||||||
antDataPageCyclingPower = 0x10 // Data page 16 = Cycling Power Feature page
|
antDataPageCyclingPower = 0x10 // Data page 16 = Cycling Power Feature page
|
||||||
@ -33,21 +42,44 @@ func resetAndWait(drv io.ReadWriter) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ListenForTrainer(vendorId uint16, productId uint16, deviceNumber uint32,
|
func ListenForTrainer(vendorId uint16, productId uint16, deviceNumber uint32,
|
||||||
events chan<- Event) {
|
events chan<- Event) error {
|
||||||
|
|
||||||
drv, err := usb.GetDevice(gousb.ID(vendorId), gousb.ID(productId))
|
gctx := gousb.NewContext()
|
||||||
|
defer gctx.Close()
|
||||||
|
|
||||||
|
dev, err := gctx.OpenDeviceWithVIDPID(gousb.ID(vendorId), gousb.ID(productId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return fmt.Errorf("ant: open device: %w", err)
|
||||||
}
|
}
|
||||||
defer drv.Close()
|
if dev == nil {
|
||||||
|
return fmt.Errorf("ant: device not found (0x%04x:0x%04x)", vendorId, productId)
|
||||||
|
}
|
||||||
|
defer dev.Close()
|
||||||
|
|
||||||
|
intf, done, err := dev.DefaultInterface()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ant: claim interface: %w", err)
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
out, err := intf.OutEndpoint(1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ant: open out endpoint: %w", err)
|
||||||
|
}
|
||||||
|
inp, err := intf.InEndpoint(1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ant: open in endpoint: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
drv := &antUSB{in: bufio.NewReader(inp), out: out}
|
||||||
|
|
||||||
logMsg("ant: device opened")
|
logMsg("ant: device opened")
|
||||||
|
|
||||||
if err := resetAndWait(drv); err != nil {
|
if err := resetAndWait(drv); err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
if err := device.StartRxScanModeWait(drv); err != nil {
|
if err := device.StartRxScanModeWait(drv); err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
logMsg("ant: scan mode started")
|
logMsg("ant: scan mode started")
|
||||||
|
|
||||||
@ -92,7 +124,7 @@ func ListenForTrainer(vendorId uint16, productId uint16, deviceNumber uint32,
|
|||||||
}
|
}
|
||||||
case <-time.After(10 * time.Second):
|
case <-time.After(10 * time.Second):
|
||||||
logMsg("ant: no more messages arrived, restarting")
|
logMsg("ant: no more messages arrived, restarting")
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@ -83,7 +84,10 @@ This allows these trainers to be used with modern devices that don't support ANT
|
|||||||
events := make(chan Event, 1)
|
events := make(chan Event, 1)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
ListenForTrainer(uint16(c.vendorID), uint16(c.productID), c.deviceNumber, events)
|
if err := ListenForTrainer(uint16(c.vendorID), uint16(c.productID), c.deviceNumber, events); err != nil {
|
||||||
|
logPrintf("ant: listener error: %v, retry in 10 seconds\n", err)
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
ExposeBluetooth(events)
|
ExposeBluetooth(events)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user