diff --git a/Makefile b/Makefile index efa73b2..28da815 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ smoketest-tinygo: @md5sum test.hex $(TINYGO) build -o test.uf2 -size=short -target=circuitplay-bluefruit ./examples/circuitplay @md5sum test.hex + $(TINYGO) build -o test.hex -size=short -target=circuitplay-bluefruit ./examples/connparams + @md5sum test.hex $(TINYGO) build -o test.uf2 -size=short -target=circuitplay-bluefruit ./examples/discover @md5sum test.hex $(TINYGO) build -o test.hex -size=short -target=pca10040-s132v6 ./examples/heartrate @@ -42,6 +44,7 @@ smoketest-tinygo: smoketest-linux: # Test on Linux. GOOS=linux go build -o /tmp/go-build-discard ./examples/advertisement + GOOS=linux go build -o /tmp/go-build-discard ./examples/connparams GOOS=linux go build -o /tmp/go-build-discard ./examples/heartrate GOOS=linux go build -o /tmp/go-build-discard ./examples/heartrate-monitor GOOS=linux go build -o /tmp/go-build-discard ./examples/nusserver diff --git a/examples/connparams/main.go b/examples/connparams/main.go new file mode 100644 index 0000000..762cbb9 --- /dev/null +++ b/examples/connparams/main.go @@ -0,0 +1,84 @@ +// Test for setting connection parameters. +// +// To test this feature, run this either on a desktop OS or by flashing it to a +// device with TinyGo. Then connect to it from a BLE connection debugger, for +// example nRF Connect on Android. After a second, you should see in the log of +// the BLE app that the connection latency has been updated. It might look +// something like this: +// +// Connection parameters updated (interval: 510.0ms, latency: 0, timeout: 10000ms) +package main + +import ( + "time" + + "tinygo.org/x/bluetooth" +) + +var ( + adapter = bluetooth.DefaultAdapter + newDevice chan bluetooth.Device +) + +func main() { + must("enable BLE stack", adapter.Enable()) + + newDevice = make(chan bluetooth.Device, 1) + adapter.SetConnectHandler(func(device bluetooth.Device, connected bool) { + // If this is a new device, signal it to the separate goroutine. + if connected { + select { + case newDevice <- device: + default: + } + } + }) + + // Start advertising, so we can be found. + const name = "Go BLE test" + adv := adapter.DefaultAdvertisement() + adv.Configure(bluetooth.AdvertisementOptions{ + LocalName: name, + }) + adv.Start() + println("advertising:", name) + + for device := range newDevice { + println("connection from device:", device.Address.String()) + + // Discover services and characteristics. + svcs, err := device.DiscoverServices(nil) + if err != nil { + println(" failed to resolve services:", err) + } + for _, svc := range svcs { + println(" service:", svc.UUID().String()) + chars, err := svc.DiscoverCharacteristics(nil) + if err != nil { + println(" failed to resolve characteristics:", err) + } + for _, char := range chars { + println(" characteristic:", char.UUID().String()) + } + } + + // Update connection parameters (as a test). + time.Sleep(time.Second) + err = device.RequestConnectionParams(bluetooth.ConnectionParams{ + MinInterval: bluetooth.NewDuration(495 * time.Millisecond), + MaxInterval: bluetooth.NewDuration(510 * time.Millisecond), + Timeout: bluetooth.NewDuration(10 * time.Second), + }) + if err != nil { + println(" failed to update connection parameters:", err) + continue + } + println(" updated connection parameters") + } +} + +func must(action string, err error) { + if err != nil { + panic("failed to " + action + ": " + err.Error()) + } +} diff --git a/gap.go b/gap.go index 17dddfd..f6a418c 100644 --- a/gap.go +++ b/gap.go @@ -391,7 +391,8 @@ func (buf *rawAdvertisementPayload) addServiceUUID(uuid UUID) (ok bool) { } } -// ConnectionParams are used when connecting to a peripherals. +// ConnectionParams are used when connecting to a peripherals or when changing +// the parameters of an active connection. type ConnectionParams struct { // The timeout for the connection attempt. Not used during the rest of the // connection. If no duration is specified, a default timeout will be used. @@ -403,4 +404,9 @@ type ConnectionParams struct { // will be used. MinInterval Duration MaxInterval Duration + + // Connection Supervision Timeout. After this time has passed with no + // communication, the connection is considered lost. If no timeout is + // specified, the timeout will be unchanged. + Timeout Duration } diff --git a/gap_darwin.go b/gap_darwin.go index c87db05..542535c 100644 --- a/gap_darwin.go +++ b/gap_darwin.go @@ -176,6 +176,18 @@ func (d Device) Disconnect() error { return nil } +// 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. +// +// This call has not yet been implemented on macOS. +func (d Device) RequestConnectionParams(params ConnectionParams) error { + // TODO: implement this using setDesiredConnectionLatency, see: + // https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393277-setdesiredconnectionlatency + return nil +} + // Peripheral delegate functions type peripheralDelegate struct { diff --git a/gap_linux.go b/gap_linux.go index 2c77c36..80ecf1a 100644 --- a/gap_linux.go +++ b/gap_linux.go @@ -366,3 +366,14 @@ func (d Device) Disconnect() error { // property change in `watchForConnect` and cancel things then return d.device.Call("org.bluez.Device1.Disconnect", 0).Err } + +// 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 +} diff --git a/gap_ninafw.go b/gap_ninafw.go index 51fbbf5..5cf7840 100644 --- a/gap_ninafw.go +++ b/gap_ninafw.go @@ -230,6 +230,16 @@ func (d Device) Disconnect() error { return nil } +// 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 NINA, this call hasn't been implemented yet. +func (d Device) RequestConnectionParams(params ConnectionParams) error { + return nil +} + func (d Device) findNotificationRegistration(handle uint16) *notificationRegistration { for _, n := range d.notificationRegistrations { if n.handle == handle { diff --git a/gap_nrf528xx-central.go b/gap_nrf528xx-central.go index b451573..085e7d0 100644 --- a/gap_nrf528xx-central.go +++ b/gap_nrf528xx-central.go @@ -188,3 +188,34 @@ func (d Device) Disconnect() error { return nil } + +// 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 the Nordic SoftDevice, this call will also set the slave latency to 0. +func (d Device) RequestConnectionParams(params ConnectionParams) error { + // The default parameters if no specific parameters are picked. + connParams := C.ble_gap_conn_params_t{ + min_conn_interval: C.BLE_GAP_CP_MIN_CONN_INTVL_NONE, + max_conn_interval: C.BLE_GAP_CP_MAX_CONN_INTVL_NONE, + slave_latency: 0, + conn_sup_timeout: C.BLE_GAP_CP_CONN_SUP_TIMEOUT_NONE, + } + + // Use specified parameters if available. + if params.MinInterval != 0 { + connParams.min_conn_interval = C.uint16_t(params.MinInterval) / 2 + } + if params.MaxInterval != 0 { + connParams.max_conn_interval = C.uint16_t(params.MaxInterval) / 2 + } + if params.Timeout != 0 { + connParams.conn_sup_timeout = C.uint16_t(params.Timeout) / 16 + } + + // Send them to peer device. + errCode := C.sd_ble_gap_conn_param_update(d.connectionHandle, &connParams) + return makeError(errCode) +} diff --git a/gap_windows.go b/gap_windows.go index 5ae0f97..c800c25 100644 --- a/gap_windows.go +++ b/gap_windows.go @@ -247,3 +247,15 @@ func (d Device) Disconnect() error { return nil } + +// 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 Windows, this call doesn't do anything. +func (d Device) RequestConnectionParams(params ConnectionParams) error { + // TODO: implement this using + // BluetoothLEDevice.RequestPreferredConnectionParameters. + return nil +}