Skip to content

Commit 99ce1e5

Browse files
microphone gain control added to app (#140)
* microphone gain control added to app * fix(device): rename mic gain channels and preserve update settings * fix: remove unused import in firmware_update_handler.dart * rename: change external/internal to outer/inner to be consistent with the app --------- Co-authored-by: Oliver Bagge <bagge@teco.edu>
1 parent a9d8bd6 commit 99ce1e5

5 files changed

Lines changed: 123 additions & 9 deletions

File tree

lib/open_earable_flutter.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export 'src/models/capabilities/jingle_player.dart';
6060
export 'src/models/capabilities/audio_player_controls.dart';
6161
export 'src/models/capabilities/storage_path_audio_player.dart';
6262
export 'src/models/capabilities/audio_mode_manager.dart';
63+
export 'src/models/capabilities/microphone_gain_manager.dart';
6364
export 'src/models/capabilities/microphone_manager.dart';
6465
export 'src/models/capabilities/power_saving_mode_manager.dart';
6566
export 'src/models/capabilities/stereo_device.dart';

lib/src/fota/handlers/firmware_update_handler.dart

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,17 @@ class FirmwareUnpacker extends FirmwareUpdateHandler {
147147
class FirmwareUpdater extends FirmwareUpdateHandler {
148148
final UpdateManagerFactory _updateManagerFactory =
149149
FirmwareUpdateManagerFactory();
150+
static const FirmwareUpgradeConfiguration _defaultUpgradeConfiguration =
151+
FirmwareUpgradeConfiguration(
152+
// Ask MCUboot to test the image and reboot into it. The firmware
153+
// confirms itself very early during boot, so confirming again from the
154+
// phone is unnecessary and can race with the swap flow.
155+
estimatedSwapTime: Duration(seconds: 90),
156+
// Preserve persisted device state such as Bluetooth bond information
157+
// during ordinary firmware upgrades.
158+
eraseAppSettings: false,
159+
firmwareUpgradeMode: FirmwareUpgradeMode.testOnly,
160+
);
150161

151162
@override
152163
Future<FirmwareUpdateManager> handleFirmwareUpdate(
@@ -168,18 +179,14 @@ class FirmwareUpdater extends FirmwareUpdateHandler {
168179
final fwImage = request.firmwareImage;
169180
await updateManager.updateWithImageData(
170181
imageData: fwImage!,
171-
configuration: const FirmwareUpgradeConfiguration(
172-
firmwareUpgradeMode: FirmwareUpgradeMode.testOnly,
173-
),
182+
configuration: _defaultUpgradeConfiguration,
174183
);
175184
return updateManager;
176185
} else {
177186
final multiImageRequest = request as MultiImageFirmwareUpdateRequest;
178187
updateManager.update(
179188
multiImageRequest.firmwareImages!,
180-
configuration: const FirmwareUpgradeConfiguration(
181-
firmwareUpgradeMode: FirmwareUpgradeMode.testOnly,
182-
),
189+
configuration: _defaultUpgradeConfiguration,
183190
);
184191
}
185192

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
abstract class MicrophoneGainManager {
2+
Future<MicrophoneGain> getMicrophoneGain();
3+
4+
Future<void> setMicrophoneGain(MicrophoneGain gain);
5+
}
6+
7+
class MicrophoneGain {
8+
static const int maxGainRegister = 0x00;
9+
static const int zeroDbRegister = 0x40;
10+
static const int minGainRegister = 0xFE;
11+
static const int muteRegister = 0xFF;
12+
static const int defaultRegister = 0x20;
13+
static const double stepDb = 0.375;
14+
static const double maxGainDb = 24.0;
15+
static const double minGainDb = -71.25;
16+
17+
final int outerRegister;
18+
final int innerRegister;
19+
20+
const MicrophoneGain({
21+
required this.outerRegister,
22+
required this.innerRegister,
23+
}) : assert(outerRegister >= 0 && outerRegister <= muteRegister),
24+
assert(innerRegister >= 0 && innerRegister <= muteRegister);
25+
26+
const MicrophoneGain.stereo(int register)
27+
: this(outerRegister: register, innerRegister: register);
28+
29+
const MicrophoneGain.muted()
30+
: this.stereo(muteRegister);
31+
32+
bool get isMuted =>
33+
outerRegister == muteRegister && innerRegister == muteRegister;
34+
35+
double? get outerDb => registerToDb(outerRegister);
36+
37+
double? get innerDb => registerToDb(innerRegister);
38+
39+
MicrophoneGain copyWith({
40+
int? outerRegister,
41+
int? innerRegister,
42+
}) {
43+
return MicrophoneGain(
44+
outerRegister: outerRegister ?? this.outerRegister,
45+
innerRegister: innerRegister ?? this.innerRegister,
46+
);
47+
}
48+
49+
static double? registerToDb(int register) {
50+
_validateRegister(register);
51+
if (register == muteRegister) {
52+
return null;
53+
}
54+
return maxGainDb - (register * stepDb);
55+
}
56+
57+
static int dbToRegister(double db) {
58+
final clampedDb = db.clamp(minGainDb, maxGainDb).toDouble();
59+
return ((maxGainDb - clampedDb) / stepDb)
60+
.round()
61+
.clamp(maxGainRegister, minGainRegister)
62+
.toInt();
63+
}
64+
65+
static void _validateRegister(int register) {
66+
if (register < 0 || register > muteRegister) {
67+
throw RangeError.range(register, 0, muteRegister, 'register');
68+
}
69+
}
70+
}

lib/src/models/capabilities/microphone_manager.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ abstract class MicrophoneManager<MC extends Microphone> {
55
required this.availableMicrophones,
66
});
77

8-
void setMicrophone(MC microphone);
8+
Future<void> setMicrophone(MC microphone);
9+
910
Future<MC> getMicrophone();
1011
}
1112

lib/src/models/devices/open_earable_v2.dart

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const String _micSelectCharacteristicUuid =
3333
"0x1410df97-5f68-4ebb-a7c7-5e0fb9ae7557";
3434
const String _audioModeCharacteristicUuid =
3535
"0x1410df96-5f68-4ebb-a7c7-5e0fb9ae7557";
36+
const String _dmicGainCharacteristicUuid =
37+
"1410df99-5f68-4ebb-a7c7-5e0fb9ae7557";
3638

3739
const String _buttonServiceUuid = "29c10bdc-4773-11ee-be56-0242ac120002";
3840
const String _buttonCharacteristicUuid = "29c10f38-4773-11ee-be56-0242ac120002";
@@ -76,6 +78,7 @@ class OpenEarableV2 extends BluetoothWearable
7678
DeviceFirmwareVersion,
7779
DeviceHardwareVersion,
7880
MicrophoneManager<OpenEarableV2Mic>,
81+
MicrophoneGainManager,
7982
AudioModeManager,
8083
EdgeRecorderManager,
8184
ButtonManager,
@@ -455,12 +458,12 @@ class OpenEarableV2 extends BluetoothWearable
455458
// MARK: MicrophoneManager
456459

457460
@override
458-
void setMicrophone(OpenEarableV2Mic microphone) {
461+
Future<void> setMicrophone(OpenEarableV2Mic microphone) {
459462
if (!availableMicrophones.contains(microphone)) {
460463
throw ArgumentError('Microphone not available: ${microphone.key}');
461464
}
462465

463-
bleManager.write(
466+
return bleManager.write(
464467
deviceId: deviceId,
465468
serviceId: _audioConfigServiceUuid,
466469
characteristicId: _micSelectCharacteristicUuid,
@@ -486,6 +489,38 @@ class OpenEarableV2 extends BluetoothWearable
486489
return availableMicrophones.firstWhere((mic) => mic.id == microphoneId);
487490
}
488491

492+
// MARK: MicrophoneGainManager
493+
494+
@override
495+
Future<MicrophoneGain> getMicrophoneGain() async {
496+
final gainBytes = await bleManager.read(
497+
deviceId: deviceId,
498+
serviceId: _audioConfigServiceUuid,
499+
characteristicId: _dmicGainCharacteristicUuid,
500+
);
501+
502+
if (gainBytes.length != 2) {
503+
throw StateError(
504+
'Microphone gain characteristic expected 2 values, but got ${gainBytes.length}',
505+
);
506+
}
507+
508+
return MicrophoneGain(
509+
outerRegister: gainBytes[0],
510+
innerRegister: gainBytes[1],
511+
);
512+
}
513+
514+
@override
515+
Future<void> setMicrophoneGain(MicrophoneGain gain) {
516+
return bleManager.write(
517+
deviceId: deviceId,
518+
serviceId: _audioConfigServiceUuid,
519+
characteristicId: _dmicGainCharacteristicUuid,
520+
byteData: [gain.outerRegister, gain.innerRegister],
521+
);
522+
}
523+
489524
// MARK: AudioModeManager
490525

491526
@override

0 commit comments

Comments
 (0)