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
10 changes: 10 additions & 0 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ble
import (
"context"
"io"
"time"
)

// Conn implements a L2CAP connection.
Expand Down Expand Up @@ -36,6 +37,15 @@ type Conn interface {
// ReadRSSI retrieves the current RSSI value of remote peripheral. [Vol 2, Part E, 7.5.4]
ReadRSSI() int

// ReadPHY retrieves the current PHY settings of the connection.
ReadPHY() (txPhy int, rxPhy int, err error)

// SetPHY sets the PHY for the connection.
SetPHY(txPhy int, rxPhy int) error

// UpdateConnection updates the connection parameters.
UpdateConnection(intervalMin time.Duration, intervalMax time.Duration, latency int, supervisionTimeout time.Duration) error

// Disconnected returns a receiving channel, which is closed when the connection disconnects.
Disconnected() <-chan struct{}
}
20 changes: 20 additions & 0 deletions darwin/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package darwin

import (
"context"
"errors"
"fmt"
"log"
"sync"
"time"

"github.com/JuulLabs-OSS/cbgo"
"github.com/go-ble/ble"
Expand Down Expand Up @@ -159,6 +161,24 @@ func (c *conn) ReadRSSI() int {
}
}

// ReadPHY retrieves the current PHY settings of the connection.
func (c *conn) ReadPHY() (int, int, error) {
// Not implemented for darwin
return 0, 0, errors.New("Not supported")
}

// SetPHY sets the PHY for the connection.
func (c *conn) SetPHY(txPhy int, rxPhy int) error {
// Not implemented for darwin
return errors.New("Not supported")
}

// UpdateConnection updates the connection parameters.
func (c *conn) UpdateConnection(intervalMin time.Duration, intervalMax time.Duration, latency int, supervisionTimeout time.Duration) error {
// Not implemented for darwin
return errors.New("Not supported")
}

// processChrRead handles an incoming read response. CoreBluetooth does not
// distinguish explicit reads from unsolicited notifications. This function
// identifies which type the incoming message is.
Expand Down
6 changes: 6 additions & 0 deletions darwin/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,3 +408,9 @@ func (d *Device) AdvertiseIBeacon(ctx context.Context, u ble.UUID, major, minor
b[20] = uint8(pwr) // Measured Tx Power
return d.AdvertiseIBeaconData(ctx, b)
}

// SetDefaultPHY sets the default PHYs for future connections.
func (d *Device) SetDefaultPHY(txPhy int, rxPhy int) error {
// Not implemented for darwin
return errors.New("Not supported")
}
3 changes: 3 additions & 0 deletions device.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ type Device interface {

// Dial ...
Dial(ctx context.Context, a Addr) (Client, error)

// SetDefaultPHY sets the default PHYs for future connections.
SetDefaultPHY(txPhy int, rxPhy int) error
}
5 changes: 5 additions & 0 deletions linux/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,8 @@ func (d *Device) Dial(ctx context.Context, a ble.Addr) (ble.Client, error) {
func (d *Device) Address() ble.Addr {
return d.HCI.Addr()
}

// SetDefaultPHY sets the default PHYs for future connections.
func (d *Device) SetDefaultPHY(txPhy int, rxPhy int) error {
return d.HCI.SetDefaultPHY(txPhy, rxPhy)
}
79 changes: 79 additions & 0 deletions linux/hci/cmd/phy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cmd

// LEReadPHY implements LE Read PHY (0x08|0x0030) [Vol 2, Part E, 7.8.47]
type LEReadPHY struct {
ConnectionHandle uint16
}

func (c *LEReadPHY) String() string {
return "LE Read PHY (0x08|0x0030)"
}

func (c *LEReadPHY) OpCode() int { return 0x08<<10 | 0x0030 }

func (c *LEReadPHY) Len() int { return 2 }

func (c *LEReadPHY) Marshal(b []byte) error {
return marshal(c, b)
}

// LEReadPHYRP returns the return parameter of LE Read PHY
type LEReadPHYRP struct {
Status uint8
ConnectionHandle uint16
TXPHY uint8
RXPHY uint8
}

func (c *LEReadPHYRP) Unmarshal(b []byte) error {
return unmarshal(c, b)
}

// LESetDefaultPHY implements LE Set Default PHY (0x08|0x0031) [Vol 2, Part E, 7.8.48]
type LESetDefaultPHY struct {
ALLPHYS uint8
TXPHYS uint8
RXPHYS uint8
}

func (c *LESetDefaultPHY) String() string {
return "LE Set Default PHY (0x08|0x0031)"
}

func (c *LESetDefaultPHY) OpCode() int { return 0x08<<10 | 0x0031 }

func (c *LESetDefaultPHY) Len() int { return 3 }

func (c *LESetDefaultPHY) Marshal(b []byte) error {
return marshal(c, b)
}

// LESetDefaultPHYRP returns the return parameter of LE Set Default PHY
type LESetDefaultPHYRP struct {
Status uint8
}

func (c *LESetDefaultPHYRP) Unmarshal(b []byte) error {
return unmarshal(c, b)
}

// LESetPHY implements LE Set PHY (0x08|0x0032) [Vol 2, Part E, 7.8.49]
type LESetPHY struct {
ConnectionHandle uint16
ALLPHYS uint8
TXPHYS uint8
RXPHYS uint8
PHYOptions uint16
}

func (c *LESetPHY) String() string {
return "LE Set PHY (0x08|0x0032)"
}

func (c *LESetPHY) OpCode() int { return 0x08<<10 | 0x0032 }

func (c *LESetPHY) Len() int { return 7 }

func (c *LESetPHY) Marshal(b []byte) error {
return marshal(c, b)
}
56 changes: 56 additions & 0 deletions linux/hci/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net"
"time"

"github.com/go-ble/ble"
"github.com/go-ble/ble/linux/hci/cmd"
Expand Down Expand Up @@ -306,6 +307,61 @@ func (c *Conn) ReadRSSI() int {
return int(rp.RSSI)
}

// ReadPHY retrieves the current PHY settings of the connection. [Vol 2, Part E, 7.8.47]
func (c *Conn) ReadPHY() (int, int, error) {
rp := new(cmd.LEReadPHYRP)
err := c.hci.Send(&cmd.LEReadPHY{
ConnectionHandle: c.param.ConnectionHandle(),
}, rp)
return int(rp.TXPHY), int(rp.RXPHY), err
}

// SetPHY sets the PHY for the connection. [Vol 2, Part E, 7.8.49]
func (c *Conn) SetPHY(txPhy int, rxPhy int) error {
return c.hci.Send(&cmd.LESetPHY{
ConnectionHandle: c.param.ConnectionHandle(),
ALLPHYS: 0,
TXPHYS: uint8(txPhy),
RXPHYS: uint8(rxPhy),
PHYOptions: 0,
}, nil)
}

// UpdateConnection updates the connection parameters. [Vol 2, Part E, 7.8.18]
func (c *Conn) UpdateConnection(intervalMin time.Duration, intervalMax time.Duration, latency int, supervisionTimeout time.Duration) error {
min := uint16(intervalMin / (1250 * time.Microsecond))
max := uint16(intervalMax / (1250 * time.Microsecond))
timeout := uint16(supervisionTimeout / (10 * time.Millisecond))

if c.param.Role() != roleMaster {
// As a slave, we should use the L2CAP Connection Parameter Update Request
req := &ConnectionParameterUpdateRequest{
IntervalMin: min,
IntervalMax: max,
SlaveLatency: uint16(latency),
TimeoutMultiplier: timeout,
}
rsp := &ConnectionParameterUpdateResponse{}
if err := c.Signal(req, rsp); err != nil {
return err
}
if rsp.Result != 0 {
return fmt.Errorf("connection parameter update rejected: 0x%04x", rsp.Result)
}
return nil
}

return c.hci.Send(&cmd.LEConnectionUpdate{
ConnectionHandle: c.param.ConnectionHandle(),
ConnIntervalMin: min,
ConnIntervalMax: max,
ConnLatency: uint16(latency),
SupervisionTimeout: timeout,
MinimumCELength: 0x0000,
MaximumCELength: 0x0000,
}, nil)
}

// Close disconnects the connection by sending hci disconnect command to the device.
func (c *Conn) Close() error {
select {
Expand Down
20 changes: 20 additions & 0 deletions linux/hci/evt/phy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package evt

import "encoding/binary"

const LEPHYUpdateCompleteSubCode = 0x0C

// LEPHYUpdateComplete implements LE PHY Update Complete (0x3E:0x0C) [Vol 2, Part E, 7.7.65.12].
type LEPHYUpdateComplete []byte

func (r LEPHYUpdateComplete) SubeventCode() uint8 { return r[0] }

func (r LEPHYUpdateComplete) Status() uint8 { return r[1] }

func (r LEPHYUpdateComplete) ConnectionHandle() uint16 {
return binary.LittleEndian.Uint16(r[2:])
}

func (r LEPHYUpdateComplete) TXPHY() uint8 { return r[4] }

func (r LEPHYUpdateComplete) RXPHY() uint8 { return r[5] }
22 changes: 22 additions & 0 deletions linux/hci/hci.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ func (h *HCI) Init() error {
h.subh[evt.LEConnectionCompleteSubCode] = h.handleLEConnectionComplete
h.subh[evt.LEConnectionUpdateCompleteSubCode] = h.handleLEConnectionUpdateComplete
h.subh[evt.LELongTermKeyRequestSubCode] = h.handleLELongTermKeyRequest
h.subh[evt.LEPHYUpdateCompleteSubCode] = h.handleLEPHYUpdateComplete
// evt.EncryptionChangeCode: todo),
// evt.ReadRemoteVersionInformationCompleteCode: todo),
// evt.HardwareErrorCode: todo),
Expand Down Expand Up @@ -230,6 +231,23 @@ func (h *HCI) init() error {
return h.err
}

// SetDefaultPHY sets the default PHYs for future connections. [Vol 2, Part E, 7.8.48]
func (h *HCI) SetDefaultPHY(txPhy int, rxPhy int) error {
rp := new(cmd.LESetDefaultPHYRP)
err := h.Send(&cmd.LESetDefaultPHY{
ALLPHYS: 0,
TXPHYS: uint8(txPhy),
RXPHYS: uint8(rxPhy),
}, rp)
if err != nil {
return err
}
if rp.Status != 0 {
return fmt.Errorf("failed to set default PHY: status 0x%02x", rp.Status)
}
return nil
}

// Send ...
func (h *HCI) Send(c Command, r CommandRP) error {
// Only allow one send after another to prevent race condition
Expand Down Expand Up @@ -527,6 +545,10 @@ func (h *HCI) handleLEConnectionUpdateComplete(b []byte) error {
return nil
}

func (h *HCI) handleLEPHYUpdateComplete(b []byte) error {
return nil
}

func (h *HCI) handleDisconnectionComplete(b []byte) error {
e := evt.DisconnectionComplete(b)
h.muConns.Lock()
Expand Down
3 changes: 2 additions & 1 deletion linux/hci/signal.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ func (c *Conn) Signal(req Signal, rsp Signal) error {
return errors.New("signaling request timed out")
}

if s.code() != req.Code() {
// The connection parameter update request will trigger an update response.
if s.code() != req.Code() && !(req.Code() == SignalConnectionParameterUpdateRequest && s.code() == SignalConnectionParameterUpdateResponse) {
return errors.New("mismatched signaling response")
}
if s.id() != c.sigID {
Expand Down