diff --git a/conn.go b/conn.go index d260dca6..564f072f 100644 --- a/conn.go +++ b/conn.go @@ -3,6 +3,7 @@ package ble import ( "context" "io" + "time" ) // Conn implements a L2CAP connection. @@ -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{} } diff --git a/darwin/conn.go b/darwin/conn.go index 9aaba428..7f26447b 100644 --- a/darwin/conn.go +++ b/darwin/conn.go @@ -2,9 +2,11 @@ package darwin import ( "context" + "errors" "fmt" "log" "sync" + "time" "github.com/JuulLabs-OSS/cbgo" "github.com/go-ble/ble" @@ -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. diff --git a/darwin/device.go b/darwin/device.go index 05996e42..3d3882ef 100644 --- a/darwin/device.go +++ b/darwin/device.go @@ -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") +} diff --git a/device.go b/device.go index ced686eb..1b3dfd22 100644 --- a/device.go +++ b/device.go @@ -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 } diff --git a/linux/device.go b/linux/device.go index 4876c50c..4e79efd8 100644 --- a/linux/device.go +++ b/linux/device.go @@ -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) +} diff --git a/linux/hci/cmd/phy.go b/linux/hci/cmd/phy.go new file mode 100644 index 00000000..142f1533 --- /dev/null +++ b/linux/hci/cmd/phy.go @@ -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) +} diff --git a/linux/hci/conn.go b/linux/hci/conn.go index 24ff0c5f..3fafb9b3 100644 --- a/linux/hci/conn.go +++ b/linux/hci/conn.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net" + "time" "github.com/go-ble/ble" "github.com/go-ble/ble/linux/hci/cmd" @@ -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 { diff --git a/linux/hci/evt/phy.go b/linux/hci/evt/phy.go new file mode 100644 index 00000000..7a1520f8 --- /dev/null +++ b/linux/hci/evt/phy.go @@ -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] } diff --git a/linux/hci/hci.go b/linux/hci/hci.go index bd9b3a80..c93295e9 100644 --- a/linux/hci/hci.go +++ b/linux/hci/hci.go @@ -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), @@ -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 @@ -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() diff --git a/linux/hci/signal.go b/linux/hci/signal.go index 9a18745c..1ecbb211 100644 --- a/linux/hci/signal.go +++ b/linux/hci/signal.go @@ -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 {