Skip to content
Open
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
27 changes: 27 additions & 0 deletions gap_darwin.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bluetooth

import (
"context"
"errors"
"fmt"
"time"
Expand Down Expand Up @@ -38,6 +39,12 @@ func (ad *Address) Set(val string) {
// Scan starts a BLE scan. It is stopped by a call to StopScan. A common pattern
// is to cancel the scan when a particular device has been found.
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
return a.ScanWithContext(context.Background(), callback)
}

// ScanWithContext starts a BLE scan. It is stopped by a call to StopScan. A common pattern
// is to cancel the scan when a particular device has been found.
func (a *Adapter) ScanWithContext(ctx context.Context, callback func(*Adapter, ScanResult)) (err error) {
if callback == nil {
return errors.New("must provide callback to Scan function")
}
Expand All @@ -62,6 +69,11 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
// the callback calls StopScan() (no new callbacks may be called after
// StopScan is called).
select {
case <-ctx.Done():
// StopScan can return an error, but we ignore it here since
// it only returns an error if no scan is in progress.
_ = a.StopScan()
return ctx.Err()
case <-a.scanChan:
close(a.scanChan)
a.scanChan = nil
Expand Down Expand Up @@ -104,6 +116,11 @@ type deviceInternal struct {

// Connect starts a connection attempt to the given peripheral device address.
func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
return a.ConnectWithContext(context.Background(), address, params)
}

// ConnectWithContext starts a connection attempt to the given peripheral device address.
func (a *Adapter) ConnectWithContext(ctx context.Context, address Address, params ConnectionParams) (Device, error) {
uuid, err := cbgo.ParseUUID(address.UUID.String())
if err != nil {
return Device{}, err
Expand Down Expand Up @@ -162,6 +179,16 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, err
// record an error to use when the disconnect comes through later.
connectionError = errors.New("timeout on Connect")

// we are not ready to return yet, we need to wait for the disconnect event to come through
// so continue on from this case and wait for something to show up on prphCh
continue
case <-ctx.Done():
// we need to cancel the connection if the context is done
a.cm.CancelConnect(prphs[0])

// record an error to use when the disconnect comes through later.
connectionError = ctx.Err()
Copy link
Author

Choose a reason for hiding this comment

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

This only difference is the error retrieval, here. Maybe we can find a "clever" way to not duplicate code.


// we are not ready to return yet, we need to wait for the disconnect event to come through
// so continue on from this case and wait for something to show up on prphCh
continue
Expand Down
60 changes: 52 additions & 8 deletions gattc_darwin.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bluetooth

import (
"context"
"errors"
"time"

Expand All @@ -15,6 +16,19 @@ import (
// Passing a nil slice of UUIDs will return a complete list of
// services.
func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
ctx, cancel := context.WithTimeoutCause(context.Background(), 10*time.Second, errors.New("timeout on DiscoverServices"))
defer cancel()
return d.DiscoverServicesWithContext(ctx, uuids)
}

// DiscoverServicesWithContext starts a service discovery procedure. Pass a list of service
// 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) DiscoverServicesWithContext(ctx context.Context, uuids []UUID) ([]DeviceService, error) {
d.prph.DiscoverServices([]cbgo.UUID{})

// clear cache of services
Expand Down Expand Up @@ -52,8 +66,8 @@ func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
d.services[svc.uuidWrapper] = svc
}
return svcs, nil
case <-time.NewTimer(10 * time.Second).C:
return nil, errors.New("timeout on DiscoverServices")
case <-ctx.Done():
return nil, ctx.Err()
}
}

Expand Down Expand Up @@ -90,6 +104,21 @@ func (s DeviceService) UUID() UUID {
// Passing a nil slice of UUIDs will return a complete list of
// characteristics.
func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteristic, error) {
ctx, cancel := context.WithTimeoutCause(context.Background(), 10*time.Second, errors.New("timeout on DiscoverCharacteristics"))
defer cancel()
return s.DiscoverCharacteristicsWithContext(ctx, uuids)
}

// DiscoverCharacteristicsWithContext discovers characteristics in this service. Pass a
// list of characteristic UUIDs you are interested in to this function. Either a
// list of all requested services is returned, or if some services could not be
// 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) DiscoverCharacteristicsWithContext(ctx context.Context, uuids []UUID) ([]DeviceCharacteristic, error) {
cbuuids := []cbgo.UUID{}

s.device.prph.DiscoverCharacteristics(cbuuids, s.service)
Expand Down Expand Up @@ -136,8 +165,8 @@ func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteri
}
}
return chars, nil
case <-time.NewTimer(10 * time.Second).C:
return nil, errors.New("timeout on DiscoverCharacteristics")
case <-ctx.Done():
return nil, ctx.Err()
}
}

Expand Down Expand Up @@ -179,13 +208,21 @@ func (c DeviceCharacteristic) UUID() UUID {
// Write replaces the characteristic value with a new value. The
// call will return after all data has been written.
func (c DeviceCharacteristic) Write(p []byte) (n int, err error) {
ctx, cancel := context.WithTimeoutCause(context.Background(), 10*time.Second, errors.New("timeout on Write()"))
defer cancel()
return c.WriteWithContext(ctx, p)
}

// WriteWithContext replaces the characteristic value with a new value. The
// call will return after all data has been written.
func (c DeviceCharacteristic) WriteWithContext(ctx context.Context, p []byte) (n int, err error) {
c.writeChan = make(chan error)
c.service.device.prph.WriteCharacteristic(p, c.characteristic, true)

// wait for result
select {
case <-time.NewTimer(10 * time.Second).C:
err = errors.New("timeout on Write()")
case <-ctx.Done():
err = ctx.Err()
case err = <-c.writeChan:
}

Expand Down Expand Up @@ -229,6 +266,13 @@ func (c DeviceCharacteristic) GetMTU() (uint16, error) {

// Read reads the current characteristic value.
func (c *deviceCharacteristic) Read(data []byte) (n int, err error) {
ctx, cancel := context.WithTimeoutCause(context.Background(), 10*time.Second, errors.New("timeout on Read()"))
defer cancel()
return c.ReadWithContext(ctx, data)
}

// ReadWithContext reads the current characteristic value.
func (c *deviceCharacteristic) ReadWithContext(ctx context.Context, data []byte) (n int, err error) {
c.readChan = make(chan error)
c.service.device.prph.ReadCharacteristic(c.characteristic)

Expand All @@ -239,9 +283,9 @@ func (c *deviceCharacteristic) Read(data []byte) (n int, err error) {
if err != nil {
return 0, err
}
case <-time.NewTimer(10 * time.Second).C:
case <-ctx.Done():
c.readChan = nil
return 0, errors.New("timeout on Read()")
return 0, ctx.Err()
}

copy(data, c.characteristic.Value())
Expand Down
Loading