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)
connectedDevices []*Device
connectedDevices []Device
notificationsStarted bool
}
@ -33,7 +33,7 @@ var DefaultAdapter = &Adapter{
connectHandler: func(device Address, connected bool) {
return
},
connectedDevices: make([]*Device, 0, maxConnections),
connectedDevices: make([]Device, 0, maxConnections),
}
// Enable configures the BLE stack. It must be called before any
@ -185,7 +185,7 @@ func (a *Adapter) startNotifications() {
}
d := a.findDevice(not.connectionHandle)
if d == nil {
if d.deviceInternal == nil {
if _debug {
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 {
if d.handle == handle {
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 {
case result := <-ch:
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 {
case result := <-ch:
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.
type Device struct {
*deviceInternal
}
type deviceInternal struct {
delegate *peripheralDelegate
cm cbgo.CentralManager
@ -97,14 +101,14 @@ type Device struct {
}
// 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())
if err != nil {
return nil, err
return Device{}, err
}
prphs := a.cm.RetrievePeripheralsWithIdentifiers([]cbgo.UUID{uuid})
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
@ -129,14 +133,16 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// check if we have received a disconnected peripheral
if p.State() == cbgo.PeripheralStateDisconnected {
return nil, connectionError
return Device{}, connectionError
}
d := &Device{
cm: a.cm,
prph: p,
servicesChan: make(chan error),
charsChan: make(chan error),
d := Device{
&deviceInternal{
cm: a.cm,
prph: p,
servicesChan: make(chan error),
charsChan: make(chan error),
},
}
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
// wait until the connection is fully gone.
func (d *Device) Disconnect() error {
func (d Device) Disconnect() error {
d.cm.CancelConnect(d.prph)
return nil
}
@ -172,7 +178,7 @@ func (d *Device) Disconnect() error {
type peripheralDelegate struct {
cbgo.PeripheralDelegateBase
d *Device
d Device
}
// 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.
//
// 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))
device := &Device{
device := Device{
device: a.bus.Object("org.bluez", devicePath),
adapter: a,
address: address,
@ -321,7 +321,7 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// Read whether this device is already connected.
connected, err := device.device.GetProperty("org.bluez.Device1.Connected")
if err != nil {
return nil, err
return Device{}, err
}
// 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).
err := device.device.Call("org.bluez.Device1.Connect", 0).Err
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.
@ -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
// 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
// property change in `watchForConnect` and cancel things then
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.
func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, error) {
func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
if _debug {
println("Connect")
}
@ -145,14 +145,14 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
if err := a.hci.leCreateConn(0x0060, 0x0030, 0x00,
random, makeNINAAddress(address.MAC),
0x00, 0x0006, 0x000c, 0x0000, 0x00c8, 0x0004, 0x0006); err != nil {
return nil, err
return Device{}, err
}
// are we connected?
start := time.Now().UnixNano()
for {
if err := a.hci.poll(); err != nil {
return nil, err
return Device{}, err
}
if a.hci.connectData.connected {
@ -163,15 +163,18 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
random = true
}
d := &Device{adapter: a,
handle: a.hci.connectData.handle,
d := Device{
Address: Address{
MACAddress{
MAC: makeAddress(a.hci.connectData.peerBdaddr),
isRandom: random},
},
mtu: defaultMTU,
notificationRegistrations: make([]notificationRegistration, 0),
deviceInternal: &deviceInternal{
adapter: a,
handle: a.hci.connectData.handle,
mtu: defaultMTU,
notificationRegistrations: make([]notificationRegistration, 0),
},
}
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
if err := a.hci.leCancelConn(); err != nil {
return nil, err
return Device{}, err
}
return nil, ErrConnect
return Device{}, ErrConnect
}
type notificationRegistration struct {
@ -202,8 +205,12 @@ type notificationRegistration struct {
// Device is a connection to a remote peripheral.
type Device struct {
adapter *Adapter
Address Address
*deviceInternal
}
type deviceInternal struct {
adapter *Adapter
handle uint16
mtu uint16
@ -211,7 +218,7 @@ type Device struct {
}
// Disconnect from the BLE device.
func (d *Device) Disconnect() error {
func (d Device) Disconnect() error {
if _debug {
println("Disconnect")
}
@ -219,11 +226,11 @@ func (d *Device) Disconnect() error {
return err
}
d.adapter.connectedDevices = []*Device{}
d.adapter.connectedDevices = []Device{}
return nil
}
func (d *Device) findNotificationRegistration(handle uint16) *notificationRegistration {
func (d Device) findNotificationRegistration(handle uint16) *notificationRegistration {
for _, n := range d.notificationRegistrations {
if n.handle == handle {
return &n
@ -233,7 +240,7 @@ func (d *Device) findNotificationRegistration(handle uint16) *notificationRegist
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,
notificationRegistration{
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()
}

View file

@ -109,7 +109,7 @@ var connectionAttempt struct {
// 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
// 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.
var addr C.ble_gap_addr_t
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
// even then, it should catch most such race conditions.
if connectionAttempt.state.Get() != 0 {
return nil, errAlreadyConnecting
return Device{}, errAlreadyConnecting
}
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)
if errCode != 0 {
connectionAttempt.state.Set(0)
return nil, Error(errCode)
return Device{}, Error(errCode)
}
// Wait until the connection is established.
@ -179,13 +179,13 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
connectionAttempt.state.Set(0)
// Connection has been established.
return &Device{
return Device{
connectionHandle: connectionHandle,
}, nil
}
// 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)
if errCode != 0 {
return Error(errCode)

View file

@ -170,7 +170,7 @@ type Device struct {
// Connect starts a connection attempt to the given peripheral device address.
//
// 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
for i := range address.MAC {
winAddr += uint64(address.MAC[i]) << (8 * i)
@ -179,23 +179,23 @@ func (a *Adapter) Connect(address Address, params ConnectionParams) (*Device, er
// IAsyncOperation<BluetoothLEDevice>
bleDeviceOp, err := bluetooth.FromBluetoothAddressAsync(winAddr)
if err != nil {
return nil, err
return Device{}, err
}
// We need to pass the signature of the parameter returned by the async operation:
// IAsyncOperation<BluetoothLEDevice>
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()
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
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)
@ -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.
dID, err := bleDevice.GetBluetoothDeviceId()
if err != nil {
return nil, err
return Device{}, err
}
// 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.
gattSessionOp, err := genericattributeprofile.FromDeviceIdAsync(dID) // IAsyncOperation<GattSession>
if err != nil {
return nil, err
return Device{}, err
}
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()
if err != nil {
return nil, err
return Device{}, err
}
newSession := (*genericattributeprofile.GattSession)(gattRes)
// This keeps the device connected until we set maintain_connection = False.
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
// wait until the connection is fully gone.
func (d *Device) Disconnect() error {
func (d Device) Disconnect() error {
defer d.device.Release()
defer d.session.Release()

View file

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

View file

@ -35,7 +35,7 @@ const (
type DeviceService struct {
uuid UUID
device *Device
device Device
startHandle, endHandle uint16
}
@ -51,7 +51,7 @@ func (s DeviceService) UUID() UUID {
//
// Passing a nil slice of UUIDs will return a complete list of
// services.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
if _debug {
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
// a time.
func (d *Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
func (d Device) DiscoverServices(uuids []UUID) ([]DeviceService, error) {
if discoveringService.state.Get() != 0 {
// Not concurrency safe, but should catch most concurrency misuses.
return nil, errAlreadyDiscovering

View file

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