Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Unreleased

* added wireless audio policy configuration and runtime-state support for OpenEarable V2 devices exposing the new protocol service.

## 2.4.1

* fixed microphone gain capability detection so it is only registered when the device exposes the required characteristic.
Expand Down
32 changes: 32 additions & 0 deletions doc/CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,38 @@ if (audioModeManager != null) {

---

### WirelessAudioConfigurationManager

Configures device-owned Bluetooth audio policies on firmware that exposes the
wireless audio configuration protocol. The capability also provides the
effective LE Audio runtime state negotiated by the standard Bluetooth services.

```dart
final audioConfiguration =
wearable.getCapability<WirelessAudioConfigurationManager>();
if (audioConfiguration != null) {
final capabilities = await audioConfiguration.getCapabilities();
final runtimeState = await audioConfiguration.getRuntimeState();

await audioConfiguration.setAclConnectionPolicy(
WirelessAudioConfigurationAclConnectionPolicy.fixedAclPolicy(
WirelessAudioConfigurationFixedAclPolicy(
interval_us: 15000,
peripheral_latency: 0,
supervision_timeout_ms: 4000,
),
),
);
}
```

Check the device-reported capabilities before applying a policy. Policy
requests remain subject to negotiation by the Bluetooth controller and peer;
use `getRuntimeState()` or `subscribeToRuntimeState()` to observe the effective
values.

---

### PowerSavingModeManager

Reads the power saving modes that the firmware currently supports, including
Expand Down
9 changes: 5 additions & 4 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -418,10 +418,11 @@ packages:
open_earable_protocols:
dependency: transitive
description:
name: open_earable_protocols
sha256: c11cae4914827c1d7617d44647a5c49304f0c2a2c02c907c595682cfade6ddb2
url: "https://pub.dev"
source: hosted
path: "generated/dart"
ref: main
resolved-ref: a4b6c08f5a2b7b59f53b7f02a209299c1f2c1864
url: "https://github.com/OpenEarable/protocol.git"
source: git
version: "0.0.2"
package_config:
dependency: transitive
Expand Down
1 change: 1 addition & 0 deletions lib/open_earable_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export 'src/models/capabilities/system_device.dart';
export 'src/managers/ble_gatt_manager.dart';
export 'src/models/capabilities/time_synchronizable.dart';
export 'src/models/capabilities/audio_response_manager.dart';
export 'src/models/capabilities/wireless_audio_configuration_manager.dart';

export 'src/fota/fota.dart';

Expand Down
4 changes: 3 additions & 1 deletion lib/src/managers/ble_gatt_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ abstract class BleGattManager {
/// Subscribes to a specific characteristic of the connected device.
///
/// The returned future completes only after the underlying GATT
/// notification subscription has been enabled.
/// subscription has been enabled. Set [indications] for an indication-only
/// characteristic; notification mode is used by default.
Future<Stream<List<int>>> subscribe({
required String deviceId,
required String serviceId,
required String characteristicId,
bool indications = false,
});

/// Reads data from a specific characteristic of the connected device.
Expand Down
18 changes: 12 additions & 6 deletions lib/src/managers/ble_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ class BleManager extends BleGattManager {
required String deviceId,
required String serviceId,
required String characteristicId,
bool indications = false,
}) async {
logger.d(
"Subscribing to $deviceId, service $serviceId, characteristic $characteristicId",
Expand Down Expand Up @@ -379,12 +380,17 @@ class BleManager extends BleGattManager {
streamController ??= StreamController<List<int>>.broadcast();
_streamControllers[streamIdentifier] = streamController;

_subscriptionSetups[streamIdentifier] ??=
UniversalBle.subscribeNotifications(
deviceId,
serviceId,
characteristicId,
);
_subscriptionSetups[streamIdentifier] ??= indications
? UniversalBle.subscribeIndications(
deviceId,
serviceId,
characteristicId,
)
: UniversalBle.subscribeNotifications(
deviceId,
serviceId,
characteristicId,
);

try {
await _subscriptionSetups[streamIdentifier];
Expand Down
118 changes: 118 additions & 0 deletions lib/src/models/capabilities/wireless_audio_configuration_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import 'package:open_earable_protocols/open_earable_protocols.dart';

export 'package:open_earable_protocols/open_earable_protocols.dart'
show
WirelessAudioConfigurationAclConnectionPolicy,
WirelessAudioConfigurationAclRadioPolicy,
WirelessAudioConfigurationAdaptiveLinearAclPolicy,
WirelessAudioConfigurationAutomaticAclRadioPolicy,
WirelessAudioConfigurationCapabilities,
WirelessAudioConfigurationCommandResult,
WirelessAudioConfigurationConfiguredAclConnectionPolicy,
WirelessAudioConfigurationConfiguredAclRadioPolicy,
WirelessAudioConfigurationConfiguredUnicastServerQosPreferences,
WirelessAudioConfigurationControllerDefaultAclPolicy,
WirelessAudioConfigurationFixedAclPolicy,
WirelessAudioConfigurationPreferredAclRadioPolicy,
WirelessAudioConfigurationPreferredRangeAclPolicy,
WirelessAudioConfigurationRuntimeState,
WirelessAudioConfigurationUnicastServerQosPreferences;

/// A configurable section of the wireless audio policy protocol.
enum WirelessAudioConfigurationSection {
/// ACL connection interval, latency, and supervision timeout policy.
aclConnectionPolicy(0),

/// ACL PHY and data-length policy.
aclRadioPolicy(1),

/// Preferences advertised by the Unicast Server during codec setup.
unicastServerQosPreferences(2);

/// Creates a section with its stable protocol identifier.
const WirelessAudioConfigurationSection(this.id);

/// Stable identifier used by the protocol.
final int id;

/// Bit used for this section in protocol masks.
int get mask => 1 << id;
}

/// Controls OpenEarable's device-owned Bluetooth audio policies.
///
/// Standard LE Audio codec and stream negotiation continues to use PACS and
/// ASCS. This capability configures only the device's local policy and exposes
/// the resulting runtime state.
abstract class WirelessAudioConfigurationManager {
/// Reads the policy features and ranges supported by the device.
Future<WirelessAudioConfigurationCapabilities> getCapabilities();

/// Reads the latest effective Bluetooth audio runtime state.
Future<WirelessAudioConfigurationRuntimeState> getRuntimeState();

/// Subscribes to effective Bluetooth audio runtime-state changes.
///
/// Awaiting this method guarantees that the BLE notification subscription is
/// active before the returned stream is consumed.
Future<Stream<WirelessAudioConfigurationRuntimeState>>
subscribeToRuntimeState();

/// Sets the ACL connection [policy].
///
/// When [persist] is true, the device retains the setting across restarts.
Future<WirelessAudioConfigurationCommandResult> setAclConnectionPolicy(
WirelessAudioConfigurationAclConnectionPolicy policy, {
bool persist = false,
});

/// Sets the ACL radio [policy].
///
/// When [persist] is true, the device retains the setting across restarts.
Future<WirelessAudioConfigurationCommandResult> setAclRadioPolicy(
WirelessAudioConfigurationAclRadioPolicy policy, {
bool persist = false,
});

/// Sets the Unicast Server QoS [preferences].
///
/// When [persist] is true, the device retains the setting across restarts.
Future<WirelessAudioConfigurationCommandResult>
setUnicastServerQosPreferences(
WirelessAudioConfigurationUnicastServerQosPreferences preferences, {
bool persist = false,
});

/// Reads the configured ACL connection policy.
Future<WirelessAudioConfigurationConfiguredAclConnectionPolicy>
getAclConnectionPolicy();

/// Reads the configured ACL radio policy.
Future<WirelessAudioConfigurationConfiguredAclRadioPolicy>
getAclRadioPolicy();

/// Reads the configured Unicast Server QoS preferences.
Future<WirelessAudioConfigurationConfiguredUnicastServerQosPreferences>
getUnicastServerQosPreferences();

/// Restores compiled defaults for [sections].
///
/// An empty set restores every section supported by the device.
Future<WirelessAudioConfigurationCommandResult> restoreDefaults({
Set<WirelessAudioConfigurationSection> sections = const {},
});
}

/// Indicates that a wireless audio configuration command was rejected.
class WirelessAudioConfigurationException implements Exception {
/// Creates an exception for the rejected protocol [result].
const WirelessAudioConfigurationException(this.result);

/// Rejection details reported by the device.
final WirelessAudioConfigurationCommandResult result;

@override
String toString() =>
'WirelessAudioConfigurationException(status: ${result.status}, '
'errorDomain: ${result.error_domain}, errorCode: ${result.error_code})';
}
14 changes: 14 additions & 0 deletions lib/src/models/devices/open_earable_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import '../capabilities/sensor_configuration_specializations/sensor_configuratio
import '../capabilities/sensor_configuration_specializations/streamable_sensor_configuration.dart';
import '../capabilities/system_device.dart';
import '../capabilities/time_synchronizable.dart';
import '../capabilities/wireless_audio_configuration_manager.dart';
import 'discovered_device.dart';
import 'open_earable_v1.dart';
import 'open_earable_v2.dart';
import 'open_earable_v2_audio_response_manager.dart';
import 'open_earable_v2_microphone_gain_manager.dart';
import 'open_earable_v2_wireless_audio_configuration_manager.dart';
import 'wearable.dart';
import '../../fota/firmware_slot_manager_impl.dart';

Expand All @@ -45,6 +47,7 @@ class OpenEarableFactory extends WearableFactory {
Set<String> get usedServiceUuids => {
...OpenEarableV1.serviceUuids,
...OpenEarableV2.serviceUuids,
WirelessAudioConfigurationBleUuids.serviceUuid,
mcuMgrSmpServiceUuid,
timeSynchronizationServiceUuid,
};
Expand Down Expand Up @@ -132,6 +135,17 @@ class OpenEarableFactory extends WearableFactory {
),
);
}
if (await bleManager!.hasService(
deviceId: device.id,
serviceId: WirelessAudioConfigurationBleUuids.serviceUuid,
)) {
wearable.registerCapability<WirelessAudioConfigurationManager>(
OpenEarableV2WirelessAudioConfigurationManager(
bleManager: bleManager!,
deviceId: device.id,
),
);
}
if (await bleManager!.hasCharacteristic(
deviceId: device.id,
serviceId: OpenEarableV2MicrophoneGainManager.serviceUuid,
Expand Down
Loading
Loading