-
Notifications
You must be signed in to change notification settings - Fork 163
Description
Description
I'm trying to create a program to read power from a cycling trainer. I'm able to successfully connect to my bike trainer from Mac devices, but not from Windows devices. They are discoverable in scans, but their service id and characteristics seem to be unreadable. In particular
advPayload.HasServiceUUID(bluetooth.New16BitUUID(bluetooth.ServiceUUIDCyclingPower.Get16Bit()))
Seems to be evaluating to true
for Mac but false
for Windows.
Furthermore, if I try to connect directly to the trainer despite not being able to evaluate the relevant service ids, it will appear as though it connects but fail to notify for changes in the bike trainer power characteristics.
Below is the code I'm using to connect to the trainers:
Trainer connection code
// Some abstractions for my specific use case
type DiscoveredBluetoothDevice struct {
DeviceType DeviceType
Address bluetooth.Address
Channel chan bluetooth.ScanResult
}
// NewDiscoveredDevice creates a new DiscoveredBluetoothDevice from a bluetooth.ScanResult
func NewDiscoveredDevice(result bluetooth.ScanResult) *DiscoveredBluetoothDevice {
log.Printf("Device name: %s\n", result.LocalName())
newDevice := &DiscoveredBluetoothDevice{
DeviceType: getDeviceType(result.AdvertisementPayload),
Address: result.Address,
Channel: make(chan bluetooth.ScanResult, 1),
}
newDevice.Channel <- result
return newDevice
}
func getDeviceType(advPayload bluetooth.AdvertisementPayload) DeviceType {
switch {
case advPayload.HasServiceUUID(bluetooth.New16BitUUID(DeviceUUIDs[cyclingPowerService].Get16Bit())):
log.Printf("Found 16 bit UUID")
return cyclingPowerService
case advPayload.HasServiceUUID(bluetooth.New32BitUUID(DeviceUUIDs[cyclingPowerService].Get32Bit())):
log.Printf("Found 32 bit UUID")
return cyclingPowerService
case advPayload.HasServiceUUID(bluetooth.New16BitUUID(DeviceUUIDs[fitnessMachineService].Get16Bit())):
log.Printf("Found 16 bit UUID")
return fitnessMachineService
case advPayload.HasServiceUUID(bluetooth.New32BitUUID(DeviceUUIDs[fitnessMachineService].Get32Bit())):
log.Printf("Found 32 bit UUID")
return fitnessMachineService
}
return unknownDeviceType
}
// ScanForDevices tries to find smart trainers
func ScanForDevices(btctrl *BluetoothController) error {
log.Println("Scanning for Smart Trainers")
adapter := btctrl.Adapter
err := adapter.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) {
go func(result bluetooth.ScanResult) {
btctrl.mu.Lock()
defer btctrl.mu.Unlock()
if !btctrl.Contains(result) {
if result.LocalName() != "" {
newDiscoveredDevice := NewDiscoveredDevice(result)
btctrl.AvailableDevices[result.LocalName()] = newDiscoveredDevice
if newDiscoveredDevice.DeviceType != unknownDeviceType {
log.Printf("Discovered new %s\n\tName: %s\n\tAddress: %v\n\t", newDiscoveredDevice.DeviceType.String(), result.LocalName(), result.Address)
} else {
log.Printf("Discovered new non-smart trainer device\n\tName: %s\n\tAddress: %v\n\t", result.LocalName(), result.Address)
}
}
}
}(result)
})
if err != nil {
return err
}
log.Printf("Found %d devices\n", len(btctrl.AvailableDevices))
for i := range btctrl.AvailableDevices {
log.Printf("Device: %v\n", btctrl.AvailableDevices[i])
}
log.Println("\nTrainers found:")
for _, smartTrainer := range btctrl.GetSmartTrainerNames() {
log.Printf("Device: %s", smartTrainer)
}
return nil
}
// ConnectToSmartTrainerDevice is used to connect to a bluetooth device by name
func ConnectToSmartTrainerDevice(btctrl *BluetoothController, deviceName string) error {
log.Printf("Connecting to %s\n", deviceName)
selectedDevice := btctrl.AvailableDevices[deviceName]
if selectedDevice == nil {
return fmt.Errorf("device not found")
}
var connectedDevice bluetooth.Device
var err error
select {
case result := <-selectedDevice.Channel:
connectedDevice, err = btctrl.Adapter.Connect(result.Address, bluetooth.ConnectionParams{})
if err != nil {
return fmt.Errorf("failed to connect to device: %w", err)
}
}
// get services
log.Println("Discover services/characteristics")
services, err := connectedDevice.DiscoverServices([]bluetooth.UUID{DeviceUUIDs[cyclingPowerService], DeviceUUIDs[fitnessMachineService]})
if err != nil {
log.Printf("Error connecting to service: %v", err)
// Get the services that the device supports
services, err = connectedDevice.DiscoverServices(nil)
if err != nil {
log.Printf("Error discovering services: %v", err)
}
log.Printf("Services: %v", services)
return err
}
if len(services) == 0 {
return fmt.Errorf("no Smart Trainer found")
}
service := services[0]
log.Printf("Found service: %s", service.UUID().String())
chars, err := service.DiscoverCharacteristics([]bluetooth.UUID{CharacteristicUUIDs[CyclingPowerCharacteristic]})
if err != nil {
return err
}
if len(chars) == 0 {
return fmt.Errorf("no Smart Trainer characteristics found")
}
log.Printf("Found characteristics: %v", chars)
char := chars[0]
log.Printf("Found characteristic: %s", char.UUID().String())
log.Printf("Characteristic ID Type: %d", getCharIDType(char))
char.EnableNotifications(func(buf []byte) {
power := uint8(buf[2])
btctrl.Characteristics[CyclingPowerCharacteristic] = power
})
return nil
}
I may have missed a few small abstractions in this, but I think its enough to understand the code
If this is a limitation of what currently exists in the winrt-go implementation, I'm happy to contribute back to it to enable this if you can point me in the right direction.
Let me know what else you need from me. Thanks in advance!