d74f6a1009
This allows changing the connection latency, slave latency, and connection timeout of an active connection - whether in the central or peripheral role. This is especially helpful on battery operated BLE devices that don't have a lot of power and need to lower the connection latency for improved speed. It might also be useful for devices that need high speed, as the defaults might be too low.
264 lines
5.3 KiB
Go
264 lines
5.3 KiB
Go
//go:build ninafw
|
|
|
|
package bluetooth
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
const defaultMTU = 23
|
|
|
|
var (
|
|
ErrConnect = errors.New("bluetooth: could not connect")
|
|
)
|
|
|
|
// Scan starts a BLE scan.
|
|
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
|
if a.scanning {
|
|
return errScanning
|
|
}
|
|
|
|
if err := a.hci.leSetScanEnable(false, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
// passive scanning, every 40ms, for 30ms
|
|
if err := a.hci.leSetScanParameters(0x00, 0x0080, 0x0030, 0x00, 0x00); err != nil {
|
|
return err
|
|
}
|
|
|
|
a.scanning = true
|
|
|
|
// scan with duplicates
|
|
if err := a.hci.leSetScanEnable(true, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
lastUpdate := time.Now().UnixNano()
|
|
|
|
for {
|
|
if err := a.hci.poll(); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch {
|
|
case a.hci.advData.reported:
|
|
adf := AdvertisementFields{}
|
|
if a.hci.advData.eirLength > 31 {
|
|
if _debug {
|
|
println("eirLength too long")
|
|
}
|
|
|
|
a.hci.clearAdvData()
|
|
continue
|
|
}
|
|
|
|
for i := 0; i < int(a.hci.advData.eirLength); {
|
|
l, t := int(a.hci.advData.eirData[i]), a.hci.advData.eirData[i+1]
|
|
if l < 1 {
|
|
break
|
|
}
|
|
|
|
switch t {
|
|
case 0x02, 0x03:
|
|
// 16-bit Service Class UUID
|
|
case 0x06, 0x07:
|
|
// 128-bit Service Class UUID
|
|
case 0x08, 0x09:
|
|
if _debug {
|
|
println("local name", string(a.hci.advData.eirData[i+2:i+1+l]))
|
|
}
|
|
|
|
adf.LocalName = string(a.hci.advData.eirData[i+2 : i+1+l])
|
|
case 0xFF:
|
|
// Manufacturer Specific Data
|
|
}
|
|
|
|
i += l + 1
|
|
}
|
|
|
|
random := a.hci.advData.peerBdaddrType == 0x01
|
|
|
|
callback(a, ScanResult{
|
|
Address: Address{
|
|
MACAddress{
|
|
MAC: makeAddress(a.hci.advData.peerBdaddr),
|
|
isRandom: random,
|
|
},
|
|
},
|
|
RSSI: int16(a.hci.advData.rssi),
|
|
AdvertisementPayload: &advertisementFields{
|
|
AdvertisementFields: adf,
|
|
},
|
|
})
|
|
|
|
a.hci.clearAdvData()
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
default:
|
|
if !a.scanning {
|
|
return nil
|
|
}
|
|
|
|
if _debug && (time.Now().UnixNano()-lastUpdate)/int64(time.Second) > 1 {
|
|
println("still scanning...")
|
|
lastUpdate = time.Now().UnixNano()
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *Adapter) StopScan() error {
|
|
if !a.scanning {
|
|
return errNotScanning
|
|
}
|
|
|
|
if err := a.hci.leSetScanEnable(false, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
a.scanning = false
|
|
|
|
return nil
|
|
}
|
|
|
|
// Address contains a Bluetooth MAC address.
|
|
type Address struct {
|
|
MACAddress
|
|
}
|
|
|
|
// Connect starts a connection attempt to the given peripheral device address.
|
|
func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
|
|
if _debug {
|
|
println("Connect")
|
|
}
|
|
|
|
random := uint8(0)
|
|
if address.isRandom {
|
|
random = 1
|
|
}
|
|
if err := a.hci.leCreateConn(0x0060, 0x0030, 0x00,
|
|
random, makeNINAAddress(address.MAC),
|
|
0x00, 0x0006, 0x000c, 0x0000, 0x00c8, 0x0004, 0x0006); err != nil {
|
|
return Device{}, err
|
|
}
|
|
|
|
// are we connected?
|
|
start := time.Now().UnixNano()
|
|
for {
|
|
if err := a.hci.poll(); err != nil {
|
|
return Device{}, err
|
|
}
|
|
|
|
if a.hci.connectData.connected {
|
|
defer a.hci.clearConnectData()
|
|
|
|
random := false
|
|
if address.isRandom {
|
|
random = true
|
|
}
|
|
|
|
d := Device{
|
|
Address: Address{
|
|
MACAddress{
|
|
MAC: makeAddress(a.hci.connectData.peerBdaddr),
|
|
isRandom: random},
|
|
},
|
|
deviceInternal: &deviceInternal{
|
|
adapter: a,
|
|
handle: a.hci.connectData.handle,
|
|
mtu: defaultMTU,
|
|
notificationRegistrations: make([]notificationRegistration, 0),
|
|
},
|
|
}
|
|
a.connectedDevices = append(a.connectedDevices, d)
|
|
|
|
return d, nil
|
|
|
|
} else {
|
|
// check for timeout
|
|
if (time.Now().UnixNano()-start)/int64(time.Second) > 5 {
|
|
break
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// cancel connection attempt that failed
|
|
if err := a.hci.leCancelConn(); err != nil {
|
|
return Device{}, err
|
|
}
|
|
|
|
return Device{}, ErrConnect
|
|
}
|
|
|
|
type notificationRegistration struct {
|
|
handle uint16
|
|
callback func([]byte)
|
|
}
|
|
|
|
// Device is a connection to a remote peripheral.
|
|
type Device struct {
|
|
Address Address
|
|
*deviceInternal
|
|
}
|
|
|
|
type deviceInternal struct {
|
|
adapter *Adapter
|
|
handle uint16
|
|
mtu uint16
|
|
|
|
notificationRegistrations []notificationRegistration
|
|
}
|
|
|
|
// Disconnect from the BLE device.
|
|
func (d Device) Disconnect() error {
|
|
if _debug {
|
|
println("Disconnect")
|
|
}
|
|
if err := d.adapter.hci.disconnect(d.handle); err != nil {
|
|
return err
|
|
}
|
|
|
|
d.adapter.connectedDevices = []Device{}
|
|
return nil
|
|
}
|
|
|
|
// RequestConnectionParams requests a different connection latency and timeout
|
|
// of the given device connection. Fields that are unset will be left alone.
|
|
// Whether or not the device will actually honor this, depends on the device and
|
|
// on the specific parameters.
|
|
//
|
|
// On NINA, this call hasn't been implemented yet.
|
|
func (d Device) RequestConnectionParams(params ConnectionParams) error {
|
|
return nil
|
|
}
|
|
|
|
func (d Device) findNotificationRegistration(handle uint16) *notificationRegistration {
|
|
for _, n := range d.notificationRegistrations {
|
|
if n.handle == handle {
|
|
return &n
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d Device) addNotificationRegistration(handle uint16, callback func([]byte)) {
|
|
d.notificationRegistrations = append(d.notificationRegistrations,
|
|
notificationRegistration{
|
|
handle: handle,
|
|
callback: callback,
|
|
})
|
|
}
|
|
|
|
func (d Device) startNotifications() {
|
|
d.adapter.startNotifications()
|
|
}
|