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