Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
.tags1

/examples/bin/*
vendor
.idea
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
[![codebeat badge](https://codebeat.co/badges/ba9fae6e-77d2-4173-8587-36ac8756676b)](https://codebeat.co/projects/github-com-go-ble-ble-master)
[![Build Status](https://travis-ci.org/go-ble/ble.svg?branch=master)](https://travis-ci.org/go-ble/ble)

## ⚠️ Archive Notice ⚠️
I sadly have no time to maintain this repository anymore. If you want a version, which is activly maintained and developed take a look at [https://github.com/rigado/ble](https://github.com/rigado/ble)

**ble** is a [Bluetooth Low Energy](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) package for Linux and macOS.
**ble** is a Golang [Bluetooth Low Energy](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) package for Linux and Mac OS.

**Note:** The Mac OS portion is not being actively maintained.
10 changes: 10 additions & 0 deletions darwin/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,13 @@ func (d *Device) SetListenerTimeout(dur time.Duration) error {
func (d *Device) SetConnParams(param cmd.LECreateConnection) error {
return errors.New("Not supported")
}

// SetScanParams overrides default scanning parameters.
func (d *Device) SetScanParams(param cmd.LESetScanParameters) error {
return errors.New("Not supported")
}

// SetAdvParams overrides default advertising parameters.
func (d *Device) SetAdvParams(param cmd.LESetAdvertisingParameters) error {
return errors.New("Not supported")
}
3 changes: 3 additions & 0 deletions linux/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,20 @@ func NewDeviceWithNameAndHandler(name string, handler ble.NotifyHandler, opts ..
return nil, errors.Wrap(err, "can't create hci")
}
if err = dev.Init(); err != nil {
dev.Close()
return nil, errors.Wrap(err, "can't init hci")
}

srv, err := gatt.NewServerWithNameAndHandler(name, handler)
if err != nil {
dev.Close()
return nil, errors.Wrap(err, "can't create server")
}

// mtu := ble.DefaultMTU
mtu := ble.MaxMTU // TODO: get this from user using Option.
if mtu > ble.MaxMTU {
dev.Close()
return nil, errors.Wrapf(err, "maximum ATT_MTU is %d", ble.MaxMTU)
}

Expand Down
9 changes: 9 additions & 0 deletions linux/hci/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,15 @@ func (c *Conn) writePDU(pdu []byte) (int, error) {
c.txBuffer.LockPool()
defer c.txBuffer.UnlockPool()

// Fail immediately if the connection is already closed
// Check this with the pool locked to avoid race conditions
// with handleDisconnectionComplete
select {
case <-c.chDone:
return 0, io.ErrClosedPipe
default:
}

for len(pdu) > 0 {
// Get a buffer from our pre-allocated and flow-controlled pool.
pkt := c.txBuffer.Get() // ACL pkt
Expand Down
69 changes: 57 additions & 12 deletions linux/hci/hci.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (h *HCI) Init() error {
}
h.skt = skt

h.chCmdBufs <- make([]byte, 64)
h.setAllowedCommands(1)

go h.sktLoop()
if err := h.init(); err != nil {
Expand Down Expand Up @@ -259,12 +259,34 @@ func (h *HCI) send(c Command) ([]byte, error) {
h.close(fmt.Errorf("hci: failed to send whole cmd pkt to hci socket"))
}

var ret []byte
var err error

// emergency timeout to prevent calls from locking up if the HCI
// interface doesn't respond. Responsed here should normally be fast
// a timeout indicates a major problem with HCI.
timeout := time.NewTimer(10 * time.Second)
select {
case <-timeout.C:
err = fmt.Errorf("hci: no response to command, hci connection failed")
ret = nil
case <-h.done:
return nil, h.err
err = h.err
ret = nil
case b := <-p.done:
return b, nil
err = nil
ret = b
}
timeout.Stop()

// clear sent table when done, we sometimes get command complete or
// command status messages with no matching send, which can attempt to
// access stale packets in sent and fail or lock up.
h.muSent.Lock()
delete(h.sent, c.OpCode())
h.muSent.Unlock()

return ret, err
}

func (h *HCI) sktLoop() {
Expand All @@ -273,7 +295,11 @@ func (h *HCI) sktLoop() {
for {
n, err := h.skt.Read(b)
if n == 0 || err != nil {
h.err = fmt.Errorf("skt: %s", err)
if err == io.EOF {
h.err = err //callers depend on detecting io.EOF, don't wrap it.
} else {
h.err = fmt.Errorf("skt: %s", err)
}
return
}
p := make([]byte, n)
Expand All @@ -293,7 +319,10 @@ func (h *HCI) sktLoop() {

func (h *HCI) close(err error) error {
h.err = err
return h.skt.Close()
if h.skt != nil {
return h.skt.Close()
}
return err
}

func (h *HCI) handlePkt(b []byte) error {
Expand Down Expand Up @@ -407,13 +436,11 @@ func (h *HCI) handleLEAdvertisingReport(b []byte) error {

func (h *HCI) handleCommandComplete(b []byte) error {
e := evt.CommandComplete(b)
for i := 0; i < int(e.NumHCICommandPackets()); i++ {
h.chCmdBufs <- make([]byte, 64)
}
h.setAllowedCommands(int(e.NumHCICommandPackets()))

// NOP command, used for flow control purpose [Vol 2, Part E, 4.4]
// no handling other than setAllowedCommands needed
if e.CommandOpcode() == 0x0000 {
h.chCmdBufs = make(chan []byte, 16)
return nil
}
h.muSent.Lock()
Expand All @@ -428,9 +455,7 @@ func (h *HCI) handleCommandComplete(b []byte) error {

func (h *HCI) handleCommandStatus(b []byte) error {
e := evt.CommandStatus(b)
for i := 0; i < int(e.NumHCICommandPackets()); i++ {
h.chCmdBufs <- make([]byte, 64)
}
h.setAllowedCommands(int(e.NumHCICommandPackets()))

h.muSent.Lock()
p, found := h.sent[int(e.CommandOpcode())]
Expand Down Expand Up @@ -514,7 +539,13 @@ func (h *HCI) handleDisconnectionComplete(b []byte) error {
}
// When a connection disconnects, all the sent packets and weren't acked yet
// will be recycled. [Vol2, Part E 4.1.1]
//
// must be done with the pool locked to avoid race conditions where
// writePDU is in progress and does a Get from the pool after this completes,
// leaking a buffer from the main pool.
c.txBuffer.LockPool()
c.txBuffer.PutAll()
c.txBuffer.UnlockPool()
return nil
}

Expand Down Expand Up @@ -542,3 +573,17 @@ func (h *HCI) handleLELongTermKeyRequest(b []byte) error {
ConnectionHandle: e.ConnectionHandle(),
}, nil)
}

func (h *HCI) setAllowedCommands(n int) {

//hard-coded limit to command queue depth
//matches make(chan []byte, 16) in NewHCI
// TODO make this a constant, decide correct size
if n > 16 {
n = 16
}

for len(h.chCmdBufs) < n {
h.chCmdBufs <- make([]byte, 64) // TODO make buffer size a constant
}
}
12 changes: 12 additions & 0 deletions linux/hci/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ func (h *HCI) SetConnParams(param cmd.LECreateConnection) error {
return nil
}

// SetScanParams overrides default scanning parameters.
func (h *HCI) SetScanParams(param cmd.LESetScanParameters) error {
h.params.scanParams = param
return nil
}

// SetAdvParams overrides default advertising parameters.
func (h *HCI) SetAdvParams(param cmd.LESetAdvertisingParameters) error {
h.params.advParams = param
return nil
}

// SetPeripheralRole is not supported
func (h *HCI) SetPeripheralRole() error {
return errors.New("Not supported")
Expand Down
15 changes: 11 additions & 4 deletions linux/hci/socket/socket.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,21 @@ func open(fd, id int) (*Socket, error) {
}

func (s *Socket) Read(p []byte) (int, error) {
s.rmu.Lock()
n, err := unix.Read(s.fd, p)
s.rmu.Unlock()
// Close always sends a dummy command to wake up Read
// bad things happen to the HCI state machines if they receive
// a reply from that command, so make sure no data is returned
// on a closed socket.
//
// note that if Write and Close are called concurrently it's
// indeterminate which replies get through.
select {
case <-s.closed:
return 0, io.EOF
default:
}
s.rmu.Lock()
defer s.rmu.Unlock()
n, err := unix.Read(s.fd, p)
return n, errors.Wrap(err, "can't read hci socket")
}

Expand All @@ -139,7 +146,7 @@ func (s *Socket) Write(p []byte) (int, error) {

func (s *Socket) Close() error {
close(s.closed)
s.Write([]byte{0x01, 0x09, 0x10, 0x00})
s.Write([]byte{0x01, 0x09, 0x10, 0x00}) // no-op command to wake up the Read call if it's blocked
s.rmu.Lock()
defer s.rmu.Unlock()
return errors.Wrap(unix.Close(s.fd), "can't close hci socket")
Expand Down
18 changes: 18 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type DeviceOption interface {
SetDialerTimeout(time.Duration) error
SetListenerTimeout(time.Duration) error
SetConnParams(cmd.LECreateConnection) error
SetScanParams(cmd.LESetScanParameters) error
SetAdvParams(cmd.LESetAdvertisingParameters) error
SetPeripheralRole() error
SetCentralRole() error
}
Expand Down Expand Up @@ -51,6 +53,22 @@ func OptConnParams(param cmd.LECreateConnection) Option {
}
}

// OptScanParams overrides default scanning parameters.
func OptScanParams(param cmd.LESetScanParameters) Option {
return func(opt DeviceOption) error {
opt.SetScanParams(param)
return nil
}
}

// OptAdvParams overrides default advertising parameters.
func OptAdvParams(param cmd.LESetAdvertisingParameters) Option {
return func(opt DeviceOption) error {
opt.SetAdvParams(param)
return nil
}
}

// OptPeripheralRole configures the device to perform Peripheral tasks.
func OptPeripheralRole() Option {
return func(opt DeviceOption) error {
Expand Down