Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions examples/discover/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"os"

"github.com/tinygo-org/bluetooth"
)

var adapter = bluetooth.DefaultAdapter

func main() {
if len(os.Args) < 2 {
println("usage: discover [local name]")
os.Exit(1)
}

// look for device with specific name
name := os.Args[1]

// Enable BLE interface.
must("enable BLE stack", adapter.Enable())

ch := make(chan bluetooth.ScanResult, 1)

// Start scanning.
println("scanning...")
err := adapter.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) {
println("found device:", result.Address.String(), result.RSSI, result.LocalName())
if result.LocalName() == name {
adapter.StopScan()
ch <- result
}
})

var device *bluetooth.Device
select {
case result := <-ch:
device, err = adapter.Connect(result.Address, bluetooth.ConnectionParams{})
if err != nil {
println(err.Error())
os.Exit(1)
}

println("connected to ", result.LocalName())
}

// get services
println("discovering services/characteristics")
srvcs, err := device.DiscoverServices(nil)
for _, srvc := range srvcs {
println("- service", srvc.UUID.String())

chars, _ := srvc.DiscoverCharacteristics(nil)
for _, char := range chars {
println("-- characteristic", char.UUID.String())
}
}

must("start scan", err)
}

func must(action string, err error) {
if err != nil {
panic("failed to " + action + ": " + err.Error())
}
}
2 changes: 1 addition & 1 deletion gap_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func makeScanResult(props *device.Device1Properties) ScanResult {
serviceUUIDs = append(serviceUUIDs, parsedUUID)
}

a := &Address{MACAddress{MAC: addr}}
a := Address{MACAddress{MAC: addr}}
a.SetRandom(props.AddressType == "random")

return ScanResult{
Expand Down
6 changes: 6 additions & 0 deletions gattc_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
// UUIDs you are interested in to this function. Either a slice of all services
// is returned (of the same length as the requested UUIDs and in the same
// order), or if some services could not be discovered an error is returned.
//
// Passing a nil slice of UUIDs will return a complete list of
// services.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
cbuuids := []cbgo.UUID{}
for _, u := range uuids {
Expand Down Expand Up @@ -58,6 +61,9 @@ type DeviceService struct {
// discovered an error is returned. If there is no error, the characteristics
// slice has the same length as the UUID slice with characteristics in the same
// order in the slice as in the requested UUID list.
//
// Passing a nil slice of UUIDs will return a complete list of
// characteristics.
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
cbuuids := []cbgo.UUID{}
for _, u := range uuids {
Expand Down
90 changes: 62 additions & 28 deletions gattc_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (

// DeviceService is a BLE service on a connected peripheral device.
type DeviceService struct {
UUID

service *gatt.GattService1
}

Expand All @@ -21,8 +23,7 @@ type DeviceService struct {
// is returned (of the same length as the requested UUIDs and in the same
// order), or if some services could not be discovered an error is returned.
//
// Passing a nil slice of UUIDs will currently result in zero services being
// returned, but this may be changed in the future to return a complete list of
// Passing a nil slice of UUIDs will return a complete list of
// services.
//
// On Linux with BlueZ, this just waits for the ServicesResolved signal (if
Expand All @@ -40,7 +41,8 @@ func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
time.Sleep(10 * time.Millisecond)
}

services := make([]DeviceService, len(uuids))
services := []DeviceService{}
uuidServices := make(map[string]string)
servicesFound := 0

// Iterate through all objects managed by BlueZ, hoping to find the services
Expand All @@ -65,23 +67,38 @@ func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
if err != nil {
return nil, err
}
for i, uuid := range uuids {
if service.Properties.UUID != uuid.String() {
// Not one of the services we're looking for.
continue

if len(uuids) > 0 {
found := false
for _, uuid := range uuids {
if service.Properties.UUID == uuid.String() {
// One of the services we're looking for.
found = true
break
}
}
if services[i].service != nil {
// There is more than one service with the same UUID?
// Don't overwrite it, to keep the servicesFound count correct.
if !found {
continue
}
services[i].service = service
servicesFound++
break
}

if _, ok := uuidServices[service.Properties.UUID]; ok {
// There is more than one service with the same UUID?
// Don't overwrite it, to keep the servicesFound count correct.
continue
}

uuid, _ := ParseUUID(service.Properties.UUID)
ds := DeviceService{UUID: uuid,
service: service,
}

services = append(services, ds)
servicesFound++
uuidServices[service.Properties.UUID] = service.Properties.UUID
}

if servicesFound != len(uuids) {
if servicesFound <= len(uuids) {
return nil, errors.New("bluetooth: could not find some services")
}

Expand All @@ -91,6 +108,8 @@ func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
// DeviceCharacteristic is a BLE characteristic on a connected peripheral
// device.
type DeviceCharacteristic struct {
UUID
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't catch this with the darwin support, but I wonder whether this would perhaps be better implemented as a getter?
The reason is that the SoftDevice has a special format for UUIDs that takes up just 3 or 4 bytes instead of the usual 16, and it would thus be possible to support them with less overhead (because many applications will just be looking for specific UUIDs and thus won't need to read the UUID field).

This could also be done in a separate PR, but seeing that the example heavily relies on UUIDs it may be best to do that in this PR.

(The same argument goes for DeviceService.UUID).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is a good idea, but perhaps best done in another PR.


characteristic *gatt.GattCharacteristic1
}

Expand All @@ -101,11 +120,11 @@ type DeviceCharacteristic struct {
// slice has the same length as the UUID slice with characteristics in the same
// order in the slice as in the requested UUID list.
//
// Passing a nil slice of UUIDs will currently result in zero characteristics
// being returned, but this may be changed in the future to return a complete
// Passing a nil slice of UUIDs will return a complete
// list of characteristics.
func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
chars := make([]DeviceCharacteristic, len(uuids))
chars := []DeviceCharacteristic{}
uuidChars := make(map[string]string)
characteristicsFound := 0

// Iterate through all objects managed by BlueZ, hoping to find the
Expand All @@ -130,23 +149,38 @@ func (s *DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacter
if err != nil {
return nil, err
}
for i, uuid := range uuids {
if char.Properties.UUID != uuid.String() {
// Not one of the characteristics we're looking for.
continue

if len(uuids) > 0 {
found := false
for _, uuid := range uuids {
if char.Properties.UUID == uuid.String() {
// One of the services we're looking for.
found = true
break
}
}
if chars[i].characteristic != nil {
// There is more than one characteristic with the same UUID?
// Don't overwrite it, to keep the servicesFound count correct.
if !found {
continue
}
chars[i].characteristic = char
characteristicsFound++
break
}

if _, ok := uuidChars[char.Properties.UUID]; ok {
// There is more than one characteristic with the same UUID?
// Don't overwrite it, to keep the servicesFound count correct.
continue
}

uuid, _ := ParseUUID(char.Properties.UUID)
dc := DeviceCharacteristic{UUID: uuid,
characteristic: char,
}

chars = append(chars, dc)
characteristicsFound++
uuidChars[char.Properties.UUID] = char.Properties.UUID
}

if characteristicsFound != len(uuids) {
if characteristicsFound < len(uuids) {
return nil, errors.New("bluetooth: could not find some characteristics")
}

Expand Down