2022-02-22 13:04:20 +03:00
|
|
|
//go:build !baremetal
|
2019-11-09 17:55:38 +03:00
|
|
|
|
|
|
|
package bluetooth
|
|
|
|
|
|
|
|
import (
|
2021-11-01 23:57:12 +03:00
|
|
|
"errors"
|
2024-01-02 14:51:05 +03:00
|
|
|
"fmt"
|
2020-06-28 01:14:25 +03:00
|
|
|
"strings"
|
2024-01-02 14:51:05 +03:00
|
|
|
"sync/atomic"
|
2020-06-28 01:14:25 +03:00
|
|
|
|
2020-06-15 19:11:53 +03:00
|
|
|
"github.com/godbus/dbus/v5"
|
2024-01-02 14:51:05 +03:00
|
|
|
"github.com/godbus/dbus/v5/prop"
|
2019-11-09 17:55:38 +03:00
|
|
|
)
|
|
|
|
|
2021-11-01 23:57:12 +03:00
|
|
|
var errAdvertisementNotStarted = errors.New("bluetooth: stop advertisement that was not started")
|
2024-01-02 14:51:05 +03:00
|
|
|
var errAdvertisementAlreadyStarted = errors.New("bluetooth: start advertisement that was already started")
|
|
|
|
|
|
|
|
// Unique ID per advertisement (to generate a unique object path).
|
|
|
|
var advertisementID uint64
|
2021-11-01 23:57:12 +03:00
|
|
|
|
2020-08-29 15:43:11 +03:00
|
|
|
// Address contains a Bluetooth MAC address.
|
2020-08-28 13:40:03 +03:00
|
|
|
type Address struct {
|
2020-08-29 15:43:11 +03:00
|
|
|
MACAddress
|
2020-08-28 13:40:03 +03:00
|
|
|
}
|
|
|
|
|
2019-11-09 17:55:38 +03:00
|
|
|
// Advertisement encapsulates a single advertisement instance.
|
|
|
|
type Advertisement struct {
|
2024-01-02 14:51:05 +03:00
|
|
|
adapter *Adapter
|
|
|
|
properties *prop.Properties
|
|
|
|
path dbus.ObjectPath
|
2019-11-09 17:55:38 +03:00
|
|
|
}
|
|
|
|
|
2020-06-01 19:04:03 +03:00
|
|
|
// DefaultAdvertisement returns the default advertisement instance but does not
|
|
|
|
// configure it.
|
|
|
|
func (a *Adapter) DefaultAdvertisement() *Advertisement {
|
|
|
|
if a.defaultAdvertisement == nil {
|
|
|
|
a.defaultAdvertisement = &Advertisement{
|
|
|
|
adapter: a,
|
|
|
|
}
|
2019-11-09 17:55:38 +03:00
|
|
|
}
|
2020-06-01 19:04:03 +03:00
|
|
|
return a.defaultAdvertisement
|
2019-11-09 17:55:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Configure this advertisement.
|
2020-06-01 15:07:07 +03:00
|
|
|
//
|
|
|
|
// On Linux with BlueZ, it is not possible to set the advertisement interval.
|
|
|
|
func (a *Advertisement) Configure(options AdvertisementOptions) error {
|
2024-01-02 14:51:05 +03:00
|
|
|
if a.properties != nil {
|
2019-11-09 17:55:38 +03:00
|
|
|
panic("todo: configure advertisement a second time")
|
|
|
|
}
|
|
|
|
|
2024-01-02 14:51:05 +03:00
|
|
|
var serviceUUIDs []string
|
2020-06-02 15:00:32 +03:00
|
|
|
for _, uuid := range options.ServiceUUIDs {
|
2024-01-02 14:51:05 +03:00
|
|
|
serviceUUIDs = append(serviceUUIDs, uuid.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build an org.bluez.LEAdvertisement1 object, to be exported over DBus.
|
|
|
|
id := atomic.AddUint64(&advertisementID, 1)
|
|
|
|
a.path = dbus.ObjectPath(fmt.Sprintf("/org/tinygo/bluetooth/advertisement%d", id))
|
|
|
|
propsSpec := map[string]map[string]*prop.Prop{
|
|
|
|
"org.bluez.LEAdvertisement1": {
|
|
|
|
"Type": {Value: "broadcast"},
|
|
|
|
"ServiceUUIDs": {Value: serviceUUIDs},
|
|
|
|
"ManufacturerData": {Value: options.ManufacturerData},
|
|
|
|
"LocalName": {Value: options.LocalName},
|
|
|
|
// The documentation states:
|
|
|
|
// > Timeout of the advertisement in seconds. This defines the
|
|
|
|
// > lifetime of the advertisement.
|
|
|
|
// however, the value 0 also works, and presumably means "no
|
|
|
|
// timeout".
|
|
|
|
"Timeout": {Value: uint16(0)},
|
|
|
|
// TODO: MinInterval and MaxInterval (experimental as of BlueZ 5.71)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
props, err := prop.Export(a.adapter.bus, a.path, propsSpec)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-06-02 15:00:32 +03:00
|
|
|
}
|
2024-01-02 14:51:05 +03:00
|
|
|
a.properties = props
|
2019-11-09 17:55:38 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start advertisement. May only be called after it has been configured.
|
|
|
|
func (a *Advertisement) Start() error {
|
2024-01-02 14:51:05 +03:00
|
|
|
// Register our advertisement object to start advertising.
|
|
|
|
err := a.adapter.adapter.Call("org.bluez.LEAdvertisingManager1.RegisterAdvertisement", 0, a.path, map[string]interface{}{}).Err
|
|
|
|
if err != nil {
|
|
|
|
if err, ok := err.(dbus.Error); ok && err.Name == "org.bluez.Error.AlreadyExists" {
|
|
|
|
return errAdvertisementAlreadyStarted
|
|
|
|
}
|
|
|
|
return fmt.Errorf("bluetooth: could not start advertisement: %w", err)
|
2019-11-09 17:55:38 +03:00
|
|
|
}
|
2024-01-02 14:51:05 +03:00
|
|
|
|
|
|
|
// Make us discoverable.
|
|
|
|
err = a.adapter.adapter.SetProperty("org.bluez.Adapter1.Discoverable", dbus.MakeVariant(true))
|
2019-11-09 17:55:38 +03:00
|
|
|
if err != nil {
|
2024-01-02 14:51:05 +03:00
|
|
|
return fmt.Errorf("bluetooth: could not start advertisement: %w", err)
|
2019-11-09 17:55:38 +03:00
|
|
|
}
|
2021-11-01 23:57:12 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop advertisement. May only be called after it has been started.
|
|
|
|
func (a *Advertisement) Stop() error {
|
2024-01-02 14:51:05 +03:00
|
|
|
err := a.adapter.adapter.Call("org.bluez.LEAdvertisingManager1.UnregisterAdvertisement", 0, a.path).Err
|
|
|
|
if err != nil {
|
|
|
|
if err, ok := err.(dbus.Error); ok && err.Name == "org.bluez.Error.DoesNotExist" {
|
|
|
|
return errAdvertisementNotStarted
|
|
|
|
}
|
|
|
|
return fmt.Errorf("bluetooth: could not stop advertisement: %w", err)
|
2021-11-01 23:57:12 +03:00
|
|
|
}
|
2019-11-09 17:55:38 +03:00
|
|
|
return nil
|
|
|
|
}
|
2020-05-28 00:13:04 +03:00
|
|
|
|
|
|
|
// Scan starts a BLE scan. It is stopped by a call to StopScan. A common pattern
|
|
|
|
// is to cancel the scan when a particular device has been found.
|
|
|
|
//
|
|
|
|
// On Linux with BlueZ, incoming packets cannot be observed directly. Instead,
|
|
|
|
// existing devices are watched for property changes. This closely simulates the
|
|
|
|
// behavior as if the actual packets were observed, but it has flaws: it is
|
|
|
|
// possible some events are missed and perhaps even possible that some events
|
|
|
|
// are duplicated.
|
|
|
|
func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
2024-01-02 14:51:05 +03:00
|
|
|
if a.scanCancelChan != nil {
|
2020-05-28 00:13:04 +03:00
|
|
|
return errScanning
|
|
|
|
}
|
|
|
|
|
2020-06-15 19:11:53 +03:00
|
|
|
// Channel that will be closed when the scan is stopped.
|
|
|
|
// Detecting whether the scan is stopped can be done by doing a non-blocking
|
|
|
|
// read from it. If it succeeds, the scan is stopped.
|
|
|
|
cancelChan := make(chan struct{})
|
2024-01-02 14:51:05 +03:00
|
|
|
a.scanCancelChan = cancelChan
|
2020-06-15 19:11:53 +03:00
|
|
|
|
2020-05-28 00:13:04 +03:00
|
|
|
// This appears to be necessary to receive any BLE discovery results at all.
|
2024-01-02 14:51:05 +03:00
|
|
|
defer a.adapter.Call("org.bluez.Adapter1.SetDiscoveryFilter", 0)
|
|
|
|
err := a.adapter.Call("org.bluez.Adapter1.SetDiscoveryFilter", 0, map[string]interface{}{
|
2020-05-28 00:13:04 +03:00
|
|
|
"Transport": "le",
|
2024-01-02 14:51:05 +03:00
|
|
|
}).Err
|
2020-05-28 00:13:04 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-15 19:11:53 +03:00
|
|
|
signal := make(chan *dbus.Signal)
|
2024-01-02 14:51:05 +03:00
|
|
|
a.bus.Signal(signal)
|
|
|
|
defer a.bus.RemoveSignal(signal)
|
2020-06-15 19:11:53 +03:00
|
|
|
|
|
|
|
propertiesChangedMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.Properties")}
|
2024-01-02 14:51:05 +03:00
|
|
|
a.bus.AddMatchSignal(propertiesChangedMatchOptions...)
|
|
|
|
defer a.bus.RemoveMatchSignal(propertiesChangedMatchOptions...)
|
2020-06-15 19:11:53 +03:00
|
|
|
|
|
|
|
newObjectMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.ObjectManager")}
|
2024-01-02 14:51:05 +03:00
|
|
|
a.bus.AddMatchSignal(newObjectMatchOptions...)
|
|
|
|
defer a.bus.RemoveMatchSignal(newObjectMatchOptions...)
|
2020-06-15 19:11:53 +03:00
|
|
|
|
|
|
|
// Go through all connected devices and present the connected devices as
|
|
|
|
// scan results. Also save the properties so that the full list of
|
|
|
|
// properties is known on a PropertiesChanged signal. We can't present the
|
|
|
|
// list of cached devices as scan results as devices may be cached for a
|
|
|
|
// long time, long after they have moved out of range.
|
2024-01-02 14:51:05 +03:00
|
|
|
var deviceList map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
|
|
|
err = a.bluez.Call("org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&deviceList)
|
2020-05-28 00:13:04 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-02 14:51:05 +03:00
|
|
|
devices := make(map[dbus.ObjectPath]map[string]dbus.Variant)
|
|
|
|
for path, v := range deviceList {
|
|
|
|
device, ok := v["org.bluez.Device1"]
|
|
|
|
if !ok {
|
|
|
|
continue // not a device
|
|
|
|
}
|
|
|
|
if !strings.HasPrefix(string(path), string(a.adapter.Path())) {
|
|
|
|
continue // not part of our adapter
|
|
|
|
}
|
|
|
|
if device["Connected"].Value().(bool) {
|
|
|
|
callback(a, makeScanResult(device))
|
2020-06-15 19:11:53 +03:00
|
|
|
select {
|
|
|
|
case <-cancelChan:
|
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
2024-01-02 14:51:05 +03:00
|
|
|
devices[path] = device
|
2020-06-15 19:11:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Instruct BlueZ to start discovering.
|
2024-01-02 14:51:05 +03:00
|
|
|
err = a.adapter.Call("org.bluez.Adapter1.StartDiscovery", 0).Err
|
2020-05-28 00:13:04 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-15 19:11:53 +03:00
|
|
|
for {
|
|
|
|
// Check whether the scan is stopped. This is necessary to avoid a race
|
|
|
|
// condition between the signal channel and the cancelScan channel when
|
|
|
|
// the callback calls StopScan() (no new callbacks may be called after
|
|
|
|
// StopScan is called).
|
|
|
|
select {
|
|
|
|
case <-cancelChan:
|
2024-01-02 14:51:05 +03:00
|
|
|
return a.adapter.Call("org.bluez.Adapter1.StopDiscovery", 0).Err
|
2020-06-15 19:11:53 +03:00
|
|
|
default:
|
2020-05-28 00:13:04 +03:00
|
|
|
}
|
|
|
|
|
2020-06-15 19:11:53 +03:00
|
|
|
select {
|
|
|
|
case sig := <-signal:
|
|
|
|
// This channel receives anything that we watch for, so we'll have
|
|
|
|
// to check for signals that are relevant to us.
|
|
|
|
switch sig.Name {
|
|
|
|
case "org.freedesktop.DBus.ObjectManager.InterfacesAdded":
|
|
|
|
objectPath := sig.Body[0].(dbus.ObjectPath)
|
|
|
|
interfaces := sig.Body[1].(map[string]map[string]dbus.Variant)
|
|
|
|
rawprops, ok := interfaces["org.bluez.Device1"]
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
2024-01-02 14:51:05 +03:00
|
|
|
devices[objectPath] = rawprops
|
|
|
|
callback(a, makeScanResult(rawprops))
|
2020-06-15 19:11:53 +03:00
|
|
|
case "org.freedesktop.DBus.Properties.PropertiesChanged":
|
|
|
|
interfaceName := sig.Body[0].(string)
|
|
|
|
if interfaceName != "org.bluez.Device1" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
changes := sig.Body[1].(map[string]dbus.Variant)
|
2024-01-02 14:51:05 +03:00
|
|
|
device, ok := devices[sig.Path]
|
|
|
|
if !ok {
|
|
|
|
// This shouldn't happen, but protect against it just in
|
|
|
|
// case.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for k, v := range changes {
|
|
|
|
device[k] = v
|
2020-06-15 19:11:53 +03:00
|
|
|
}
|
2024-01-02 14:51:05 +03:00
|
|
|
callback(a, makeScanResult(device))
|
2020-06-15 19:11:53 +03:00
|
|
|
}
|
|
|
|
case <-cancelChan:
|
2020-05-28 00:13:04 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 19:11:53 +03:00
|
|
|
// unreachable
|
2020-05-28 00:13:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// StopScan stops any in-progress scan. It can be called from within a Scan
|
|
|
|
// callback to stop the current scan. If no scan is in progress, an error will
|
|
|
|
// be returned.
|
|
|
|
func (a *Adapter) StopScan() error {
|
2024-01-02 14:51:05 +03:00
|
|
|
if a.scanCancelChan == nil {
|
2020-05-28 00:13:04 +03:00
|
|
|
return errNotScanning
|
|
|
|
}
|
2024-01-02 14:51:05 +03:00
|
|
|
close(a.scanCancelChan)
|
|
|
|
a.scanCancelChan = nil
|
2020-05-28 00:13:04 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-02 14:51:05 +03:00
|
|
|
// makeScanResult creates a ScanResult from a raw DBus device.
|
|
|
|
func makeScanResult(props map[string]dbus.Variant) ScanResult {
|
2020-05-28 00:13:04 +03:00
|
|
|
// Assume the Address property is well-formed.
|
2024-01-02 14:51:05 +03:00
|
|
|
addr, _ := ParseMAC(props["Address"].Value().(string))
|
2020-05-28 00:13:04 +03:00
|
|
|
|
2020-06-04 19:51:01 +03:00
|
|
|
// Create a list of UUIDs.
|
|
|
|
var serviceUUIDs []UUID
|
2024-01-02 14:51:05 +03:00
|
|
|
for _, uuid := range props["UUIDs"].Value().([]string) {
|
2020-06-04 19:51:01 +03:00
|
|
|
// Assume the UUID is well-formed.
|
|
|
|
parsedUUID, _ := ParseUUID(uuid)
|
|
|
|
serviceUUIDs = append(serviceUUIDs, parsedUUID)
|
|
|
|
}
|
|
|
|
|
2020-09-02 20:15:52 +03:00
|
|
|
a := Address{MACAddress{MAC: addr}}
|
2024-01-02 14:51:05 +03:00
|
|
|
a.SetRandom(props["AddressType"].Value().(string) == "random")
|
|
|
|
|
|
|
|
manufacturerData := make(map[uint16][]byte)
|
|
|
|
if mdata, ok := props["ManufacturerData"].Value().(map[uint16]dbus.Variant); ok {
|
|
|
|
for k, v := range mdata {
|
|
|
|
manufacturerData[k] = v.Value().([]byte)
|
2022-09-26 13:51:29 +03:00
|
|
|
}
|
2022-05-11 17:11:52 +03:00
|
|
|
}
|
|
|
|
|
2024-01-02 14:51:05 +03:00
|
|
|
// Get optional properties.
|
|
|
|
localName, _ := props["Name"].Value().(string)
|
|
|
|
rssi, _ := props["RSSI"].Value().(int16)
|
|
|
|
|
2020-05-28 00:13:04 +03:00
|
|
|
return ScanResult{
|
2024-01-02 14:51:05 +03:00
|
|
|
RSSI: rssi,
|
2020-08-29 15:43:11 +03:00
|
|
|
Address: a,
|
2020-05-28 00:13:04 +03:00
|
|
|
AdvertisementPayload: &advertisementFields{
|
|
|
|
AdvertisementFields{
|
2024-01-02 14:51:05 +03:00
|
|
|
LocalName: localName,
|
2022-05-11 17:11:52 +03:00
|
|
|
ServiceUUIDs: serviceUUIDs,
|
2024-01-02 14:51:05 +03:00
|
|
|
ManufacturerData: manufacturerData,
|
2020-05-28 00:13:04 +03:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2020-06-28 01:14:25 +03:00
|
|
|
|
|
|
|
// Device is a connection to a remote peripheral.
|
|
|
|
type Device struct {
|
2023-12-25 16:52:10 +03:00
|
|
|
Address Address // the MAC address of the device
|
|
|
|
|
2024-01-02 14:51:05 +03:00
|
|
|
device dbus.BusObject // bluez device interface
|
|
|
|
adapter *Adapter // the adapter that was used to form this device connection
|
2020-06-28 01:14:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Connect starts a connection attempt to the given peripheral device address.
|
|
|
|
//
|
|
|
|
// On Linux and Windows, the IsRandom part of the address is ignored.
|
2023-12-25 16:28:56 +03:00
|
|
|
func (a *Adapter) Connect(address Address, params ConnectionParams) (Device, error) {
|
2023-04-25 03:12:05 +03:00
|
|
|
devicePath := dbus.ObjectPath(string(a.adapter.Path()) + "/dev_" + strings.Replace(address.MAC.String(), ":", "_", -1))
|
2023-12-25 16:28:56 +03:00
|
|
|
device := Device{
|
2023-12-25 16:52:10 +03:00
|
|
|
Address: address,
|
2024-01-02 14:51:05 +03:00
|
|
|
device: a.bus.Object("org.bluez", devicePath),
|
2023-05-04 11:45:58 +03:00
|
|
|
adapter: a,
|
|
|
|
}
|
|
|
|
|
2024-01-02 14:51:05 +03:00
|
|
|
// Already start watching for property changes. We do this before reading
|
|
|
|
// the Connected property below to avoid a race condition: if the device
|
|
|
|
// were connected between the two calls the signal wouldn't be picked up.
|
|
|
|
signal := make(chan *dbus.Signal)
|
|
|
|
a.bus.Signal(signal)
|
|
|
|
defer a.bus.RemoveSignal(signal)
|
|
|
|
propertiesChangedMatchOptions := []dbus.MatchOption{dbus.WithMatchInterface("org.freedesktop.DBus.Properties")}
|
|
|
|
a.bus.AddMatchSignal(propertiesChangedMatchOptions...)
|
|
|
|
defer a.bus.RemoveMatchSignal(propertiesChangedMatchOptions...)
|
|
|
|
|
|
|
|
// Read whether this device is already connected.
|
|
|
|
connected, err := device.device.GetProperty("org.bluez.Device1.Connected")
|
|
|
|
if err != nil {
|
2023-12-25 16:28:56 +03:00
|
|
|
return Device{}, err
|
2024-01-02 14:51:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Connect to the device, if not already connected.
|
|
|
|
if !connected.Value().(bool) {
|
|
|
|
// Start connecting (async).
|
|
|
|
err := device.device.Call("org.bluez.Device1.Connect", 0).Err
|
2020-06-28 01:14:25 +03:00
|
|
|
if err != nil {
|
2023-12-25 16:28:56 +03:00
|
|
|
return Device{}, fmt.Errorf("bluetooth: failed to connect: %w", err)
|
2020-06-28 01:14:25 +03:00
|
|
|
}
|
2024-01-02 14:51:05 +03:00
|
|
|
|
|
|
|
// Wait until the device has connected.
|
|
|
|
connectChan := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
for sig := range signal {
|
|
|
|
switch sig.Name {
|
|
|
|
case "org.freedesktop.DBus.Properties.PropertiesChanged":
|
|
|
|
interfaceName := sig.Body[0].(string)
|
|
|
|
if interfaceName != "org.bluez.Device1" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if sig.Path != device.device.Path() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
changes := sig.Body[1].(map[string]dbus.Variant)
|
|
|
|
if connected, ok := changes["Connected"].Value().(bool); ok && connected {
|
|
|
|
close(connectChan)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
<-connectChan
|
2020-06-28 01:14:25 +03:00
|
|
|
}
|
|
|
|
|
2023-05-04 11:45:58 +03:00
|
|
|
return device, nil
|
2020-06-28 01:14:25 +03:00
|
|
|
}
|
2020-09-22 17:55:44 +03:00
|
|
|
|
2020-09-24 18:07:29 +03:00
|
|
|
// Disconnect from the BLE device. This method is non-blocking and does not
|
|
|
|
// wait until the connection is fully gone.
|
2023-12-25 16:28:56 +03:00
|
|
|
func (d Device) Disconnect() error {
|
2023-05-04 11:45:58 +03:00
|
|
|
// we don't call our cancel function here, instead we wait for the
|
|
|
|
// property change in `watchForConnect` and cancel things then
|
2024-01-02 14:51:05 +03:00
|
|
|
return d.device.Call("org.bluez.Device1.Disconnect", 0).Err
|
2023-05-04 11:45:58 +03:00
|
|
|
}
|
2023-12-25 17:29:49 +03:00
|
|
|
|
|
|
|
// RequestConnectionParams requests a different connection latency and timeout
|
|
|
|
// of the given device connection. Fields that are unset will be left alone.
|
|
|
|
// Whether or not the device will actually honor this, depends on the device and
|
|
|
|
// on the specific parameters.
|
|
|
|
//
|
|
|
|
// On Linux, this call doesn't do anything because BlueZ doesn't support
|
|
|
|
// changing the connection latency.
|
|
|
|
func (d Device) RequestConnectionParams(params ConnectionParams) error {
|
|
|
|
return nil
|
|
|
|
}
|