diff --git a/cmd/bridge/bluetooth.go b/cmd/bridge/bluetooth.go index 06ab7d0..2cdc95d 100644 --- a/cmd/bridge/bluetooth.go +++ b/cmd/bridge/bluetooth.go @@ -1 +1,83 @@ package main + +import ( + "encoding/binary" + "fmt" + "math" + "time" + + "tinygo.org/x/bluetooth" +) + +var adapter = bluetooth.DefaultAdapter + +func must(err error) { + if err != nil { + panic(err) + } +} + +func ExposeBluetooth() { + must(adapter.Enable()) + + var powerChar bluetooth.Characteristic + + must(adapter.AddService(&bluetooth.Service{ + UUID: bluetooth.New16BitUUID(0x1818), + Characteristics: []bluetooth.CharacteristicConfig{ + { + UUID: bluetooth.New16BitUUID(0x2A65), // Cycling Power Feature + Flags: bluetooth.CharacteristicReadPermission, + Value: []byte{0, 0, 0, 0}, + }, + { + UUID: bluetooth.New16BitUUID(0x2A63), // Cycling Power Measurement + Flags: bluetooth.CharacteristicNotifyPermission, + Handle: &powerChar, + }, + }, + })) + + must(adapter.DefaultAdvertisement().Configure(bluetooth.AdvertisementOptions{ + LocalName: "LinuxAntBtBridge", + ServiceUUIDs: []bluetooth.UUID{bluetooth.New16BitUUID(0x1818)}, + })) + must(adapter.DefaultAdvertisement().Start()) + fmt.Println("BLE advertising...") + + var totalRevs float64 + var lastCompletedRev float64 // time in 1/1024s of last completed revolution + lastUpdate := time.Now() + var elapsedTicks float64 + for { + now := time.Now() + deltaT := now.Sub(lastUpdate).Seconds() + lastUpdate = now + + sec := now.Second() + watts := uint16(100 + sec) + rpm := float64(50 + sec) + + totalRevs += rpm / 60.0 * deltaT + elapsedTicks += deltaT * 1024.0 + + completedRevs := math.Floor(totalRevs) + // time at which the last completed revolution occurred + // = current time - fractional part of revolution * time per revolution + fracRev := totalRevs - completedRevs + timePerRev := 1024.0 * 60.0 / rpm + lastCompletedRev = elapsedTicks - fracRev*timePerRev + + payload := make([]byte, 8) + binary.LittleEndian.PutUint16(payload[0:], 0x20) + binary.LittleEndian.PutUint16(payload[2:], watts) + binary.LittleEndian.PutUint16(payload[4:], uint16(completedRevs)) + binary.LittleEndian.PutUint16(payload[6:], uint16(lastCompletedRev)) + + powerChar.Write(payload) + fmt.Printf("watts: %d rpm: %.0f completedRevs: %.0f lastRevTime: %.0f\n", + watts, rpm, completedRevs, lastCompletedRev) + + time.Sleep(250 * time.Millisecond) + } +} diff --git a/cmd/bridge/main.go b/cmd/bridge/main.go index 466e455..ccc5421 100644 --- a/cmd/bridge/main.go +++ b/cmd/bridge/main.go @@ -1,14 +1,10 @@ package main import ( - "encoding/binary" "fmt" - "math" "os/exec" - "time" "github.com/google/gousb" - "tinygo.org/x/bluetooth" ) func oldMain() { @@ -22,78 +18,9 @@ func oldMain() { fmt.Println("channel closed") } -var adapter = bluetooth.DefaultAdapter - func main() { exec.Command("hciconfig", "hci0", "down").Run() exec.Command("hciconfig", "hci0", "up").Run() - must(adapter.Enable()) - - var powerChar bluetooth.Characteristic - - must(adapter.AddService(&bluetooth.Service{ - UUID: bluetooth.New16BitUUID(0x1818), - Characteristics: []bluetooth.CharacteristicConfig{ - { - UUID: bluetooth.New16BitUUID(0x2A65), // Cycling Power Feature - Flags: bluetooth.CharacteristicReadPermission, - Value: []byte{0, 0, 0, 0}, - }, - { - UUID: bluetooth.New16BitUUID(0x2A63), // Cycling Power Measurement - Flags: bluetooth.CharacteristicNotifyPermission, - Handle: &powerChar, - }, - }, - })) - - must(adapter.DefaultAdvertisement().Configure(bluetooth.AdvertisementOptions{ - LocalName: "LinuxAntBtBridge", - ServiceUUIDs: []bluetooth.UUID{bluetooth.New16BitUUID(0x1818)}, - })) - must(adapter.DefaultAdvertisement().Start()) - fmt.Println("BLE advertising...") - - var totalRevs float64 - var lastCompletedRev float64 // time in 1/1024s of last completed revolution - lastUpdate := time.Now() - var elapsedTicks float64 - for { - now := time.Now() - deltaT := now.Sub(lastUpdate).Seconds() - lastUpdate = now - - sec := now.Second() - watts := uint16(100 + sec) - rpm := float64(50 + sec) - - totalRevs += rpm / 60.0 * deltaT - elapsedTicks += deltaT * 1024.0 - - completedRevs := math.Floor(totalRevs) - // time at which the last completed revolution occurred - // = current time - fractional part of revolution * time per revolution - fracRev := totalRevs - completedRevs - timePerRev := 1024.0 * 60.0 / rpm - lastCompletedRev = elapsedTicks - fracRev*timePerRev - - payload := make([]byte, 8) - binary.LittleEndian.PutUint16(payload[0:], 0x20) - binary.LittleEndian.PutUint16(payload[2:], watts) - binary.LittleEndian.PutUint16(payload[4:], uint16(completedRevs)) - binary.LittleEndian.PutUint16(payload[6:], uint16(lastCompletedRev)) - - powerChar.Write(payload) - fmt.Printf("watts: %d rpm: %.0f completedRevs: %.0f lastRevTime: %.0f\n", - watts, rpm, completedRevs, lastCompletedRev) - - time.Sleep(250 * time.Millisecond) - } -} - -func must(err error) { - if err != nil { - panic(err) - } + ExposeBluetooth() }