From 7d5e8adbeaaea74395da44dafbaa920323cc4a80 Mon Sep 17 00:00:00 2001 From: Alexis Couvreur Date: Wed, 2 Sep 2026 14:45:32 -0400 Subject: [PATCH] feat(gap): add ConnectionPriority to ConnectionParams Not every platform accepts the connection parameters from the Core Specification. Some only accept a small fixed set of presets: on Windows, BluetoothLEPreferredConnectionParameters has no public constructor, just the static ThroughputOptimized, Balanced and PowerOptimized sets. ConnectionPriority is the form of the request those platforms can honour. The constants are named after the trade-off rather than the resulting latency because the vendor enumerations disagree on a direction: Apple's CBPeripheralManagerConnectionLatency .low and Android's CONNECTION_PRIORITY_HIGH mean the same thing, so Low/Medium/High would be read backwards by half of the audience. It also keeps "connection latency" free for the numeric parameter the Core Specification uses it for, which is the number of connection events a peripheral may skip. The zero value means "leave the connection unchanged", matching the other ConnectionParams fields. No backend reads Priority yet; wiring up the platforms that can honour it follows separately. Co-Authored-By: Claude Opus 5 (1M context) --- gap.go | 68 ++++++++++++++++++++++++++++++++++++++++++ gap_connparams_test.go | 35 ++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 gap_connparams_test.go diff --git a/gap.go b/gap.go index 2c7aa51b..2d8d6b24 100644 --- a/gap.go +++ b/gap.go @@ -594,6 +594,61 @@ func (buf *rawAdvertisementPayload) addServiceUUID(uuid UUID) (ok bool) { } } +// ConnectionPriority is a coarse description of what a connection should be +// optimized for. +// +// It exists because not every platform accepts the connection parameters from +// the Bluetooth Core Specification. Some only accept a small fixed set of +// presets: on Windows, BluetoothLEPreferredConnectionParameters has no public +// constructor, just the static ThroughputOptimized, Balanced and PowerOptimized +// sets. A ConnectionPriority is the form of the request that those platforms +// can honour, and it maps onto the same three-way split that Android +// (CONNECTION_PRIORITY_HIGH, BALANCED and LOW_POWER) and CoreBluetooth +// (CBPeripheralManagerConnectionLatency low, medium and high) also use. +// +// The constants are named after the trade-off rather than after the resulting +// latency on purpose: those vendor enumerations do not agree on a direction, so +// that Apple's "low" and Android's "high" mean the same thing, and a name like +// "low" would be read backwards by half of the audience. It also keeps the term +// "connection latency" free, which the Core Specification uses for the number of +// connection events a peripheral may skip. +type ConnectionPriority uint8 + +const ( + // ConnectionPriorityUnspecified leaves the connection parameters unchanged. + // It is the zero value, matching the other ConnectionParams fields, which + // also mean "leave alone" when unset. + ConnectionPriorityUnspecified ConnectionPriority = iota + + // ConnectionPriorityThroughput optimizes for rapid communication, at the + // cost of power usage on both sides and of the number of connections the + // adapter can maintain at once. Suitable for a firmware update or another + // bulk transfer, and best used only for as long as it is needed. + ConnectionPriorityThroughput + + // ConnectionPriorityBalanced balances communication speed against power + // usage. + ConnectionPriorityBalanced + + // ConnectionPriorityPowerSaving optimizes for power usage and for the number + // of simultaneous connections, at the cost of communication speed. + ConnectionPriorityPowerSaving +) + +// String returns a lowercase name for the priority, for use in logs. +func (p ConnectionPriority) String() string { + switch p { + case ConnectionPriorityThroughput: + return "throughput" + case ConnectionPriorityBalanced: + return "balanced" + case ConnectionPriorityPowerSaving: + return "power-saving" + default: + return "unspecified" + } +} + // ConnectionParams are used when connecting to a peripherals or when changing // the parameters of an active connection. type ConnectionParams struct { @@ -612,6 +667,19 @@ type ConnectionParams struct { // communication, the connection is considered lost. If no timeout is // specified, the timeout will be unchanged. Timeout Duration + + // Priority is a coarse alternative to the fields above, for platforms that + // do not accept explicit connection parameters. + // + // The two are never mixed: a platform that can program the controller uses + // the explicit fields and ignores Priority, and a platform that only accepts + // presets uses Priority and ignores the explicit fields. Setting both is + // therefore the portable way to make a request, and is what a caller that + // runs on more than one platform should do. + // + // If Priority is unset, a platform that only accepts presets leaves the + // connection unchanged. + Priority ConnectionPriority } type PHY int diff --git a/gap_connparams_test.go b/gap_connparams_test.go new file mode 100644 index 00000000..51b8fb6e --- /dev/null +++ b/gap_connparams_test.go @@ -0,0 +1,35 @@ +package bluetooth + +import "testing" + +// The zero value has to stay ConnectionPriorityUnspecified, so that an unset +// Priority means "leave the connection alone" like every other field in +// ConnectionParams. Reordering the constants would silently change what an +// empty ConnectionParams asks for. +func TestConnectionPriorityZeroValue(t *testing.T) { + var priority ConnectionPriority + if priority != ConnectionPriorityUnspecified { + t.Errorf("zero value = %d, want ConnectionPriorityUnspecified", priority) + } + if got := (ConnectionParams{}).Priority; got != ConnectionPriorityUnspecified { + t.Errorf("ConnectionParams{}.Priority = %s, want unspecified", got) + } +} + +func TestConnectionPriorityString(t *testing.T) { + tests := []struct { + priority ConnectionPriority + want string + }{ + {ConnectionPriorityUnspecified, "unspecified"}, + {ConnectionPriorityThroughput, "throughput"}, + {ConnectionPriorityBalanced, "balanced"}, + {ConnectionPriorityPowerSaving, "power-saving"}, + {ConnectionPriority(99), "unspecified"}, + } + for _, test := range tests { + if got := test.priority.String(); got != test.want { + t.Errorf("ConnectionPriority(%d).String() = %q, want %q", test.priority, got, test.want) + } + } +}