Skip to content

Windows: AdvertisementPayload.HasServiceUUID does not recognize ServiceUUIDCyclingPower on Windows but does on Mac #362

@jtmcg

Description

@jtmcg

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions