Skip to content

Commit

Permalink
windows: add device connection and disconnection
Browse files Browse the repository at this point in the history
  • Loading branch information
jagobagascon authored and deadprogram committed Sep 12, 2022
1 parent 8f13d06 commit 7113f8c
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 0 deletions.
27 changes: 27 additions & 0 deletions adapter_windows.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package bluetooth

import (
"fmt"

"github.com/go-ole/go-ole"
"github.com/saltosystems/winrt-go"
"github.com/saltosystems/winrt-go/windows/devices/bluetooth/advertisement"
"github.com/saltosystems/winrt-go/windows/foundation"
)

type Adapter struct {
Expand All @@ -25,3 +29,26 @@ var DefaultAdapter = &Adapter{
func (a *Adapter) Enable() error {
return ole.RoInitialize(1) // initialize with multithreading enabled
}

func awaitAsyncOperation(asyncOperation *foundation.IAsyncOperation, genericParamSignature string) error {
var status foundation.AsyncStatus

// We need to obtain the GUID of the AsyncOperationCompletedHandler, but its a generic delegate
// so we also need the generic parameter type's signature:
// AsyncOperationCompletedHandler<genericParamSignature>
iid := winrt.ParameterizedInstanceGUID(foundation.GUIDAsyncOperationCompletedHandler, genericParamSignature)

// Wait until the async operation completes.
waitChan := make(chan struct{})
asyncOperation.SetCompleted(foundation.NewAsyncOperationCompletedHandler(ole.NewGUID(iid), func(instance *foundation.AsyncOperationCompletedHandler, asyncInfo *foundation.IAsyncOperation, asyncStatus foundation.AsyncStatus) {
status = asyncStatus
close(waitChan)
}))
// Wait until async operation has stopped, and finish.
<-waitChan

if status != foundation.AsyncStatusCompleted {
return fmt.Errorf("async operation failed with status %d", status)
}
return nil
}
92 changes: 92 additions & 0 deletions gap_windows.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package bluetooth

import (
"fmt"
"unsafe"

"github.com/go-ole/go-ole"
"github.com/saltosystems/winrt-go"
"github.com/saltosystems/winrt-go/windows/devices/bluetooth"
"github.com/saltosystems/winrt-go/windows/devices/bluetooth/advertisement"
"github.com/saltosystems/winrt-go/windows/devices/bluetooth/genericattributeprofile"
"github.com/saltosystems/winrt-go/windows/foundation"
"github.com/saltosystems/winrt-go/windows/storage/streams"
)
Expand Down Expand Up @@ -147,3 +150,92 @@ func (a *Adapter) StopScan() error {
}
return a.watcher.Stop()
}

// Device is a connection to a remote peripheral.
type Device struct {
device *bluetooth.BluetoothLEDevice
session *genericattributeprofile.GattSession
}

// 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(addresser Addresser, params ConnectionParams) (*Device, error) {
address := addresser.(Address).MACAddress

var winAddr uint64
for i := range address.MAC {
winAddr += uint64(address.MAC[i]) << (8 * i)
}

// IAsyncOperation<BluetoothLEDevice>
bleDeviceOp, err := bluetooth.FromBluetoothAddressAsync(winAddr)
if err != nil {
return nil, 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)
}

res, err := bleDeviceOp.GetResults()
if err != nil {
return nil, 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")
}

bleDevice := (*bluetooth.BluetoothLEDevice)(res)

// Creating a BluetoothLEDevice object by calling this method alone doesn't (necessarily) initiate a connection.
// To initiate a connection, we need to set GattSession.MaintainConnection to true.
dID, err := bleDevice.GetBluetoothDeviceId()
if err != nil {
return nil, err
}

// Windows does not support explicitly connecting to a device.
// Instead it has the concept of a GATT session that is owned
// by the calling program.
gattSessionOp, err := genericattributeprofile.FromDeviceIdAsync(dID) // IAsyncOperation<GattSession>
if err != nil {
return nil, err
}

if err := awaitAsyncOperation(gattSessionOp, genericattributeprofile.SignatureGattSession); err != nil {
return nil, fmt.Errorf("error getting gatt session: %w", err)
}

gattRes, err := gattSessionOp.GetResults()
if err != nil {
return nil, 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{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 {
defer d.device.Release()
defer d.session.Release()

if err := d.session.Close(); err != nil {
return err
}
if err := d.device.Close(); err != nil {
return err
}

return nil
}

0 comments on commit 7113f8c

Please sign in to comment.