package main import ( "fmt" "os" "os/exec" "strconv" "time" "github.com/spf13/cobra" ) // Version and BuildTime are injected via ldflags at build time. var Version = "unknown" var BuildTime = "unknown" func parseHex(s string, bits int) (uint64, error) { return strconv.ParseUint(s, 0, bits) } type config struct { vendor string product string device string vendorID uint64 productID uint64 deviceNumber uint32 } func newConfig() *config { return &config{} } func main() { rootCmd := &cobra.Command{ Use: "bridge", Short: "ANT+ to Bluetooth LE bridge", Long: `Bridge an ANT+ cycling power trainer over Bluetooth Low Energy. This allows these trainers to be used with modern devices that don't support ANT+.`, Version: fmt.Sprintf("%s (built %s)", Version, BuildTime), RunE: func(cmd *cobra.Command, args []string) error { c := newConfig() c.vendor = cmd.Flags().Lookup("vendor").Value.String() c.product = cmd.Flags().Lookup("product").Value.String() c.device = cmd.Flags().Lookup("device").Value.String() vendorID, err := parseHex(c.vendor, 16) if err != nil { return fmt.Errorf("invalid --vendor %q: %w", c.vendor, err) } productID, err := parseHex(c.product, 16) if err != nil { return fmt.Errorf("invalid --product %q: %w", c.product, err) } c.vendorID = uint64(vendorID) c.productID = uint64(productID) if c.device != "" { deviceNumber, err := parseHex(c.device, 32) if err != nil { return fmt.Errorf("invalid --device %q: %w", c.device, err) } c.deviceNumber = uint32(deviceNumber) } deviceNumber := c.deviceNumber logMsgf("ANT Bridge %s built %s", Version, BuildTime) logMsgf("usb: vendor: 0x%04x/%d", vendorID, vendorID) logMsgf("usb: product: 0x%04x/%d", productID, productID) if deviceNumber == 0 { logMsgf("ant: device: any") } else { logMsgf("ant: device: 0x%03x/%d", deviceNumber, deviceNumber) } if err := exec.Command("hciconfig", "hci0", "down").Run(); err != nil { logPrintf("bt: hciconfig down failed: %v\n", err) } if err := exec.Command("hciconfig", "hci0", "up").Run(); err != nil { logPrintf("bt: hciconfig up failed: %v\n", err) } events := make(chan Event, 1) go func() { for { 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) return nil }, } rootCmd.Flags().String("vendor", "0x0fcf", "USB vendor ID (hex, e.g. 0x0fcf)") rootCmd.Flags().String("product", "0x1008", "USB product ID (hex, e.g. 0x1008)") rootCmd.Flags().String("device", "", "ANT+ device number (hex or decimal). Use 0 or omit to listen to all devices") if err := rootCmd.Execute(); err != nil { os.Exit(1) } }