You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2017/05/16 21:54:15 UTC

incubator-mynewt-newtmgr git commit: nmxact - Device discovery.

Repository: incubator-mynewt-newtmgr
Updated Branches:
  refs/heads/master fc22b9fa5 -> 822cea97d


nmxact - Device discovery.


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/commit/822cea97
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/tree/822cea97
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/diff/822cea97

Branch: refs/heads/master
Commit: 822cea97dd33b02255269392be0ea7aa9472d709
Parents: fc22b9f
Author: Christopher Collins <cc...@apache.org>
Authored: Mon May 1 14:21:54 2017 -0700
Committer: Christopher Collins <cc...@apache.org>
Committed: Tue May 16 14:49:35 2017 -0700

----------------------------------------------------------------------
 nmxact/bledefs/bledefs.go           |  17 ++++
 nmxact/example/ble_dual/ble_dual.go |  35 +++++++-
 nmxact/example/ble_scan/ble_scan.go | 144 +++++++++++++++++++++++++++++
 nmxact/nmble/ble_act.go             |  55 ++++++++++--
 nmxact/nmble/ble_fsm.go             | 150 +++++++++++++++++++++----------
 nmxact/nmble/ble_oic_sesn.go        |   5 ++
 nmxact/nmble/ble_plain_sesn.go      |   4 +
 nmxact/nmble/ble_proto.go           |  39 +++++++-
 nmxact/nmble/ble_scanner.go         | 127 ++++++++++++++++++++++++++
 nmxact/nmble/ble_util.go            |  14 +--
 nmxact/nmble/ble_xport.go           |  18 +++-
 nmxact/nmble/dispatch.go            |  38 ++++----
 nmxact/nmserial/serial_xport.go     |   5 ++
 nmxact/nmxutil/nmxutil.go           |  77 +++++++++++++---
 nmxact/scan/scan.go                 |  48 ++++++++++
 nmxact/sesn/sesn_cfg.go             |   2 +
 nmxact/xport/xport.go               |   3 +
 17 files changed, 685 insertions(+), 96 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/bledefs/bledefs.go
----------------------------------------------------------------------
diff --git a/nmxact/bledefs/bledefs.go b/nmxact/bledefs/bledefs.go
index b811dc0..3d7e85b 100644
--- a/nmxact/bledefs/bledefs.go
+++ b/nmxact/bledefs/bledefs.go
@@ -29,6 +29,15 @@ import (
 
 const BLE_ATT_ATTR_MAX_LEN = 512
 
+const NmpPlainSvcUuid = "8D53DC1D-1DB7-4CD3-868B-8A527460AA84"
+const NmpPlainChrUuid = "DA2E7828-FBCE-4E01-AE9E-261174997C48"
+const NmpOicSvcUuid = "ADE3D529-C784-4F63-A987-EB69F70EE816"
+const NmpOicReqChrUuid = "AD7B334F-4637-4B86-90B6-9D787F03D218"
+const NmpOicRspChrUuid = "E9241982-4580-42C4-8831-95048216B256"
+
+// This supersedes the 128-bit UUID above.
+const OmpSvcUuid = 0x9923
+
 type BleAddrType int
 
 const (
@@ -391,3 +400,11 @@ func (d *BleConnDesc) String() string {
 		BleAddrTypeToString(d.PeerOtaAddrType),
 		d.PeerOtaAddr.String())
 }
+
+type BleEncryptWhen int
+
+const (
+	BLE_ENCRYPT_NEVER BleEncryptWhen = iota
+	BLE_ENCRYPT_PRIV_ONLY
+	BLE_ENCRYPT_ALWAYS
+)

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/example/ble_dual/ble_dual.go
----------------------------------------------------------------------
diff --git a/nmxact/example/ble_dual/ble_dual.go b/nmxact/example/ble_dual/ble_dual.go
index 5be14e5..d446c3a 100644
--- a/nmxact/example/ble_dual/ble_dual.go
+++ b/nmxact/example/ble_dual/ble_dual.go
@@ -21,6 +21,7 @@ package main
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os"
 	"os/signal"
 	"sync"
@@ -31,6 +32,7 @@ import (
 	"mynewt.apache.org/newt/util"
 	"mynewt.apache.org/newtmgr/nmxact/bledefs"
 	"mynewt.apache.org/newtmgr/nmxact/nmble"
+	"mynewt.apache.org/newtmgr/nmxact/nmp"
 	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
 	"mynewt.apache.org/newtmgr/nmxact/sesn"
 	"mynewt.apache.org/newtmgr/nmxact/xact"
@@ -144,6 +146,37 @@ func sendTaskStat(s sesn.Sesn) error {
 	return nil
 }
 
+func sendImageUpload(s sesn.Sesn) error {
+	data, err := ioutil.ReadFile("/Users/ccollins/Downloads/f17c9295bfbbf8f6b31ddcfa835be095090d0a875edcf88116bfaeec55f27e93/app.img")
+	if err != nil {
+		return err
+	}
+
+	c := xact.NewImageUploadCmd()
+	c.Data = data
+	c.ProgressCb = func(c *xact.ImageUploadCmd, r *nmp.ImageUploadRsp) {
+		fmt.Printf("Rxed upload rsp: %d\n", r.Off)
+	}
+
+	res, err := c.Run(s)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error executing image upload command: %s\n",
+			err.Error())
+		panic(err.Error())
+	}
+
+	if res.Status() != 0 {
+		fmt.Printf("Peer responded negatively to image upload command; "+
+			"status=%d\n", res.Status())
+		panic("ERROR")
+	}
+
+	fmt.Printf("[%p] Peer responded with image upload: rc=%#v\n",
+		s, res.Status())
+
+	return nil
+}
+
 func sendOne(s sesn.Sesn) {
 	// Repeatedly:
 	//     * Connect to peer if unconnected.
@@ -183,7 +216,7 @@ func sendOne(s sesn.Sesn) {
 }
 
 func main() {
-	nmxutil.SetLogLevel(log.InfoLevel)
+	nmxutil.SetLogLevel(log.DebugLevel)
 
 	// Initialize the BLE transport.
 	params := nmble.NewXportCfg()

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/example/ble_scan/ble_scan.go
----------------------------------------------------------------------
diff --git a/nmxact/example/ble_scan/ble_scan.go b/nmxact/example/ble_scan/ble_scan.go
new file mode 100644
index 0000000..d9f794c
--- /dev/null
+++ b/nmxact/example/ble_scan/ble_scan.go
@@ -0,0 +1,144 @@
+/**
+ * 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 main
+
+import (
+	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
+
+	log "github.com/Sirupsen/logrus"
+
+	"mynewt.apache.org/newt/util"
+	"mynewt.apache.org/newtmgr/nmxact/bledefs"
+	"mynewt.apache.org/newtmgr/nmxact/nmble"
+	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
+	"mynewt.apache.org/newtmgr/nmxact/scan"
+	"mynewt.apache.org/newtmgr/nmxact/sesn"
+	"mynewt.apache.org/newtmgr/nmxact/xport"
+)
+
+func configExitHandler(x xport.Xport, s sesn.Sesn) {
+	onExit := func() {
+		if s != nil && s.IsOpen() {
+			s.Close()
+		}
+
+		x.Stop()
+	}
+
+	sigChan := make(chan os.Signal, 1)
+	signal.Notify(sigChan)
+
+	go func() {
+		for {
+			s := <-sigChan
+			switch s {
+			case os.Interrupt, syscall.SIGTERM:
+				onExit()
+				os.Exit(0)
+
+			case syscall.SIGQUIT:
+				util.PrintStacks()
+			}
+		}
+	}()
+}
+
+func main() {
+	//nmxutil.SetLogLevel(log.DebugLevel)
+	nmxutil.SetLogLevel(log.InfoLevel)
+
+	// Initialize the BLE transport.
+	params := nmble.NewXportCfg()
+	params.SockPath = "/tmp/blehostd-uds"
+	params.BlehostdPath = "blehostd.elf"
+	params.DevPath = "/dev/cu.usbmodem14221"
+
+	x, err := nmble.NewBleXport(params)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error creating BLE transport: %s\n",
+			err.Error())
+		os.Exit(1)
+	}
+
+	// Start the BLE transport.
+	if err := x.Start(); err != nil {
+		fmt.Fprintf(os.Stderr, "error starting BLE transport: %s\n",
+			err.Error())
+		os.Exit(1)
+	}
+	defer x.Stop()
+
+	scanChan := make(chan scan.ScanPeer)
+	scanCb := func(peer scan.ScanPeer) {
+		fmt.Printf("discovered peer: %#v\n", peer)
+		scanChan <- peer
+	}
+
+	configExitHandler(x, nil)
+
+	scanner, err := x.BuildScanner()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error building BLE scanner: %s\n",
+			err.Error())
+		os.Exit(1)
+	}
+
+	for {
+		sc := scan.BleOmpScanCfg(scanCb)
+		if err := scanner.Start(sc); err != nil {
+			fmt.Fprintf(os.Stderr, "error starting scan: %s\n", err.Error())
+			os.Exit(1)
+		}
+
+		p := <-scanChan
+
+		// Found a peer; stop scanning.
+		if err := scanner.Stop(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to stop scan: %s\n", err.Error())
+			os.Exit(1)
+		}
+
+		fmt.Printf("Connecting to %#v\n", p)
+		c := sesn.NewSesnCfg()
+		c.MgmtProto = sesn.MGMT_PROTO_OMP
+		c.Ble.OwnAddrType = bledefs.BLE_ADDR_TYPE_RANDOM
+		c.Ble.PeerSpec = sesn.BlePeerSpecDev(p.Opaque.(bledefs.BleDev))
+
+		s, err := x.BuildSesn(c)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "error creating BLE session: %s\n",
+				err.Error())
+			os.Exit(1)
+		}
+
+		if err := s.Open(); err != nil {
+			fmt.Fprintf(os.Stderr, "error opening BLE session: %s\n",
+				err.Error())
+			os.Exit(1)
+		}
+
+		fmt.Printf("Connected\n")
+		fmt.Printf("Closing\n")
+		s.Close()
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmble/ble_act.go
----------------------------------------------------------------------
diff --git a/nmxact/nmble/ble_act.go b/nmxact/nmble/ble_act.go
index 4c5eef0..ed129cd 100644
--- a/nmxact/nmble/ble_act.go
+++ b/nmxact/nmble/ble_act.go
@@ -84,12 +84,12 @@ func connCancel(x *BleXport, bl *BleListener, r *BleConnCancelReq) error {
 			switch msg := bm.(type) {
 			case *BleConnCancelRsp:
 				bl.Acked = true
-				if msg.Status != 0 {
+				if msg.Status != 0 && msg.Status != ERR_CODE_EALREADY {
 					return StatusError(MSG_OP_RSP, rspType, msg.Status)
-				} else {
-					return nil
 				}
 
+				return nil
+
 			default:
 			}
 
@@ -288,9 +288,8 @@ func exchangeMtu(x *BleXport, bl *BleListener, r *BleExchangeMtuReq) (
 type scanSuccessFn func()
 type advRptFn func(r BleAdvReport)
 
-func scan(x *BleXport, bl *BleListener, r *BleScanReq,
-	abortChan chan struct{},
-	scanSuccessCb scanSuccessFn, advRptCb advRptFn) error {
+func actScan(x *BleXport, bl *BleListener, r *BleScanReq,
+	abortChan chan struct{}, advRptCb advRptFn) error {
 
 	const rspType = MSG_TYPE_SCAN
 
@@ -314,8 +313,6 @@ func scan(x *BleXport, bl *BleListener, r *BleScanReq,
 				bl.Acked = true
 				if msg.Status != 0 {
 					return StatusError(MSG_OP_RSP, rspType, msg.Status)
-				} else {
-					scanSuccessCb()
 				}
 
 			case *BleScanEvt:
@@ -358,7 +355,7 @@ func scanCancel(x *BleXport, bl *BleListener, r *BleScanCancelReq) error {
 			switch msg := bm.(type) {
 			case *BleScanCancelRsp:
 				bl.Acked = true
-				if msg.Status != 0 {
+				if msg.Status != 0 && msg.Status != ERR_CODE_EALREADY {
 					return StatusError(MSG_OP_RSP, rspType, msg.Status)
 				}
 				return nil
@@ -443,6 +440,46 @@ func reset(x *BleXport, bl *BleListener,
 	}
 }
 
+// Blocking
+func encInitiate(x *BleXport, bl *BleListener, encChan chan error,
+	r *BleSecurityInitiateReq) error {
+
+	const rspType = MSG_TYPE_SECURITY_INITIATE
+
+	j, err := json.Marshal(r)
+	if err != nil {
+		return err
+	}
+
+	if err := x.Tx(j); err != nil {
+		return err
+	}
+
+	for {
+		select {
+		case err := <-bl.ErrChan:
+			return err
+
+		case bm := <-bl.BleChan:
+			switch msg := bm.(type) {
+			case *BleSecurityInitiateRsp:
+				bl.Acked = true
+				if msg.Status != 0 {
+					return StatusError(MSG_OP_RSP, rspType, msg.Status)
+				}
+
+			default:
+			}
+
+		case err := <-encChan:
+			return err
+
+		case <-bl.AfterTimeout(x.RspTimeout()):
+			return BhdTimeoutError(rspType, r.Seq)
+		}
+	}
+}
+
 // Asks the controller to generate a random address.  This is done when the
 // transport is starting up, and therefore does not require the transport to be
 // synced.  Only the transport should call this function.

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmble/ble_fsm.go
----------------------------------------------------------------------
diff --git a/nmxact/nmble/ble_fsm.go b/nmxact/nmble/ble_fsm.go
index 828997a..ed7b90b 100644
--- a/nmxact/nmble/ble_fsm.go
+++ b/nmxact/nmble/ble_fsm.go
@@ -34,10 +34,11 @@ const (
 	SESN_STATE_EXCHANGE_MTU                 = 3
 	SESN_STATE_DISCOVER_SVC                 = 4
 	SESN_STATE_DISCOVER_CHR                 = 5
-	SESN_STATE_SUBSCRIBE                    = 6
-	SESN_STATE_DONE                         = 7
-	SESN_STATE_TERMINATING                  = 8
-	SESN_STATE_CONN_CANCELLING              = 9
+	SESN_STATE_SECURITY                     = 6
+	SESN_STATE_SUBSCRIBE                    = 7
+	SESN_STATE_DONE                         = 8
+	SESN_STATE_TERMINATING                  = 9
+	SESN_STATE_CONN_CANCELLING              = 10
 )
 
 type BleFsmDisconnectType int
@@ -67,6 +68,7 @@ type BleFsmParams struct {
 	RspChrUuid   BleUuid
 	RxNmpCb      BleRxNmpFn
 	DisconnectCb BleDisconnectFn
+	Encrypt      BleEncryptWhen
 }
 
 type BleFsm struct {
@@ -79,15 +81,13 @@ type BleFsm struct {
 	nmpRspChr  *BleChr
 	attMtu     int
 	connChan   chan error
+	encChan    chan error
 	bls        map[*BleListener]struct{}
 	state      BleSesnState
 	errFunnel  nmxutil.ErrFunnel
 	id         uint32
 	wg         sync.WaitGroup
 
-	// Gets notified when the FSM has stopped due to an error.
-	errChan chan error
-
 	// Protects all accesses to the FSM state variable.
 	stateMtx sync.Mutex
 
@@ -207,8 +207,12 @@ func (bf *BleFsm) removeBleSeqListener(name string, seq BleSeq) {
 	bf.removeBleListener(name, base)
 }
 
+func (bf *BleFsm) connInfo() (BleConnDesc, error) {
+	return ConnFindXact(bf.params.Bx, bf.connHandle)
+}
+
 func (bf *BleFsm) logConnection() {
-	desc, err := ConnFindXact(bf.params.Bx, bf.connHandle)
+	desc, err := bf.connInfo()
 	if err != nil {
 		return
 	}
@@ -263,19 +267,17 @@ func (bf *BleFsm) errorAll(err error) {
 
 func (bf *BleFsm) processErr(err error) {
 	// Remember some fields before we clear them.
-	dt := calcDisconnectType(bf.state)
+	dt := calcDisconnectType(bf.getState())
 
 	var peer BleDev
 	if bf.peerDev != nil {
 		peer = *bf.peerDev
 	}
 
+	bf.params.Bx.StopWaitingForMaster(bf, err)
 	bf.errorAll(err)
 
-	bf.stateMtx.Lock()
-	bf.state = SESN_STATE_UNCONNECTED
-	bf.stateMtx.Unlock()
-
+	bf.setState(SESN_STATE_UNCONNECTED)
 	bf.peerDev = nil
 
 	// Wait for all listeners to get removed.
@@ -283,8 +285,6 @@ func (bf *BleFsm) processErr(err error) {
 
 	bf.errFunnel.Reset()
 	bf.params.DisconnectCb(dt, peer, err)
-
-	bf.errChan <- err
 }
 
 func (bf *BleFsm) connectListen(seq BleSeq) error {
@@ -357,6 +357,21 @@ func (bf *BleFsm) connectListen(seq BleSeq) error {
 						bf.attMtu = int(msg.Mtu)
 					}
 
+				case *BleEncChangeEvt:
+					var err error
+					if msg.Status != 0 {
+						err = StatusError(MSG_OP_EVT,
+							MSG_TYPE_ENC_CHANGE_EVT,
+							msg.Status)
+						log.Debugf(err.Error())
+					} else {
+						log.Debugf("Connection encrypted; conn_handle=%d",
+							msg.ConnHandle)
+					}
+					if bf.encChan != nil {
+						bf.encChan <- err
+					}
+
 				case *BleDisconnectEvt:
 					err := bf.disconnectError(msg.Reason)
 					bf.errFunnel.Insert(err)
@@ -419,7 +434,7 @@ func (bf *BleFsm) connect() error {
 	r.PeerAddr = bf.peerDev.Addr
 
 	// Initiating a connection requires dedicated master privileges.
-	if err := bf.params.Bx.AcquireMaster(); err != nil {
+	if err := bf.params.Bx.AcquireMaster(bf); err != nil {
 		return err
 	}
 	defer bf.params.Bx.ReleaseMaster()
@@ -439,7 +454,7 @@ func (bf *BleFsm) connect() error {
 	}
 
 	// Connection operation now in progress.
-	bf.state = SESN_STATE_CONNECTING
+	bf.setState(SESN_STATE_CONNECTING)
 
 	err := <-bf.connChan
 	if !nmxutil.IsXport(err) {
@@ -447,11 +462,8 @@ func (bf *BleFsm) connect() error {
 		// operation.  In most cases, the host has already stopped connecting
 		// and will respond with an "ealready" error that can be ignored.
 		if err := bf.connCancel(); err != nil {
-			bhe := nmxutil.ToBleHost(err)
-			if bhe == nil || bhe.Status != ERR_CODE_EALREADY {
-				log.Errorf("Failed to cancel connect in progress: %s",
-					err.Error())
-			}
+			log.Errorf("Failed to cancel connect in progress: %s",
+				err.Error())
 		}
 	}
 
@@ -468,7 +480,7 @@ func (bf *BleFsm) scan() error {
 	r.FilterDuplicates = true
 
 	// Scanning requires dedicated master privileges.
-	if err := bf.params.Bx.AcquireMaster(); err != nil {
+	if err := bf.params.Bx.AcquireMaster(bf); err != nil {
 		return err
 	}
 	defer bf.params.Bx.ReleaseMaster()
@@ -481,9 +493,6 @@ func (bf *BleFsm) scan() error {
 
 	abortChan := make(chan struct{}, 1)
 
-	// This function gets called when scanning begins.
-	scanSuccessCb := func() { bf.state = SESN_STATE_SCANNING }
-
 	// This function gets called for each incoming advertisement.
 	advRptCb := func(r BleAdvReport) {
 		// Ask client if we should connect to this advertiser.
@@ -493,17 +502,14 @@ func (bf *BleFsm) scan() error {
 		}
 	}
 
-	err = scan(bf.params.Bx, bl, r, abortChan, scanSuccessCb, advRptCb)
+	err = actScan(bf.params.Bx, bl, r, abortChan, advRptCb)
 	if !nmxutil.IsXport(err) {
 		// The transport did not restart; always attempt to cancel the scan
 		// operation.  In most cases, the host has already stopped scanning
 		// and will respond with an "ealready" error that can be ignored.
 		if err := bf.scanCancel(); err != nil {
-			bhe := nmxutil.ToBleHost(err)
-			if bhe == nil || bhe.Status != ERR_CODE_EALREADY {
-				log.Errorf("Failed to cancel scan in progress: %s",
-					err.Error())
-			}
+			log.Errorf("Failed to cancel scan in progress: %s",
+				err.Error())
 		}
 	}
 
@@ -527,10 +533,7 @@ func (bf *BleFsm) scanCancel() error {
 }
 
 func (bf *BleFsm) terminateSetState() error {
-	bf.stateMtx.Lock()
-	defer bf.stateMtx.Unlock()
-
-	switch bf.state {
+	switch bf.getState() {
 	case SESN_STATE_UNCONNECTED,
 		SESN_STATE_CONNECTING,
 		SESN_STATE_CONN_CANCELLING:
@@ -539,7 +542,7 @@ func (bf *BleFsm) terminateSetState() error {
 		return fmt.Errorf(
 			"BLE terminate failed; session already being closed")
 	default:
-		bf.state = SESN_STATE_TERMINATING
+		bf.setState(SESN_STATE_TERMINATING)
 	}
 
 	return nil
@@ -602,6 +605,26 @@ func (bf *BleFsm) discSvcUuid() error {
 	return nil
 }
 
+func (bf *BleFsm) encInitiate() error {
+	r := NewBleSecurityInitiateReq()
+	r.ConnHandle = bf.connHandle
+
+	bl, err := bf.addBleSeqListener("enc-initiate", r.Seq)
+	if err != nil {
+		return err
+	}
+	defer bf.removeBleSeqListener("enc-initiate", r.Seq)
+
+	bf.encChan = make(chan error, 1)
+	defer func() { bf.encChan = nil }()
+
+	if err := encInitiate(bf.params.Bx, bl, bf.encChan, r); err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func (bf *BleFsm) discAllChrs() error {
 	r := NewBleDiscAllChrsReq()
 	r.ConnHandle = bf.connHandle
@@ -700,6 +723,24 @@ func (bf *BleFsm) subscribe() error {
 	return nil
 }
 
+func (bf *BleFsm) shouldEncrypt() bool {
+	switch bf.params.Encrypt {
+	case BLE_ENCRYPT_NEVER:
+		return false
+
+	case BLE_ENCRYPT_ALWAYS:
+		return true
+
+	case BLE_ENCRYPT_PRIV_ONLY:
+		return bf.peerDev.AddrType == BLE_ADDR_TYPE_RPA_PUB ||
+			bf.peerDev.AddrType == BLE_ADDR_TYPE_RPA_RND
+
+	default:
+		panic(fmt.Sprintf("Invalid BleEncryptWhen value: %d",
+			bf.params.Encrypt))
+	}
+}
+
 // Tries to populate the FSM's peerDev field.  This function succeeds if the
 // client specified the address of the peer to connect to.
 func (bf *BleFsm) tryFillPeerDev() bool {
@@ -718,10 +759,7 @@ func (bf *BleFsm) tryFillPeerDev() bool {
 }
 
 func (bf *BleFsm) executeState() (bool, error) {
-	bf.stateMtx.Lock()
-	defer bf.stateMtx.Unlock()
-
-	switch bf.state {
+	switch bf.getState() {
 	case SESN_STATE_UNCONNECTED:
 		// Determine if we can immediately initiate a connection, or if we
 		// need to scan for a peer first.  If the client specified a peer
@@ -731,17 +769,18 @@ func (bf *BleFsm) executeState() (bool, error) {
 		bf.tryFillPeerDev()
 		if bf.peerDev == nil {
 			// Peer not inferred yet.  Initiate scan.
+			bf.setState(SESN_STATE_SCANNING)
 			if err := bf.scan(); err != nil {
 				return false, err
 			}
-			bf.state = SESN_STATE_UNCONNECTED
+			bf.setState(SESN_STATE_UNCONNECTED)
 		} else {
 			// We already know the address we want to connect to.  Initiate
 			// a connection.
 			if err := bf.connect(); err != nil {
 				return false, err
 			}
-			bf.state = SESN_STATE_EXCHANGE_MTU
+			bf.setState(SESN_STATE_EXCHANGE_MTU)
 		}
 
 	case SESN_STATE_EXCHANGE_MTU:
@@ -750,25 +789,35 @@ func (bf *BleFsm) executeState() (bool, error) {
 			retry := bhe != nil && bhe.Status == ERR_CODE_ENOTCONN
 			return retry, err
 		}
-		bf.state = SESN_STATE_DISCOVER_SVC
+		bf.setState(SESN_STATE_DISCOVER_SVC)
 
 	case SESN_STATE_DISCOVER_SVC:
 		if err := bf.discSvcUuid(); err != nil {
 			return false, err
 		}
-		bf.state = SESN_STATE_DISCOVER_CHR
+		bf.setState(SESN_STATE_DISCOVER_CHR)
 
 	case SESN_STATE_DISCOVER_CHR:
 		if err := bf.discAllChrs(); err != nil {
 			return false, err
 		}
-		bf.state = SESN_STATE_SUBSCRIBE
+		if bf.shouldEncrypt() {
+			bf.setState(SESN_STATE_SECURITY)
+		} else {
+			bf.setState(SESN_STATE_SUBSCRIBE)
+		}
+
+	case SESN_STATE_SECURITY:
+		if err := bf.encInitiate(); err != nil {
+			return false, err
+		}
+		bf.setState(SESN_STATE_SUBSCRIBE)
 
 	case SESN_STATE_SUBSCRIBE:
 		if err := bf.subscribe(); err != nil {
 			return false, err
 		}
-		bf.state = SESN_STATE_DONE
+		bf.setState(SESN_STATE_DONE)
 
 	case SESN_STATE_DONE:
 		/* Open complete. */
@@ -789,13 +838,12 @@ func (bf *BleFsm) startOnce() (bool, error) {
 	}
 
 	bf.errFunnel.Start()
-	bf.errChan = make(chan error, 1)
 
 	for {
 		retry, err := bf.executeState()
 		if err != nil {
 			bf.errFunnel.Insert(err)
-			err = <-bf.errChan
+			err = bf.errFunnel.Wait()
 			return retry, err
 		} else if bf.getState() == SESN_STATE_DONE {
 			return false, nil
@@ -837,6 +885,10 @@ func (bf *BleFsm) Stop() (bool, error) {
 		bf.errFunnel.Insert(fmt.Errorf("Connection attempt cancelled"))
 		return false, nil
 
+	case SESN_STATE_SCANNING:
+		bf.errFunnel.Insert(fmt.Errorf("Scan cancelled"))
+		return false, nil
+
 	default:
 		if err := bf.terminate(); err != nil {
 			return false, err

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmble/ble_oic_sesn.go
----------------------------------------------------------------------
diff --git a/nmxact/nmble/ble_oic_sesn.go b/nmxact/nmble/ble_oic_sesn.go
index 433ecb0..b390738 100644
--- a/nmxact/nmble/ble_oic_sesn.go
+++ b/nmxact/nmble/ble_oic_sesn.go
@@ -57,6 +57,7 @@ func NewBleOicSesn(bx *BleXport, cfg sesn.SesnCfg) *BleOicSesn {
 		SvcUuid:     svcUuid,
 		ReqChrUuid:  reqChrUuid,
 		RspChrUuid:  rspChrUuid,
+		Encrypt:     cfg.Ble.Encrypt,
 		RxNmpCb:     func(d []byte) { bos.onRxNmp(d) },
 		DisconnectCb: func(dt BleFsmDisconnectType, p BleDev, e error) {
 			bos.onDisconnect(dt, p, e)
@@ -245,3 +246,7 @@ func (bos *BleOicSesn) MtuOut() int {
 		nmp.NMP_HDR_SIZE
 	return util.IntMin(mtu, BLE_ATT_ATTR_MAX_LEN)
 }
+
+func (bos *BleOicSesn) ConnInfo() (BleConnDesc, error) {
+	return bos.bf.connInfo()
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmble/ble_plain_sesn.go
----------------------------------------------------------------------
diff --git a/nmxact/nmble/ble_plain_sesn.go b/nmxact/nmble/ble_plain_sesn.go
index 2bcd950..f2048c3 100644
--- a/nmxact/nmble/ble_plain_sesn.go
+++ b/nmxact/nmble/ble_plain_sesn.go
@@ -217,3 +217,7 @@ func (bps *BlePlainSesn) MtuOut() int {
 	mtu := bps.bf.attMtu - WRITE_CMD_BASE_SZ - nmp.NMP_HDR_SIZE
 	return util.IntMin(mtu, BLE_ATT_ATTR_MAX_LEN)
 }
+
+func (bps *BlePlainSesn) ConnInfo() (BleConnDesc, error) {
+	return bps.bf.connInfo()
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmble/ble_proto.go
----------------------------------------------------------------------
diff --git a/nmxact/nmble/ble_proto.go b/nmxact/nmble/ble_proto.go
index 0bb4d01..24363c7 100644
--- a/nmxact/nmble/ble_proto.go
+++ b/nmxact/nmble/ble_proto.go
@@ -93,7 +93,7 @@ const (
 	ERR_CODE_HCI_ACL_CONN_EXISTS         = 11
 	ERR_CODE_HCI_CMD_DISALLOWED          = 12
 	ERR_CODE_HCI_CONN_REJ_RESOURCES      = 13
-	ERR_CODE_HCI_CONN_REJ_SECURITY       = 14
+	ERR_CODE_HCI_CONN_REJ_ENC            = 14
 	ERR_CODE_HCI_CONN_REJ_BD_ADDR        = 15
 	ERR_CODE_HCI_CONN_ACCEPT_TMO         = 16
 	ERR_CODE_HCI_UNSUPPORTED             = 17
@@ -157,7 +157,7 @@ var HciErrCodeStringMap = map[int]string{
 	ERR_CODE_HCI_ACL_CONN_EXISTS:     "acl conn exists",
 	ERR_CODE_HCI_CMD_DISALLOWED:      "cmd disallowed",
 	ERR_CODE_HCI_CONN_REJ_RESOURCES:  "conn rej resources",
-	ERR_CODE_HCI_CONN_REJ_SECURITY:   "conn rej security",
+	ERR_CODE_HCI_CONN_REJ_ENC:        "conn rej security",
 	ERR_CODE_HCI_CONN_REJ_BD_ADDR:    "conn rej bd addr",
 	ERR_CODE_HCI_CONN_ACCEPT_TMO:     "conn accept tmo",
 	ERR_CODE_HCI_UNSUPPORTED:         "unsupported",
@@ -270,6 +270,7 @@ var MsgTypeStringMap = map[MsgType]string{
 	MSG_TYPE_SCAN:              "scan",
 	MSG_TYPE_SCAN_CANCEL:       "scan_cancel",
 	MSG_TYPE_SET_PREFERRED_MTU: "set_preferred_mtu",
+	MSG_TYPE_SECURITY_INITIATE: "security_initiate",
 	MSG_TYPE_CONN_FIND:         "conn_find",
 	MSG_TYPE_RESET:             "reset",
 
@@ -784,6 +785,40 @@ type BleResetRsp struct {
 	Op   MsgOp   `json:"op"`
 	Type MsgType `json:"type"`
 	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	Status int `json:"status"`
+}
+
+type BleSecurityInitiateReq struct {
+	// Header
+	Op   MsgOp   `json:"op"`
+	Type MsgType `json:"type"`
+	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	ConnHandle uint16 `json:"conn_handle"`
+}
+
+type BleSecurityInitiateRsp struct {
+	// Header
+	Op   MsgOp   `json:"op"`
+	Type MsgType `json:"type"`
+	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	Status int `json:"status"`
+}
+
+type BleEncChangeEvt struct {
+	// Header
+	Op   MsgOp   `json:"op"`
+	Type MsgType `json:"type"`
+	Seq  BleSeq  `json:"seq"`
+
+	// Mandatory
+	Status     int    `json:"status"`
+	ConnHandle uint16 `json:"conn_handle"`
 }
 
 func ErrCodeToString(e int) string {

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmble/ble_scanner.go
----------------------------------------------------------------------
diff --git a/nmxact/nmble/ble_scanner.go b/nmxact/nmble/ble_scanner.go
new file mode 100644
index 0000000..4cdcf07
--- /dev/null
+++ b/nmxact/nmble/ble_scanner.go
@@ -0,0 +1,127 @@
+package nmble
+
+import (
+	"encoding/base64"
+	"fmt"
+
+	log "github.com/Sirupsen/logrus"
+
+	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
+	"mynewt.apache.org/newtmgr/nmxact/omp"
+	"mynewt.apache.org/newtmgr/nmxact/scan"
+	"mynewt.apache.org/newtmgr/nmxact/xact"
+)
+
+// Implements scan.Scanner.
+type BleScanner struct {
+	cfg scan.Cfg
+
+	bx           *BleXport
+	reportedDevs map[BleDev][]byte
+	bos          *BleOicSesn
+	od           *omp.OmpDispatcher
+	enabled      bool
+}
+
+func NewBleScanner(bx *BleXport) *BleScanner {
+	return &BleScanner{
+		bx:           bx,
+		reportedDevs: map[BleDev][]byte{},
+	}
+}
+
+func (s *BleScanner) scan() (scan.ScanPeer, error) {
+	if err := s.bos.Open(); err != nil {
+		return scan.ScanPeer{}, err
+	}
+	defer s.bos.Close()
+
+	// Now we are connected and paired.  Read the peer's hardware ID and report
+	// it upstream.
+
+	desc, err := s.bos.ConnInfo()
+	if err != nil {
+		return scan.ScanPeer{}, err
+	}
+
+	c := xact.NewConfigReadCmd()
+	c.Name = "id/hwid"
+
+	res, err := c.Run(s.bos)
+	if err != nil {
+		return scan.ScanPeer{}, err
+	}
+	if res.Status() != 0 {
+		return scan.ScanPeer{},
+			fmt.Errorf("failed to read hardware ID; NMP status=%d",
+				res.Status())
+	}
+	cres := res.(*xact.ConfigReadResult)
+
+	rawId, err := base64.StdEncoding.DecodeString(cres.Rsp.Val)
+	if err != nil {
+		return scan.ScanPeer{},
+			fmt.Errorf("failed to decode hardware ID; undecoded=%s",
+				cres.Rsp.Val)
+	}
+
+	peer := scan.ScanPeer{
+		HwId: rawId,
+		Opaque: BleDev{
+			AddrType: desc.PeerIdAddrType,
+			Addr:     desc.PeerIdAddr,
+		},
+	}
+
+	return peer, nil
+}
+
+func (s *BleScanner) Start(cfg scan.Cfg) error {
+	if s.enabled {
+		return fmt.Errorf("Attempt to start BLE scanner twice")
+	}
+
+	// Wrap predicate with logic that discards duplicates.
+	innerPred := cfg.SesnCfg.Ble.PeerSpec.ScanPred
+	cfg.SesnCfg.Ble.PeerSpec.ScanPred = func(adv BleAdvReport) bool {
+		// Filter devices that have already been reported.
+		if s.reportedDevs[adv.Sender] != nil {
+			return false
+		}
+		return innerPred(adv)
+	}
+
+	session, err := s.bx.BuildSesn(cfg.SesnCfg)
+	if err != nil {
+		return err
+	}
+
+	s.enabled = true
+	s.cfg = cfg
+	s.bos = session.(*BleOicSesn)
+
+	// Start background scanning.
+	go func() {
+		for s.enabled {
+			p, err := s.scan()
+			if err != nil {
+				log.Debugf("Scan error: %s", err.Error())
+			} else {
+				s.reportedDevs[p.Opaque.(BleDev)] = p.HwId
+				s.cfg.ScanCb(p)
+			}
+		}
+	}()
+
+	return nil
+}
+
+func (s *BleScanner) Stop() error {
+	if !s.enabled {
+		return fmt.Errorf("Attempt to stop BLE scanner twice")
+	}
+	s.enabled = false
+
+	s.bos.Close()
+	return nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmble/ble_util.go
----------------------------------------------------------------------
diff --git a/nmxact/nmble/ble_util.go b/nmxact/nmble/ble_util.go
index 13a2b8c..fc5fe27 100644
--- a/nmxact/nmble/ble_util.go
+++ b/nmxact/nmble/ble_util.go
@@ -11,12 +11,6 @@ import (
 	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
 )
 
-const NmpPlainSvcUuid = "8D53DC1D-1DB7-4CD3-868B-8A527460AA84"
-const NmpPlainChrUuid = "DA2E7828-FBCE-4E01-AE9E-261174997C48"
-const NmpOicSvcUuid = "ADE3D529-C784-4F63-A987-EB69F70EE816"
-const NmpOicReqChrUuid = "AD7B334F-4637-4B86-90B6-9D787F03D218"
-const NmpOicRspChrUuid = "E9241982-4580-42C4-8831-95048216B256"
-
 const WRITE_CMD_BASE_SZ = 3
 const NOTIFY_CMD_BASE_SZ = 3
 
@@ -251,6 +245,14 @@ func NewResetReq() *BleResetReq {
 	}
 }
 
+func NewBleSecurityInitiateReq() *BleSecurityInitiateReq {
+	return &BleSecurityInitiateReq{
+		Op:   MSG_OP_REQ,
+		Type: MSG_TYPE_SECURITY_INITIATE,
+		Seq:  NextSeq(),
+	}
+}
+
 func ConnFindXact(x *BleXport, connHandle uint16) (BleConnDesc, error) {
 	r := NewBleConnFindReq()
 	r.ConnHandle = connHandle

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmble/ble_xport.go
----------------------------------------------------------------------
diff --git a/nmxact/nmble/ble_xport.go b/nmxact/nmble/ble_xport.go
index 0c2fe80..fdaf07c 100644
--- a/nmxact/nmble/ble_xport.go
+++ b/nmxact/nmble/ble_xport.go
@@ -12,6 +12,7 @@ import (
 	"mynewt.apache.org/newt/util/unixchild"
 	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
 	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
+	"mynewt.apache.org/newtmgr/nmxact/scan"
 	"mynewt.apache.org/newtmgr/nmxact/sesn"
 )
 
@@ -94,6 +95,7 @@ type BleXport struct {
 	master            nmxutil.SingleResource
 	randAddr          *BleAddr
 	mtx               sync.Mutex
+	scanner           *BleScanner
 
 	cfg XportCfg
 }
@@ -123,6 +125,14 @@ func (bx *BleXport) createUnixChild() {
 	bx.client = unixchild.New(config)
 }
 
+func (bx *BleXport) BuildScanner() (scan.Scanner, error) {
+	if bx.scanner == nil {
+		bx.scanner = NewBleScanner(bx)
+	}
+
+	return bx.scanner, nil
+}
+
 func (bx *BleXport) BuildSesn(cfg sesn.SesnCfg) (sesn.Sesn, error) {
 	switch cfg.MgmtProto {
 	case sesn.MGMT_PROTO_NMP:
@@ -533,10 +543,14 @@ func (bx *BleXport) RspTimeout() time.Duration {
 	return bx.cfg.BlehostdRspTimeout
 }
 
-func (bx *BleXport) AcquireMaster() error {
-	return bx.master.Acquire()
+func (bx *BleXport) AcquireMaster(token interface{}) error {
+	return bx.master.Acquire(token)
 }
 
 func (bx *BleXport) ReleaseMaster() {
 	bx.master.Release()
 }
+
+func (bx *BleXport) StopWaitingForMaster(token interface{}, err error) {
+	bx.master.StopWaiting(token, err)
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmble/dispatch.go
----------------------------------------------------------------------
diff --git a/nmxact/nmble/dispatch.go b/nmxact/nmble/dispatch.go
index c9f17ec..9225292 100644
--- a/nmxact/nmble/dispatch.go
+++ b/nmxact/nmble/dispatch.go
@@ -84,23 +84,24 @@ type BleDispatcher struct {
 
 type msgCtor func() BleMsg
 
-func errRspCtor() BleMsg             { return &BleErrRsp{} }
-func syncRspCtor() BleMsg            { return &BleSyncRsp{} }
-func connectRspCtor() BleMsg         { return &BleConnectRsp{} }
-func terminateRspCtor() BleMsg       { return &BleTerminateRsp{} }
-func discSvcUuidRspCtor() BleMsg     { return &BleDiscSvcUuidRsp{} }
-func discAllChrsRspCtor() BleMsg     { return &BleDiscAllChrsRsp{} }
-func discChrUuidRspCtor() BleMsg     { return &BleDiscChrUuidRsp{} }
-func writeCmdRspCtor() BleMsg        { return &BleWriteCmdRsp{} }
-func exchangeMtuRspCtor() BleMsg     { return &BleExchangeMtuRsp{} }
-func genRandAddrRspCtor() BleMsg     { return &BleGenRandAddrRsp{} }
-func setRandAddrRspCtor() BleMsg     { return &BleSetRandAddrRsp{} }
-func connCancelRspCtor() BleMsg      { return &BleConnCancelRsp{} }
-func scanRspCtor() BleMsg            { return &BleScanRsp{} }
-func scanCancelRspCtor() BleMsg      { return &BleScanCancelRsp{} }
-func setPreferredMtuRspCtor() BleMsg { return &BleSetPreferredMtuRsp{} }
-func connFindRspCtor() BleMsg        { return &BleConnFindRsp{} }
-func resetRspCtor() BleMsg           { return &BleResetRsp{} }
+func errRspCtor() BleMsg              { return &BleErrRsp{} }
+func syncRspCtor() BleMsg             { return &BleSyncRsp{} }
+func connectRspCtor() BleMsg          { return &BleConnectRsp{} }
+func terminateRspCtor() BleMsg        { return &BleTerminateRsp{} }
+func discSvcUuidRspCtor() BleMsg      { return &BleDiscSvcUuidRsp{} }
+func discAllChrsRspCtor() BleMsg      { return &BleDiscAllChrsRsp{} }
+func discChrUuidRspCtor() BleMsg      { return &BleDiscChrUuidRsp{} }
+func writeCmdRspCtor() BleMsg         { return &BleWriteCmdRsp{} }
+func exchangeMtuRspCtor() BleMsg      { return &BleExchangeMtuRsp{} }
+func genRandAddrRspCtor() BleMsg      { return &BleGenRandAddrRsp{} }
+func setRandAddrRspCtor() BleMsg      { return &BleSetRandAddrRsp{} }
+func connCancelRspCtor() BleMsg       { return &BleConnCancelRsp{} }
+func scanRspCtor() BleMsg             { return &BleScanRsp{} }
+func scanCancelRspCtor() BleMsg       { return &BleScanCancelRsp{} }
+func setPreferredMtuRspCtor() BleMsg  { return &BleSetPreferredMtuRsp{} }
+func securityInitiateRspCtor() BleMsg { return &BleSecurityInitiateRsp{} }
+func connFindRspCtor() BleMsg         { return &BleConnFindRsp{} }
+func resetRspCtor() BleMsg            { return &BleResetRsp{} }
 
 func syncEvtCtor() BleMsg       { return &BleSyncEvt{} }
 func connectEvtCtor() BleMsg    { return &BleConnectEvt{} }
@@ -111,6 +112,7 @@ func notifyRxEvtCtor() BleMsg   { return &BleNotifyRxEvt{} }
 func mtuChangeEvtCtor() BleMsg  { return &BleMtuChangeEvt{} }
 func scanEvtCtor() BleMsg       { return &BleScanEvt{} }
 func scanTmoEvtCtor() BleMsg    { return &BleScanTmoEvt{} }
+func encChangeEvtCtor() BleMsg  { return &BleEncChangeEvt{} }
 
 var msgCtorMap = map[OpTypePair]msgCtor{
 	{MSG_OP_RSP, MSG_TYPE_ERR}:               errRspCtor,
@@ -128,6 +130,7 @@ var msgCtorMap = map[OpTypePair]msgCtor{
 	{MSG_OP_RSP, MSG_TYPE_SCAN}:              scanRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_SCAN_CANCEL}:       scanCancelRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_SET_PREFERRED_MTU}: setPreferredMtuRspCtor,
+	{MSG_OP_RSP, MSG_TYPE_SECURITY_INITIATE}: securityInitiateRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_CONN_FIND}:         connFindRspCtor,
 	{MSG_OP_RSP, MSG_TYPE_RESET}:             resetRspCtor,
 
@@ -140,6 +143,7 @@ var msgCtorMap = map[OpTypePair]msgCtor{
 	{MSG_OP_EVT, MSG_TYPE_MTU_CHANGE_EVT}: mtuChangeEvtCtor,
 	{MSG_OP_EVT, MSG_TYPE_SCAN_EVT}:       scanEvtCtor,
 	{MSG_OP_EVT, MSG_TYPE_SCAN_TMO_EVT}:   scanTmoEvtCtor,
+	{MSG_OP_EVT, MSG_TYPE_ENC_CHANGE_EVT}: encChangeEvtCtor,
 }
 
 func NewBleDispatcher() *BleDispatcher {

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmserial/serial_xport.go
----------------------------------------------------------------------
diff --git a/nmxact/nmserial/serial_xport.go b/nmxact/nmserial/serial_xport.go
index 37f991f..a8c0c71 100644
--- a/nmxact/nmserial/serial_xport.go
+++ b/nmxact/nmserial/serial_xport.go
@@ -14,6 +14,7 @@ import (
 
 	"mynewt.apache.org/newt/util"
 	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
+	"mynewt.apache.org/newtmgr/nmxact/scan"
 	"mynewt.apache.org/newtmgr/nmxact/sesn"
 )
 
@@ -56,6 +57,10 @@ func (sx *SerialXport) BuildSesn(cfg sesn.SesnCfg) (sesn.Sesn, error) {
 	}
 }
 
+func (sx *SerialXport) BuildScanner() (scan.Scanner, error) {
+	return nil, fmt.Errorf("Attempt to build serial scanner")
+}
+
 func (sx *SerialXport) Start() error {
 	c := &serial.Config{
 		Name:        sx.cfg.DevPath,

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/nmxutil/nmxutil.go
----------------------------------------------------------------------
diff --git a/nmxact/nmxutil/nmxutil.go b/nmxact/nmxutil/nmxutil.go
index 707b1bc..257d6b5 100644
--- a/nmxact/nmxutil/nmxutil.go
+++ b/nmxact/nmxutil/nmxutil.go
@@ -1,6 +1,7 @@
 package nmxutil
 
 import (
+	"fmt"
 	"math/rand"
 	"os"
 	"sync"
@@ -40,19 +41,22 @@ func NextNmpSeq() uint8 {
 	return val
 }
 
+type SRWaiter struct {
+	c     chan error
+	token interface{}
+}
+
 type SingleResource struct {
 	acquired  bool
-	waitQueue [](chan error)
+	waitQueue []SRWaiter
 	mtx       sync.Mutex
 }
 
 func NewSingleResource() SingleResource {
-	return SingleResource{
-		waitQueue: [](chan error){},
-	}
+	return SingleResource{}
 }
 
-func (s *SingleResource) Acquire() error {
+func (s *SingleResource) Acquire(token interface{}) error {
 	s.mtx.Lock()
 
 	if !s.acquired {
@@ -61,12 +65,17 @@ func (s *SingleResource) Acquire() error {
 		return nil
 	}
 
-	w := make(chan error)
+	// XXX: Verify no duplicates.
+
+	w := SRWaiter{
+		c:     make(chan error),
+		token: token,
+	}
 	s.waitQueue = append(s.waitQueue, w)
 
 	s.mtx.Unlock()
 
-	err := <-w
+	err := <-w.c
 	if err != nil {
 		return err
 	}
@@ -94,7 +103,19 @@ func (s *SingleResource) Release() {
 
 	s.mtx.Unlock()
 
-	w <- nil
+	w.c <- nil
+}
+
+func (s *SingleResource) StopWaiting(token interface{}, err error) {
+	s.mtx.Lock()
+	defer s.mtx.Unlock()
+
+	for _, w := range s.waitQueue {
+		if w.token == token {
+			w.c <- err
+			return
+		}
+	}
 }
 
 func (s *SingleResource) Abort(err error) {
@@ -102,9 +123,9 @@ func (s *SingleResource) Abort(err error) {
 	defer s.mtx.Unlock()
 
 	for _, w := range s.waitQueue {
-		w <- err
+		w.c <- err
 	}
-	s.waitQueue = [](chan error){}
+	s.waitQueue = nil
 }
 
 type ErrLessFn func(a error, b error) bool
@@ -122,6 +143,7 @@ type ErrFunnel struct {
 	curErr   error
 	errTimer *time.Timer
 	started  bool
+	waiters  [](chan error)
 }
 
 func (f *ErrFunnel) Start() {
@@ -175,8 +197,13 @@ func (f *ErrFunnel) Reset() {
 
 func (f *ErrFunnel) timerExp() {
 	f.mtx.Lock()
+
 	err := f.curErr
 	f.curErr = nil
+
+	waiters := f.waiters
+	f.waiters = nil
+
 	f.mtx.Unlock()
 
 	if err == nil {
@@ -184,4 +211,34 @@ func (f *ErrFunnel) timerExp() {
 	}
 
 	f.ProcCb(err)
+
+	for _, w := range waiters {
+		w <- err
+	}
+}
+
+func (f *ErrFunnel) Wait() error {
+	var err error
+	var c chan error
+
+	f.mtx.Lock()
+
+	if !f.started {
+		if f.curErr == nil {
+			err = fmt.Errorf("Wait on unstarted ErrFunnel")
+		} else {
+			err = f.curErr
+		}
+	} else {
+		c = make(chan error)
+		f.waiters = append(f.waiters, c)
+	}
+
+	f.mtx.Unlock()
+
+	if err != nil {
+		return err
+	} else {
+		return <-c
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/scan/scan.go
----------------------------------------------------------------------
diff --git a/nmxact/scan/scan.go b/nmxact/scan/scan.go
new file mode 100644
index 0000000..c90546e
--- /dev/null
+++ b/nmxact/scan/scan.go
@@ -0,0 +1,48 @@
+package scan
+
+import (
+	"mynewt.apache.org/newtmgr/nmxact/bledefs"
+	"mynewt.apache.org/newtmgr/nmxact/sesn"
+)
+
+type ScanPeer struct {
+	HwId   []byte
+	Opaque interface{}
+}
+
+type ScanFn func(peer ScanPeer)
+
+type Cfg struct {
+	ScanCb  ScanFn
+	SesnCfg sesn.SesnCfg
+}
+
+type Scanner interface {
+	Start(cfg Cfg) error
+	Stop() error
+}
+
+func BleOmpScanCfg(ScanCb ScanFn) Cfg {
+	sc := sesn.NewSesnCfg()
+	sc.MgmtProto = sesn.MGMT_PROTO_OMP
+	sc.Ble.OwnAddrType = bledefs.BLE_ADDR_TYPE_RANDOM
+	sc.Ble.Encrypt = bledefs.BLE_ENCRYPT_PRIV_ONLY
+	sc.Ble.PeerSpec = sesn.BlePeerSpec{
+		ScanPred: func(adv bledefs.BleAdvReport) bool {
+			for _, u := range adv.Uuids16 {
+				if u == bledefs.OmpSvcUuid {
+					return true
+				}
+			}
+
+			return false
+		},
+	}
+
+	cfg := Cfg{
+		ScanCb:  ScanCb,
+		SesnCfg: sc,
+	}
+
+	return cfg
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/sesn/sesn_cfg.go
----------------------------------------------------------------------
diff --git a/nmxact/sesn/sesn_cfg.go b/nmxact/sesn/sesn_cfg.go
index 60cdc81..0022640 100644
--- a/nmxact/sesn/sesn_cfg.go
+++ b/nmxact/sesn/sesn_cfg.go
@@ -47,6 +47,8 @@ type SesnCfgBle struct {
 	ConnTries    int
 	CloseTimeout time.Duration
 	OnCloseCb    BleOnCloseFn
+
+	Encrypt bledefs.BleEncryptWhen
 }
 
 type SesnCfg struct {

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newtmgr/blob/822cea97/nmxact/xport/xport.go
----------------------------------------------------------------------
diff --git a/nmxact/xport/xport.go b/nmxact/xport/xport.go
index 85ef2b2..ec8a499 100644
--- a/nmxact/xport/xport.go
+++ b/nmxact/xport/xport.go
@@ -1,6 +1,7 @@
 package xport
 
 import (
+	"mynewt.apache.org/newtmgr/nmxact/scan"
 	"mynewt.apache.org/newtmgr/nmxact/sesn"
 )
 
@@ -9,7 +10,9 @@ type RxFn func(data []byte)
 type Xport interface {
 	Start() error
 	Stop() error
+
 	BuildSesn(cfg sesn.SesnCfg) (sesn.Sesn, error)
+	BuildScanner() (scan.Scanner, error)
 
 	Tx(data []byte) error
 }