all: make Device a value instead of a pointer

This is a refactor that is necessary to make it easier to work with
connected central devices on a SoftDevice.
This commit is contained in:
Ayke van Laethem 2023-12-25 14:28:56 +01:00 committed by Ron Evans
parent 6e0df0ec3c
commit c9eafaff20
12 changed files with 75 additions and 62 deletions

View file

@ -21,7 +21,7 @@ type Adapter struct {
connectHandler func(device Address, connected bool) connectHandler func(device Address, connected bool)
connectedDevices []*Device connectedDevices []Device
notificationsStarted bool notificationsStarted bool
} }
@ -33,7 +33,7 @@ var DefaultAdapter = &Adapter{
connectHandler: func(device Address, connected bool) { connectHandler: func(device Address, connected bool) {
return return
}, },
connectedDevices: make([]*Device, 0, maxConnections), connectedDevices: make([]Device, 0, maxConnections),
} }
// Enable configures the BLE stack. It must be called before any // Enable configures the BLE stack. It must be called before any
@ -185,7 +185,7 @@ func (a *Adapter) startNotifications() {
} }
d := a.findDevice(not.connectionHandle) d := a.findDevice(not.connectionHandle)
if d == nil { if d.deviceInternal == nil {
if _debug { if _debug {
println("no device found for handle", not.connectionHandle) println("no device found for handle", not.connectionHandle)
} }
@ -212,7 +212,7 @@ func (a *Adapter) startNotifications() {
}() }()
} }
func (a *Adapter) findDevice(handle uint16) *Device { func (a *Adapter) findDevice(handle uint16) Device {
for _, d := range a.connectedDevices { for _, d := range a.connectedDevices {
if d.handle == handle { if d.handle == handle {
if _debug { if _debug {
@ -223,5 +223,5 @@ func (a *Adapter) findDevice(handle uint16) *Device {
} }
} }
return nil return Device{}
} }

View file

@ -43,7 +43,7 @@ func main() {
} }
}) })
var device *bluetooth.Device var device bluetooth.Device
select { select {
case result := <-ch: case result := <-ch:
device, err = adapter.Connect(result.Address, bluetooth.ConnectionParams{}) device, err = adapter.Connect(result.Address, bluetooth.ConnectionParams{})

View file

@ -52,7 +52,7 @@ func main() {
} }
}) })
var device *bluetooth.Device var device bluetooth.Device
select { select {
case result := <-ch: case result := <-ch:
device, err = adapter.Connect(result.Address, bluetooth.ConnectionParams{}) device, err = adapter.Connect(result.Address, bluetooth.ConnectionParams{})

View file

@ -85,6 +85,10 @@ func (a *Adapter) StopScan() error {
// Device is a connection to a remote peripheral. // Device is a connection to a remote peripheral.
type Device struct { type Device struct {
*deviceInternal
}
type deviceInternal struct {
delegate *peripheralDelegate delegate *peripheralDelegate
cm cbgo.CentralManager cm cbgo.CentralManager
@ -97,14 +101,14 @@ type Device struct {
} }
// Connect starts a connection attempt to the given peripheral device address. // Connect starts a connection attempt to the given peripheral device address.
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) { func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
uuid, err := cbgo.ParseUUID(address.UUID.String()) uuid, err := cbgo.ParseUUID(address.UUID.String())
if err != nil { if err != nil {
return nil, err return Device{}, err
} }
prphs := a.cm.RetrievePeripheralsWithIdentifiers([]cbgo.UUID{uuid}) prphs := a.cm.RetrievePeripheralsWithIdentifiers([]cbgo.UUID{uuid})
if len(prphs) == 0 { if len(prphs) == 0 {
return nil, fmt.Errorf("Connect failed: no peer with address: %s", address.UUID.String()) return Device{}, fmt.Errorf("Connect failed: no peer with address: %s", address.UUID.String())
} }
timeout := defaultConnectionTimeout timeout := defaultConnectionTimeout
@ -129,14 +133,16 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// check if we have received a disconnected peripheral // check if we have received a disconnected peripheral
if p.State() == cbgo.PeripheralStateDisconnected { if p.State() == cbgo.PeripheralStateDisconnected {
return nil, connectionError return Device{}, connectionError
} }
d := &Device{ d := Device{
&deviceInternal{
cm: a.cm, cm: a.cm,
prph: p, prph: p,
servicesChan: make(chan error), servicesChan: make(chan error),
charsChan: make(chan error), charsChan: make(chan error),
},
} }
d.delegate = &peripheralDelegate{d: d} d.delegate = &peripheralDelegate{d: d}
@ -162,7 +168,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// Disconnect from the BLE device. This method is non-blocking and does not // Disconnect from the BLE device. This method is non-blocking and does not
// wait until the connection is fully gone. // wait until the connection is fully gone.
func (d *Device) Disconnect() error { func (d Device) Disconnect() error {
d.cm.CancelConnect(d.prph) d.cm.CancelConnect(d.prph)
return nil return nil
} }
@ -172,7 +178,7 @@ func (d *Device) Disconnect() error {
type peripheralDelegate struct { type peripheralDelegate struct {
cbgo.PeripheralDelegateBase cbgo.PeripheralDelegateBase
d *Device d Device
} }
// DidDiscoverServices is called when the services for a Peripheral // DidDiscoverServices is called when the services for a Peripheral

View file

@ -300,9 +300,9 @@ type Device struct {
// Connect starts a connection attempt to the given peripheral device address. // Connect starts a connection attempt to the given peripheral device address.
// //
// On Linux and Windows, the IsRandom part of the address is ignored. // On Linux and Windows, the IsRandom part of the address is ignored.
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) { func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
devicePath := dbus.ObjectPath(string(a.adapter.Path()) + "/dev_" + strings.Replace(address.MAC.String(), ":", "_", -1)) devicePath := dbus.ObjectPath(string(a.adapter.Path()) + "/dev_" + strings.Replace(address.MAC.String(), ":", "_", -1))
device := &Device{ device := Device{
device: a.bus.Object("org.bluez", devicePath), device: a.bus.Object("org.bluez", devicePath),
adapter: a, adapter: a,
address: address, address: address,
@ -321,7 +321,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// Read whether this device is already connected. // Read whether this device is already connected.
connected, err := device.device.GetProperty("org.bluez.Device1.Connected") connected, err := device.device.GetProperty("org.bluez.Device1.Connected")
if err != nil { if err != nil {
return nil, err return Device{}, err
} }
// Connect to the device, if not already connected. // Connect to the device, if not already connected.
@ -329,7 +329,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// Start connecting (async). // Start connecting (async).
err := device.device.Call("org.bluez.Device1.Connect", 0).Err err := device.device.Call("org.bluez.Device1.Connect", 0).Err
if err != nil { if err != nil {
return nil, fmt.Errorf("bluetooth: failed to connect: %w", err) return Device{}, fmt.Errorf("bluetooth: failed to connect: %w", err)
} }
// Wait until the device has connected. // Wait until the device has connected.
@ -360,7 +360,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// Disconnect from the BLE device. This method is non-blocking and does not // Disconnect from the BLE device. This method is non-blocking and does not
// wait until the connection is fully gone. // wait until the connection is fully gone.
func (d *Device) Disconnect() error { func (d Device) Disconnect() error {
// we don't call our cancel function here, instead we wait for the // we don't call our cancel function here, instead we wait for the
// property change in `watchForConnect` and cancel things then // property change in `watchForConnect` and cancel things then
return d.device.Call("org.bluez.Device1.Disconnect", 0).Err return d.device.Call("org.bluez.Device1.Disconnect", 0).Err

View file

@ -133,7 +133,7 @@ type Address struct {
} }
// Connect starts a connection attempt to the given peripheral device address. // Connect starts a connection attempt to the given peripheral device address.
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) { func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
if _debug { if _debug {
println("Connect") println("Connect")
} }
@ -145,14 +145,14 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
if err := a.hci.leCreateConn(0x0060, 0x0030, 0x00, if err := a.hci.leCreateConn(0x0060, 0x0030, 0x00,
random, makeNINAAddress(address.MAC), random, makeNINAAddress(address.MAC),
0x00, 0x0006, 0x000c, 0x0000, 0x00c8, 0x0004, 0x0006); err != nil { 0x00, 0x0006, 0x000c, 0x0000, 0x00c8, 0x0004, 0x0006); err != nil {
return nil, err return Device{}, err
} }
// are we connected? // are we connected?
start := time.Now().UnixNano() start := time.Now().UnixNano()
for { for {
if err := a.hci.poll(); err != nil { if err := a.hci.poll(); err != nil {
return nil, err return Device{}, err
} }
if a.hci.connectData.connected { if a.hci.connectData.connected {
@ -163,15 +163,18 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
random = true random = true
} }
d := &Device{adapter: a, d := Device{
handle: a.hci.connectData.handle,
Address: Address{ Address: Address{
MACAddress{ MACAddress{
MAC: makeAddress(a.hci.connectData.peerBdaddr), MAC: makeAddress(a.hci.connectData.peerBdaddr),
isRandom: random}, isRandom: random},
}, },
deviceInternal: &deviceInternal{
adapter: a,
handle: a.hci.connectData.handle,
mtu: defaultMTU, mtu: defaultMTU,
notificationRegistrations: make([]notificationRegistration, 0), notificationRegistrations: make([]notificationRegistration, 0),
},
} }
a.connectedDevices = append(a.connectedDevices, d) a.connectedDevices = append(a.connectedDevices, d)
@ -189,10 +192,10 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// cancel connection attempt that failed // cancel connection attempt that failed
if err := a.hci.leCancelConn(); err != nil { if err := a.hci.leCancelConn(); err != nil {
return nil, err return Device{}, err
} }
return nil, ErrConnect return Device{}, ErrConnect
} }
type notificationRegistration struct { type notificationRegistration struct {
@ -202,8 +205,12 @@ type notificationRegistration struct {
// Device is a connection to a remote peripheral. // Device is a connection to a remote peripheral.
type Device struct { type Device struct {
adapter *Adapter
Address Address Address Address
*deviceInternal
}
type deviceInternal struct {
adapter *Adapter
handle uint16 handle uint16
mtu uint16 mtu uint16
@ -211,7 +218,7 @@ type Device struct {
} }
// Disconnect from the BLE device. // Disconnect from the BLE device.
func (d *Device) Disconnect() error { func (d Device) Disconnect() error {
if _debug { if _debug {
println("Disconnect") println("Disconnect")
} }
@ -219,11 +226,11 @@ func (d *Device) Disconnect() error {
return err return err
} }
d.adapter.connectedDevices = []*Device{} d.adapter.connectedDevices = []Device{}
return nil return nil
} }
func (d *Device) findNotificationRegistration(handle uint16) *notificationRegistration { func (d Device) findNotificationRegistration(handle uint16) *notificationRegistration {
for _, n := range d.notificationRegistrations { for _, n := range d.notificationRegistrations {
if n.handle == handle { if n.handle == handle {
return &n return &n
@ -233,7 +240,7 @@ func (d *Device) findNotificationRegistration(handle uint16) *notificationRegist
return nil return nil
} }
func (d *Device) addNotificationRegistration(handle uint16, callback func([]byte)) { func (d Device) addNotificationRegistration(handle uint16, callback func([]byte)) {
d.notificationRegistrations = append(d.notificationRegistrations, d.notificationRegistrations = append(d.notificationRegistrations,
notificationRegistration{ notificationRegistration{
handle: handle, handle: handle,
@ -241,6 +248,6 @@ func (d *Device) addNotificationRegistration(handle uint16, callback func([]byte
}) })
} }
func (d *Device) startNotifications() { func (d Device) startNotifications() {
d.adapter.startNotifications() d.adapter.startNotifications()
} }

View file

@ -109,7 +109,7 @@ var connectionAttempt struct {
// connection attempt at once and that the address parameter must have the // connection attempt at once and that the address parameter must have the
// IsRandom bit set correctly. This bit is set correctly for scan results, so // IsRandom bit set correctly. This bit is set correctly for scan results, so
// you can reuse that address directly. // you can reuse that address directly.
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) { func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
// Construct an address object as used in the SoftDevice. // Construct an address object as used in the SoftDevice.
var addr C.ble_gap_addr_t var addr C.ble_gap_addr_t
addr.addr = makeSDAddress(address.MAC) addr.addr = makeSDAddress(address.MAC)
@ -158,7 +158,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// This should be safe as long as Connect is not called concurrently. And // This should be safe as long as Connect is not called concurrently. And
// even then, it should catch most such race conditions. // even then, it should catch most such race conditions.
if connectionAttempt.state.Get() != 0 { if connectionAttempt.state.Get() != 0 {
return nil, errAlreadyConnecting return Device{}, errAlreadyConnecting
} }
connectionAttempt.state.Set(1) connectionAttempt.state.Set(1)
@ -166,7 +166,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
errCode := C.sd_ble_gap_connect(&addr, &scanParams, &connectionParams, C.BLE_CONN_CFG_TAG_DEFAULT) errCode := C.sd_ble_gap_connect(&addr, &scanParams, &connectionParams, C.BLE_CONN_CFG_TAG_DEFAULT)
if errCode != 0 { if errCode != 0 {
connectionAttempt.state.Set(0) connectionAttempt.state.Set(0)
return nil, Error(errCode) return Device{}, Error(errCode)
} }
// Wait until the connection is established. // Wait until the connection is established.
@ -179,13 +179,13 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
connectionAttempt.state.Set(0) connectionAttempt.state.Set(0)
// Connection has been established. // Connection has been established.
return &Device{ return Device{
connectionHandle: connectionHandle, connectionHandle: connectionHandle,
}, nil }, nil
} }
// Disconnect from the BLE device. // Disconnect from the BLE device.
func (d *Device) Disconnect() error { func (d Device) Disconnect() error {
errCode := C.sd_ble_gap_disconnect(d.connectionHandle, C.BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION) errCode := C.sd_ble_gap_disconnect(d.connectionHandle, C.BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION)
if errCode != 0 { if errCode != 0 {
return Error(errCode) return Error(errCode)

View file

@ -170,7 +170,7 @@ type Device struct {
// Connect starts a connection attempt to the given peripheral device address. // Connect starts a connection attempt to the given peripheral device address.
// //
// On Linux and Windows, the IsRandom part of the address is ignored. // On Linux and Windows, the IsRandom part of the address is ignored.
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) { func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
var winAddr uint64 var winAddr uint64
for i := range address.MAC { for i := range address.MAC {
winAddr += uint64(address.MAC[i]) << (8 * i) winAddr += uint64(address.MAC[i]) << (8 * i)
@ -179,23 +179,23 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// IAsyncOperation<BluetoothLEDevice> // IAsyncOperation<BluetoothLEDevice>
bleDeviceOp, err := bluetooth.FromBluetoothAddressAsync(winAddr) bleDeviceOp, err := bluetooth.FromBluetoothAddressAsync(winAddr)
if err != nil { if err != nil {
return nil, err return Device{}, err
} }
// We need to pass the signature of the parameter returned by the async operation: // We need to pass the signature of the parameter returned by the async operation:
// IAsyncOperation<BluetoothLEDevice> // IAsyncOperation<BluetoothLEDevice>
if err := awaitAsyncOperation(bleDeviceOp, bluetooth.SignatureBluetoothLEDevice); err != nil { if err := awaitAsyncOperation(bleDeviceOp, bluetooth.SignatureBluetoothLEDevice); err != nil {
return nil, fmt.Errorf("error connecting to device: %w", err) return Device{}, fmt.Errorf("error connecting to device: %w", err)
} }
res, err := bleDeviceOp.GetResults() res, err := bleDeviceOp.GetResults()
if err != nil { if err != nil {
return nil, err return Device{}, err
} }
// The returned BluetoothLEDevice is set to null if FromBluetoothAddressAsync can't find the device identified by bluetoothAddress // The returned BluetoothLEDevice is set to null if FromBluetoothAddressAsync can't find the device identified by bluetoothAddress
if uintptr(res) == 0x0 { if uintptr(res) == 0x0 {
return nil, fmt.Errorf("device with the given address was not found") return Device{}, fmt.Errorf("device with the given address was not found")
} }
bleDevice := (*bluetooth.BluetoothLEDevice)(res) bleDevice := (*bluetooth.BluetoothLEDevice)(res)
@ -204,7 +204,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// To initiate a connection, we need to set GattSession.MaintainConnection to true. // To initiate a connection, we need to set GattSession.MaintainConnection to true.
dID, err := bleDevice.GetBluetoothDeviceId() dID, err := bleDevice.GetBluetoothDeviceId()
if err != nil { if err != nil {
return nil, err return Device{}, err
} }
// Windows does not support explicitly connecting to a device. // Windows does not support explicitly connecting to a device.
@ -212,29 +212,29 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// by the calling program. // by the calling program.
gattSessionOp, err := genericattributeprofile.FromDeviceIdAsync(dID) // IAsyncOperation<GattSession> gattSessionOp, err := genericattributeprofile.FromDeviceIdAsync(dID) // IAsyncOperation<GattSession>
if err != nil { if err != nil {
return nil, err return Device{}, err
} }
if err := awaitAsyncOperation(gattSessionOp, genericattributeprofile.SignatureGattSession); err != nil { if err := awaitAsyncOperation(gattSessionOp, genericattributeprofile.SignatureGattSession); err != nil {
return nil, fmt.Errorf("error getting gatt session: %w", err) return Device{}, fmt.Errorf("error getting gatt session: %w", err)
} }
gattRes, err := gattSessionOp.GetResults() gattRes, err := gattSessionOp.GetResults()
if err != nil { if err != nil {
return nil, err return Device{}, err
} }
newSession := (*genericattributeprofile.GattSession)(gattRes) newSession := (*genericattributeprofile.GattSession)(gattRes)
// This keeps the device connected until we set maintain_connection = False. // This keeps the device connected until we set maintain_connection = False.
if err := newSession.SetMaintainConnection(true); err != nil { if err := newSession.SetMaintainConnection(true); err != nil {
return nil, err return Device{}, err
} }
return &Device{bleDevice, newSession}, nil return Device{bleDevice, newSession}, nil
} }
// Disconnect from the BLE device. This method is non-blocking and does not // Disconnect from the BLE device. This method is non-blocking and does not
// wait until the connection is fully gone. // wait until the connection is fully gone.
func (d *Device) Disconnect() error { func (d Device) Disconnect() error {
defer d.device.Release() defer d.device.Release()
defer d.session.Release() defer d.session.Release()

View file

@ -14,7 +14,7 @@ import (
// //
// Passing a nil slice of UUIDs will return a complete list of // Passing a nil slice of UUIDs will return a complete list of
// services. // services.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) { func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
d.prph.DiscoverServices([]cbgo.UUID{}) d.prph.DiscoverServices([]cbgo.UUID{})
// clear cache of services // clear cache of services
@ -69,7 +69,7 @@ type DeviceService struct {
type deviceService struct { type deviceService struct {
uuidWrapper uuidWrapper
device *Device device Device
service cbgo.Service service cbgo.Service
characteristics []DeviceCharacteristic characteristics []DeviceCharacteristic

View file

@ -35,7 +35,7 @@ const (
type DeviceService struct { type DeviceService struct {
uuid UUID uuid UUID
device *Device device Device
startHandle, endHandle uint16 startHandle, endHandle uint16
} }
@ -51,7 +51,7 @@ func (s DeviceService) UUID() UUID {
// //
// Passing a nil slice of UUIDs will return a complete list of // Passing a nil slice of UUIDs will return a complete list of
// services. // services.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) { func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
if _debug { if _debug {
println("DiscoverServices") println("DiscoverServices")
} }

View file

@ -59,7 +59,7 @@ func (s DeviceService) UUID() UUID {
// //
// On the Nordic SoftDevice, only one service discovery procedure may be done at // On the Nordic SoftDevice, only one service discovery procedure may be done at
// a time. // a time.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) { func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
if discoveringService.state.Get() != 0 { if discoveringService.state.Get() != 0 {
// Not concurrency safe, but should catch most concurrency misuses. // Not concurrency safe, but should catch most concurrency misuses.
return nil, errAlreadyDiscovering return nil, errAlreadyDiscovering

View file

@ -30,7 +30,7 @@ var (
// //
// Passing a nil slice of UUIDs will return a complete list of // Passing a nil slice of UUIDs will return a complete list of
// services. // services.
func (d *Device) DiscoverServices(filterUUIDs []UUID) ([]DeviceService, error) { func (d Device) DiscoverServices(filterUUIDs []UUID) ([]DeviceService, error) {
// IAsyncOperation<GattDeviceServicesResult> // IAsyncOperation<GattDeviceServicesResult>
getServicesOperation, err := d.device.GetGattServicesWithCacheModeAsync(bluetooth.BluetoothCacheModeUncached) getServicesOperation, err := d.device.GetGattServicesWithCacheModeAsync(bluetooth.BluetoothCacheModeUncached)
if err != nil { if err != nil {
@ -133,7 +133,7 @@ type DeviceService struct {
uuidWrapper uuidWrapper
service *genericattributeprofile.GattDeviceService service *genericattributeprofile.GattDeviceService
device *Device device Device
} }
// UUID returns the UUID for this DeviceService. // UUID returns the UUID for this DeviceService.