From 5fdd50f89e5ca9ec0347570ddf33a6aba905a0af Mon Sep 17 00:00:00 2001 From: habibialireza Date: Wed, 17 Jun 2026 11:35:03 +0200 Subject: [PATCH 1/3] microphone gain control added to app --- lib/open_earable_flutter.dart | 1 + .../capabilities/microphone_gain_manager.dart | 70 +++++++++++++++++++ lib/src/models/devices/open_earable_v2.dart | 35 ++++++++++ 3 files changed, 106 insertions(+) create mode 100644 lib/src/models/capabilities/microphone_gain_manager.dart diff --git a/lib/open_earable_flutter.dart b/lib/open_earable_flutter.dart index a9cc926..a2ebce1 100644 --- a/lib/open_earable_flutter.dart +++ b/lib/open_earable_flutter.dart @@ -60,6 +60,7 @@ export 'src/models/capabilities/jingle_player.dart'; export 'src/models/capabilities/audio_player_controls.dart'; export 'src/models/capabilities/storage_path_audio_player.dart'; export 'src/models/capabilities/audio_mode_manager.dart'; +export 'src/models/capabilities/microphone_gain_manager.dart'; export 'src/models/capabilities/microphone_manager.dart'; export 'src/models/capabilities/power_saving_mode_manager.dart'; export 'src/models/capabilities/stereo_device.dart'; diff --git a/lib/src/models/capabilities/microphone_gain_manager.dart b/lib/src/models/capabilities/microphone_gain_manager.dart new file mode 100644 index 0000000..78b9f97 --- /dev/null +++ b/lib/src/models/capabilities/microphone_gain_manager.dart @@ -0,0 +1,70 @@ +abstract class MicrophoneGainManager { + Future getMicrophoneGain(); + + Future setMicrophoneGain(MicrophoneGain gain); +} + +class MicrophoneGain { + static const int maxGainRegister = 0x00; + static const int zeroDbRegister = 0x40; + static const int minGainRegister = 0xFE; + static const int muteRegister = 0xFF; + static const int defaultRegister = 0x20; + static const double stepDb = 0.375; + static const double maxGainDb = 24.0; + static const double minGainDb = -71.25; + + final int leftRegister; + final int rightRegister; + + const MicrophoneGain({ + required this.leftRegister, + required this.rightRegister, + }) : assert(leftRegister >= 0 && leftRegister <= muteRegister), + assert(rightRegister >= 0 && rightRegister <= muteRegister); + + const MicrophoneGain.stereo(int register) + : this(leftRegister: register, rightRegister: register); + + const MicrophoneGain.muted() + : this.stereo(muteRegister); + + bool get isMuted => + leftRegister == muteRegister && rightRegister == muteRegister; + + double? get leftDb => registerToDb(leftRegister); + + double? get rightDb => registerToDb(rightRegister); + + MicrophoneGain copyWith({ + int? leftRegister, + int? rightRegister, + }) { + return MicrophoneGain( + leftRegister: leftRegister ?? this.leftRegister, + rightRegister: rightRegister ?? this.rightRegister, + ); + } + + static double? registerToDb(int register) { + _validateRegister(register); + if (register == muteRegister) { + return null; + } + return maxGainDb - (register * stepDb); + } + + static int dbToRegister(double db) { + final clampedDb = db.clamp(minGainDb, maxGainDb).toDouble(); + return ((maxGainDb - clampedDb) / stepDb) + .round() + .clamp(maxGainRegister, minGainRegister) + .toInt(); + } + + static void _validateRegister(int register) { + if (register < 0 || register > muteRegister) { + throw RangeError.range(register, 0, muteRegister, 'register'); + } + } +} diff --git a/lib/src/models/devices/open_earable_v2.dart b/lib/src/models/devices/open_earable_v2.dart index 235c4fd..1a01c82 100644 --- a/lib/src/models/devices/open_earable_v2.dart +++ b/lib/src/models/devices/open_earable_v2.dart @@ -33,6 +33,8 @@ const String _micSelectCharacteristicUuid = "0x1410df97-5f68-4ebb-a7c7-5e0fb9ae7557"; const String _audioModeCharacteristicUuid = "0x1410df96-5f68-4ebb-a7c7-5e0fb9ae7557"; +const String _dmicGainCharacteristicUuid = + "1410df99-5f68-4ebb-a7c7-5e0fb9ae7557"; const String _buttonServiceUuid = "29c10bdc-4773-11ee-be56-0242ac120002"; const String _buttonCharacteristicUuid = "29c10f38-4773-11ee-be56-0242ac120002"; @@ -76,6 +78,7 @@ class OpenEarableV2 extends BluetoothWearable DeviceFirmwareVersion, DeviceHardwareVersion, MicrophoneManager, + MicrophoneGainManager, AudioModeManager, EdgeRecorderManager, ButtonManager, @@ -460,6 +463,38 @@ class OpenEarableV2 extends BluetoothWearable return availableMicrophones.firstWhere((mic) => mic.id == microphoneId); } + // MARK: MicrophoneGainManager + + @override + Future getMicrophoneGain() async { + final gainBytes = await bleManager.read( + deviceId: deviceId, + serviceId: _audioConfigServiceUuid, + characteristicId: _dmicGainCharacteristicUuid, + ); + + if (gainBytes.length != 2) { + throw StateError( + 'Microphone gain characteristic expected 2 values, but got ${gainBytes.length}', + ); + } + + return MicrophoneGain( + leftRegister: gainBytes[0], + rightRegister: gainBytes[1], + ); + } + + @override + Future setMicrophoneGain(MicrophoneGain gain) { + return bleManager.write( + deviceId: deviceId, + serviceId: _audioConfigServiceUuid, + characteristicId: _dmicGainCharacteristicUuid, + byteData: [gain.leftRegister, gain.rightRegister], + ); + } + // MARK: AudioModeManager @override From 7d06150413d68c2bbfc07a8506b79a379b7099c5 Mon Sep 17 00:00:00 2001 From: Oliver Bagge Date: Tue, 28 Jul 2026 12:48:06 +0200 Subject: [PATCH 2/3] fix(device): rename mic gain channels and preserve update settings --- .../handlers/firmware_update_handler.dart | 20 +++++++++---- .../capabilities/microphone_gain_manager.dart | 28 +++++++++---------- .../capabilities/microphone_manager.dart | 3 +- lib/src/models/devices/open_earable_v2.dart | 10 +++---- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/lib/src/fota/handlers/firmware_update_handler.dart b/lib/src/fota/handlers/firmware_update_handler.dart index 9e7ec1f..22ec52d 100644 --- a/lib/src/fota/handlers/firmware_update_handler.dart +++ b/lib/src/fota/handlers/firmware_update_handler.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter_archive/flutter_archive.dart'; import 'package:mcumgr_flutter/models/firmware_upgrade_mode.dart'; +import 'package:mcumgr_flutter/models/image_upload_alignment.dart'; import '../model/firmware_update_request.dart'; import '../model/manifest.dart'; @@ -147,6 +148,17 @@ class FirmwareUnpacker extends FirmwareUpdateHandler { class FirmwareUpdater extends FirmwareUpdateHandler { final UpdateManagerFactory _updateManagerFactory = FirmwareUpdateManagerFactory(); + static const FirmwareUpgradeConfiguration _defaultUpgradeConfiguration = + FirmwareUpgradeConfiguration( + // Ask MCUboot to test the image and reboot into it. The firmware + // confirms itself very early during boot, so confirming again from the + // phone is unnecessary and can race with the swap flow. + estimatedSwapTime: Duration(seconds: 90), + // Preserve persisted device state such as Bluetooth bond information + // during ordinary firmware upgrades. + eraseAppSettings: false, + firmwareUpgradeMode: FirmwareUpgradeMode.testOnly, + ); @override Future handleFirmwareUpdate( @@ -168,18 +180,14 @@ class FirmwareUpdater extends FirmwareUpdateHandler { final fwImage = request.firmwareImage; await updateManager.updateWithImageData( imageData: fwImage!, - configuration: const FirmwareUpgradeConfiguration( - firmwareUpgradeMode: FirmwareUpgradeMode.testOnly, - ), + configuration: _defaultUpgradeConfiguration, ); return updateManager; } else { final multiImageRequest = request as MultiImageFirmwareUpdateRequest; updateManager.update( multiImageRequest.firmwareImages!, - configuration: const FirmwareUpgradeConfiguration( - firmwareUpgradeMode: FirmwareUpgradeMode.testOnly, - ), + configuration: _defaultUpgradeConfiguration, ); } diff --git a/lib/src/models/capabilities/microphone_gain_manager.dart b/lib/src/models/capabilities/microphone_gain_manager.dart index 78b9f97..bea9c33 100644 --- a/lib/src/models/capabilities/microphone_gain_manager.dart +++ b/lib/src/models/capabilities/microphone_gain_manager.dart @@ -14,35 +14,35 @@ class MicrophoneGain { static const double maxGainDb = 24.0; static const double minGainDb = -71.25; - final int leftRegister; - final int rightRegister; + final int externalRegister; + final int internalRegister; const MicrophoneGain({ - required this.leftRegister, - required this.rightRegister, - }) : assert(leftRegister >= 0 && leftRegister <= muteRegister), - assert(rightRegister >= 0 && rightRegister <= muteRegister); + required this.externalRegister, + required this.internalRegister, + }) : assert(externalRegister >= 0 && externalRegister <= muteRegister), + assert(internalRegister >= 0 && internalRegister <= muteRegister); const MicrophoneGain.stereo(int register) - : this(leftRegister: register, rightRegister: register); + : this(externalRegister: register, internalRegister: register); const MicrophoneGain.muted() : this.stereo(muteRegister); bool get isMuted => - leftRegister == muteRegister && rightRegister == muteRegister; + externalRegister == muteRegister && internalRegister == muteRegister; - double? get leftDb => registerToDb(leftRegister); + double? get externalDb => registerToDb(externalRegister); - double? get rightDb => registerToDb(rightRegister); + double? get internalDb => registerToDb(internalRegister); MicrophoneGain copyWith({ - int? leftRegister, - int? rightRegister, + int? externalRegister, + int? internalRegister, }) { return MicrophoneGain( - leftRegister: leftRegister ?? this.leftRegister, - rightRegister: rightRegister ?? this.rightRegister, + externalRegister: externalRegister ?? this.externalRegister, + internalRegister: internalRegister ?? this.internalRegister, ); } diff --git a/lib/src/models/capabilities/microphone_manager.dart b/lib/src/models/capabilities/microphone_manager.dart index 00cbf5e..535ab3d 100644 --- a/lib/src/models/capabilities/microphone_manager.dart +++ b/lib/src/models/capabilities/microphone_manager.dart @@ -5,7 +5,8 @@ abstract class MicrophoneManager { required this.availableMicrophones, }); - void setMicrophone(MC microphone); + Future setMicrophone(MC microphone); + Future getMicrophone(); } diff --git a/lib/src/models/devices/open_earable_v2.dart b/lib/src/models/devices/open_earable_v2.dart index 1a01c82..d8864f0 100644 --- a/lib/src/models/devices/open_earable_v2.dart +++ b/lib/src/models/devices/open_earable_v2.dart @@ -432,12 +432,12 @@ class OpenEarableV2 extends BluetoothWearable // MARK: MicrophoneManager @override - void setMicrophone(OpenEarableV2Mic microphone) { + Future setMicrophone(OpenEarableV2Mic microphone) { if (!availableMicrophones.contains(microphone)) { throw ArgumentError('Microphone not available: ${microphone.key}'); } - bleManager.write( + return bleManager.write( deviceId: deviceId, serviceId: _audioConfigServiceUuid, characteristicId: _micSelectCharacteristicUuid, @@ -480,8 +480,8 @@ class OpenEarableV2 extends BluetoothWearable } return MicrophoneGain( - leftRegister: gainBytes[0], - rightRegister: gainBytes[1], + externalRegister: gainBytes[0], + internalRegister: gainBytes[1], ); } @@ -491,7 +491,7 @@ class OpenEarableV2 extends BluetoothWearable deviceId: deviceId, serviceId: _audioConfigServiceUuid, characteristicId: _dmicGainCharacteristicUuid, - byteData: [gain.leftRegister, gain.rightRegister], + byteData: [gain.externalRegister, gain.internalRegister], ); } From 8032fe8b69518e89826b9d20828717305e81ffe6 Mon Sep 17 00:00:00 2001 From: Oliver Bagge Date: Wed, 29 Jul 2026 10:29:29 +0200 Subject: [PATCH 3/3] fix: remove unused import in firmware_update_handler.dart --- lib/src/fota/handlers/firmware_update_handler.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/fota/handlers/firmware_update_handler.dart b/lib/src/fota/handlers/firmware_update_handler.dart index 22ec52d..6c70512 100644 --- a/lib/src/fota/handlers/firmware_update_handler.dart +++ b/lib/src/fota/handlers/firmware_update_handler.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:flutter_archive/flutter_archive.dart'; import 'package:mcumgr_flutter/models/firmware_upgrade_mode.dart'; -import 'package:mcumgr_flutter/models/image_upload_alignment.dart'; import '../model/firmware_update_request.dart'; import '../model/manifest.dart';