feat: add CLI with Cobra and timestamped logging
Introduce structured CLI argument parsing using Cobra with three configurable flags: --vendor: USB vendor ID (default: 0x0fcf) --product: USB product ID (default: 0x1008) --device: ANT+ device number (default: 3001) Add timestamped logging for all log statements with format YYYY-MM-DD HH:MM:SS. Log vendor, product, and device values at startup in both hex and decimal. Remove hardcoded USB identifiers from main.go and consolidate all startup logic into cli.go with a dedicated Config struct. Update ant.go ListenForTrainer signature to accept uint16 for vendor and product IDs.
This commit is contained in:
parent
39b5a95eb4
commit
b24513fcc3
2
Makefile
2
Makefile
@ -15,7 +15,7 @@ vet: fmt
|
|||||||
|
|
||||||
build: vet
|
build: vet
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
go build -ldflags "-X main.Version=$(shell git rev-parse HEAD ) -X main.BuildTime=$(shell date +'%Y-%m-%dT%H:%M:%S')" -o bin ./cmd/...
|
go build -ldflags "-X main.Version=$(shell git rev-parse HEAD ) -X main.BuildTime=$(shell date +'%Y-%m-%dT%H:%M:%S')" -o bin/bridge ./cmd/...
|
||||||
|
|
||||||
install: build
|
install: build
|
||||||
go install ./...
|
go install ./...
|
||||||
|
|||||||
@ -26,9 +26,9 @@ func resetAndWait(drv io.ReadWriter) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenForTrainer(vendorId gousb.ID, productId gousb.ID, deviceNumber uint32,
|
func ListenForTrainer(vendorId uint16, productId uint16, deviceNumber uint32,
|
||||||
events chan<- Event) {
|
events chan<- Event) {
|
||||||
drv, err := usb.GetDevice(vendorId, productId)
|
drv, err := usb.GetDevice(gousb.ID(vendorId), gousb.ID(productId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
89
cmd/bridge/cli.go
Normal file
89
cmd/bridge/cli.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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)
|
||||||
|
}
|
||||||
|
deviceNumber, err := parseHex(c.device, 32)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid --device %q: %w", c.device, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.vendorID = vendorID
|
||||||
|
c.productID = productID
|
||||||
|
c.deviceNumber = uint32(deviceNumber)
|
||||||
|
|
||||||
|
logMsgf("ANT Bridge %s built %s", Version, BuildTime)
|
||||||
|
logMsgf("vendor: 0x%04x/%d", vendorID, vendorID)
|
||||||
|
logMsgf("product: 0x%04x/%d", productID, productID)
|
||||||
|
logMsgf("device: 0x%03x/%d", deviceNumber, deviceNumber)
|
||||||
|
|
||||||
|
exec.Command("hciconfig", "hci0", "down").Run()
|
||||||
|
exec.Command("hciconfig", "hci0", "up").Run()
|
||||||
|
|
||||||
|
events := make(chan Event)
|
||||||
|
go ListenForTrainer(uint16(c.vendorID), uint16(c.productID), c.deviceNumber, events)
|
||||||
|
ExposeBluetooth(events)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
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", "3001", "ANT+ device number (hex or decimal, e.g. 3001 or 0xbb9)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
cmd/bridge/log.go
Normal file
18
cmd/bridge/log.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func logMsg(msg string) {
|
||||||
|
fmt.Println(time.Now().Format("2006-01-02 15:04:05") + " " + msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logPrintf(format string, a ...any) {
|
||||||
|
fmt.Printf(time.Now().Format("2006-01-02 15:04:05")+" "+format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logMsgf(format string, a ...any) {
|
||||||
|
logPrintf(format+"\n", a...)
|
||||||
|
}
|
||||||
@ -1,20 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/google/gousb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
exec.Command("hciconfig", "hci0", "down").Run()
|
|
||||||
exec.Command("hciconfig", "hci0", "up").Run()
|
|
||||||
|
|
||||||
var vendorId gousb.ID = 0x0fcf
|
|
||||||
var productId gousb.ID = 0x1008
|
|
||||||
var deviceNumber uint32 = 3001
|
|
||||||
|
|
||||||
events := make(chan Event)
|
|
||||||
go ListenForTrainer(vendorId, productId, deviceNumber, events)
|
|
||||||
ExposeBluetooth(events)
|
|
||||||
}
|
|
||||||
6
go.mod
6
go.mod
@ -4,9 +4,15 @@ go 1.26.2
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/half2me/antgo v0.0.0-20240825222428-e6b9ddc9c32f
|
github.com/half2me/antgo v0.0.0-20240825222428-e6b9ddc9c32f
|
||||||
|
github.com/spf13/cobra v1.10.2
|
||||||
tinygo.org/x/bluetooth v0.15.0
|
tinygo.org/x/bluetooth v0.15.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.9 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
|
|||||||
9
go.sum
9
go.sum
@ -1,3 +1,4 @@
|
|||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -9,9 +10,12 @@ github.com/google/gousb v1.1.3 h1:xt6M5TDsGSZ+rlomz5Si5Hmd/Fvbmo2YCJHN+yGaK4o=
|
|||||||
github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg=
|
github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg=
|
||||||
github.com/half2me/antgo v0.0.0-20240825222428-e6b9ddc9c32f h1:T1EVnxPi3CqwmJdOs/s76vplnG8t0MEFmwo3bvHrZAo=
|
github.com/half2me/antgo v0.0.0-20240825222428-e6b9ddc9c32f h1:T1EVnxPi3CqwmJdOs/s76vplnG8t0MEFmwo3bvHrZAo=
|
||||||
github.com/half2me/antgo v0.0.0-20240825222428-e6b9ddc9c32f/go.mod h1:BXp/M+7//3V0GY2ytRJw4vcxZve8mQ0zRVuow+fANkE=
|
github.com/half2me/antgo v0.0.0-20240825222428-e6b9ddc9c32f/go.mod h1:BXp/M+7//3V0GY2ytRJw4vcxZve8mQ0zRVuow+fANkE=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/saltosystems/winrt-go v0.0.0-20260317170058-9c2fec580d96 h1:IXxzj3yjfDNXZJ35foY+RpFShqPsZZ81hhCckgfh5PI=
|
github.com/saltosystems/winrt-go v0.0.0-20260317170058-9c2fec580d96 h1:IXxzj3yjfDNXZJ35foY+RpFShqPsZZ81hhCckgfh5PI=
|
||||||
github.com/saltosystems/winrt-go v0.0.0-20260317170058-9c2fec580d96/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA=
|
github.com/saltosystems/winrt-go v0.0.0-20260317170058-9c2fec580d96/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA=
|
||||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||||
@ -23,6 +27,10 @@ github.com/soypat/lneto v0.1.0 h1:VAHCJ33hvC3wDqhM0Vm7w0k6vwNsOCAsQ8XTrXJpS7I=
|
|||||||
github.com/soypat/lneto v0.1.0/go.mod h1:g/8Lk+hIsMZydyWDJjK2YfsCuG6jA5mWCO6U+4S7w1U=
|
github.com/soypat/lneto v0.1.0/go.mod h1:g/8Lk+hIsMZydyWDJjK2YfsCuG6jA5mWCO6U+4S7w1U=
|
||||||
github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710 h1:Y9fBuiR/urFY/m76+SAZTxk2xAOS2n85f+H1CugajeA=
|
github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710 h1:Y9fBuiR/urFY/m76+SAZTxk2xAOS2n85f+H1CugajeA=
|
||||||
github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710/go.mod h1:oCVCNGCHMKoBj97Zp9znLbQ1nHxpkmOY9X+UAGzOxc8=
|
github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710/go.mod h1:oCVCNGCHMKoBj97Zp9znLbQ1nHxpkmOY9X+UAGzOxc8=
|
||||||
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
|
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||||
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
@ -32,6 +40,7 @@ github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU
|
|||||||
github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk=
|
github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk=
|
||||||
github.com/tinygo-org/pio v0.3.0 h1:opEnOtw58KGB4RJD3/n/Rd0/djYGX3DeJiXLI6y/yDI=
|
github.com/tinygo-org/pio v0.3.0 h1:opEnOtw58KGB4RJD3/n/Rd0/djYGX3DeJiXLI6y/yDI=
|
||||||
github.com/tinygo-org/pio v0.3.0/go.mod h1:wf6c6lKZp+pQOzKKcpzchmRuhiMc27ABRuo7KVnaMFU=
|
github.com/tinygo-org/pio v0.3.0/go.mod h1:wf6c6lKZp+pQOzKKcpzchmRuhiMc27ABRuo7KVnaMFU=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user