bluetooth/gap_ninafw.go
Ayke van Laethem d74f6a1009 all: add RequestConnectionParams to request new connection parameters
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.
2024-01-11 15:53:20 +01:00

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()
}