windows: add device connection and disconnection

release
Jagoba Gascón Sánchez 2 years ago committed by Ron Evans
parent 8f13d06111
commit 7113f8c021

@ -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 {
@ -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
}

@ -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"
)
@ -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
}

Loading…
Cancel
Save