package main import ( "encoding/binary" "math" "time" "tinygo.org/x/bluetooth" ) const ( bleCyclingPowerServiceUUID = 0x1818 // BLE Cycling Power Service UUID bleCyclingPowerFeatureCharacteristic = 0x2A65 // BLE Cycling Power Feature characteristic bleCyclingPowerMeasurementCharacteristic = 0x2A63 // BLE Cycling Power Measurement characteristic bleSignalDetectedFlag = 0x20 // Signal Detected bit in BLE payload header ) var adapter = bluetooth.DefaultAdapter func must(err error) { if err != nil { panic(err) } } func ExposeBluetooth(events <-chan Event) { must(adapter.Enable()) var powerChar bluetooth.Characteristic must(adapter.AddService(&bluetooth.Service{ UUID: bluetooth.New16BitUUID(bleCyclingPowerServiceUUID), Characteristics: []bluetooth.CharacteristicConfig{ { UUID: bluetooth.New16BitUUID(bleCyclingPowerFeatureCharacteristic), Flags: bluetooth.CharacteristicReadPermission, Value: []byte{0, 0, 0, 0}, }, { UUID: bluetooth.New16BitUUID(bleCyclingPowerMeasurementCharacteristic), Flags: bluetooth.CharacteristicNotifyPermission, Handle: &powerChar, }, }, })) must(adapter.DefaultAdvertisement().Configure(bluetooth.AdvertisementOptions{ LocalName: "LinuxAntBtBridge", ServiceUUIDs: []bluetooth.UUID{bluetooth.New16BitUUID(bleCyclingPowerServiceUUID)}, })) must(adapter.DefaultAdvertisement().Start()) logMsg("BLE advertising...") var totalRevs float64 var lastCompletedRev float64 // time in 1/1024s of last completed revolution lastUpdate := time.Now() var elapsedTicks float64 for event := range events { now := time.Now() deltaT := now.Sub(lastUpdate).Seconds() lastUpdate = now watts := event.Power rpm := float64(event.Cadence) 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:], bleSignalDetectedFlag) binary.LittleEndian.PutUint16(payload[2:], watts) binary.LittleEndian.PutUint16(payload[4:], uint16(completedRevs)) binary.LittleEndian.PutUint16(payload[6:], uint16(lastCompletedRev)) if _, err := powerChar.Write(payload); err != nil { logPrintf("bt: failed to write power characteristic: %v\n", err) } logPrintf("bt: watts: %d rpm: %.0f completedRevs: %.0f lastRevTime: %.0f processingTime: %v\n", watts, rpm, completedRevs, lastCompletedRev, time.Now().Sub(now)) } logMsg("BLE advertising stopped") }