diff --git a/gap_darwin.go b/gap_darwin.go index 542535c..fc1c143 100644 --- a/gap_darwin.go +++ b/gap_darwin.go @@ -1,6 +1,7 @@ package bluetooth import ( + "context" "errors" "fmt" "time" @@ -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") } @@ -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 @@ -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 @@ -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() + // 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 diff --git a/gattc_darwin.go b/gattc_darwin.go index 2d737da..ca9ff57 100644 --- a/gattc_darwin.go +++ b/gattc_darwin.go @@ -1,6 +1,7 @@ package bluetooth import ( + "context" "errors" "time" @@ -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 @@ -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() } } @@ -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) @@ -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() } } @@ -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: } @@ -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) @@ -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())