| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package nmble |
| |
| import ( |
| "sync" |
| "time" |
| |
| log "github.com/Sirupsen/logrus" |
| |
| . "mynewt.apache.org/newtmgr/nmxact/bledefs" |
| "mynewt.apache.org/newtmgr/nmxact/nmxutil" |
| ) |
| |
| type discovererState int |
| |
| const ( |
| DISCOVERER_STATE_IDLE discovererState = iota |
| DISCOVERER_STATE_STARTED |
| DISCOVERER_STATE_STOPPING |
| DISCOVERER_STATE_STOPPED |
| ) |
| |
| type DiscovererParams struct { |
| Bx *BleXport |
| OwnAddrType BleAddrType |
| Passive bool |
| Duration time.Duration |
| } |
| |
| // Listens for advertisements; reports the ones that match the specified |
| // predicate. This type is not thread-safe. |
| type Discoverer struct { |
| params DiscovererParams |
| bl *Listener |
| state discovererState |
| stopChan chan struct{} |
| wg sync.WaitGroup |
| mtx sync.Mutex |
| } |
| |
| func NewDiscoverer(params DiscovererParams) *Discoverer { |
| return &Discoverer{ |
| params: params, |
| } |
| } |
| |
| func (d *Discoverer) setState(state discovererState) { |
| d.state = state |
| } |
| |
| func (d *Discoverer) scanCancel() error { |
| r := NewBleScanCancelReq() |
| |
| bl, err := d.params.Bx.AddListener(SeqKey(r.Seq)) |
| if err != nil { |
| return err |
| } |
| defer d.params.Bx.RemoveListener(bl) |
| |
| if err := scanCancel(d.params.Bx, bl, r); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (d *Discoverer) Start() (<-chan BleAdvReport, <-chan error, error) { |
| d.mtx.Lock() |
| defer d.mtx.Unlock() |
| |
| if d.state != DISCOVERER_STATE_IDLE { |
| return nil, nil, nmxutil.NewAlreadyError( |
| "Attempt to start BLE discoverer twice") |
| } |
| |
| d.stopChan = make(chan struct{}) |
| |
| r := NewBleScanReq() |
| r.OwnAddrType = d.params.OwnAddrType |
| r.DurationMs = int(d.params.Duration / time.Millisecond) |
| r.FilterPolicy = BLE_SCAN_FILT_NO_WL |
| r.Limited = false |
| r.Passive = d.params.Passive |
| r.FilterDuplicates = true |
| |
| bl, err := d.params.Bx.AddListener(SeqKey(r.Seq)) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| ach, ech, err := actScan(d.params.Bx, bl, r) |
| if err != nil { |
| d.params.Bx.RemoveListener(bl) |
| return nil, nil, err |
| } |
| |
| // A buffer size of 1 is used so that the receiving Goroutine can stop the |
| // discoverer without triggering a deadlock. |
| parentEch := make(chan error, 1) |
| |
| d.wg.Add(1) |
| go func() { |
| defer d.wg.Done() |
| |
| stopChan := d.stopChan |
| |
| // Block until scan completes. |
| var done bool |
| var err error |
| for !done { |
| select { |
| case err = <-ech: |
| done = true |
| |
| case <-stopChan: |
| // Discovery aborted; remove the BLE listener. This will cause |
| // the scan to fail, triggering a send on the ech channel. |
| if bl != nil { |
| d.params.Bx.RemoveListener(bl) |
| bl = nil |
| stopChan = nil |
| } |
| } |
| } |
| |
| d.mtx.Lock() |
| defer d.mtx.Unlock() |
| |
| // Always attempt to cancel scanning. No harm done if scanning is |
| // already stopped. |
| if !nmxutil.IsXport(err) { |
| if err := d.scanCancel(); err != nil { |
| log.Debugf("Failed to cancel scan in progress: %s", |
| err.Error()) |
| } |
| } |
| |
| if bl != nil { |
| d.params.Bx.RemoveListener(bl) |
| } |
| |
| d.setState(DISCOVERER_STATE_IDLE) |
| |
| parentEch <- err |
| close(parentEch) |
| }() |
| |
| d.setState(DISCOVERER_STATE_STARTED) |
| |
| return ach, parentEch, nil |
| } |
| |
| // Ensures the discoverer is stopped. Errors can typically be ignored. |
| func (d *Discoverer) Stop() error { |
| initiate := func() error { |
| d.mtx.Lock() |
| defer d.mtx.Unlock() |
| |
| if d.state != DISCOVERER_STATE_STARTED { |
| return nmxutil.NewAlreadyError( |
| "Attempt to stop inactive discoverer") |
| } |
| |
| d.setState(DISCOVERER_STATE_STOPPING) |
| close(d.stopChan) |
| return nil |
| } |
| |
| if err := initiate(); err != nil { |
| return err |
| } |
| |
| // Don't return until discovery is fully stopped. |
| d.wg.Wait() |
| |
| d.mtx.Lock() |
| d.setState(DISCOVERER_STATE_IDLE) |
| d.mtx.Unlock() |
| |
| return nil |
| } |
| |
| // Discovers a single device. After a device is successfully discovered, |
| // discovery is stopped. |
| func DiscoverDevice( |
| bx *BleXport, |
| ownAddrType BleAddrType, |
| duration time.Duration, |
| advPred BleAdvPredicate) (*BleDev, error) { |
| |
| d := NewDiscoverer(DiscovererParams{ |
| Bx: bx, |
| OwnAddrType: ownAddrType, |
| Passive: false, |
| Duration: duration, |
| }) |
| |
| ach, ech, err := d.Start() |
| if err != nil { |
| if nmxutil.IsScanTmo(err) { |
| return nil, nil |
| } else { |
| return nil, err |
| } |
| } |
| |
| for { |
| select { |
| case adv, ok := <-ach: |
| if ok && advPred(adv) { |
| d.Stop() |
| return &adv.Sender, nil |
| } |
| |
| case err := <-ech: |
| return nil, err |
| } |
| } |
| } |