-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
358 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import 'package:rxdart/rxdart.dart'; | ||
import '../framework/bluetooth_headphones.dart'; | ||
import '../framework/headphones_info.dart'; | ||
import '../framework/headphones_settings.dart'; | ||
import '../framework/lrc_battery.dart'; | ||
import 'settings.dart'; | ||
|
||
/// Base abstract class of 4i's. It contains static info like vendor names etc, | ||
/// but no logic whatsoever. | ||
/// | ||
/// It makes both a solid ground for actual implementation (by defining what | ||
/// features they implement), and some basic info for easy simulation | ||
abstract base class HuaweiFreeBudsSE2 | ||
implements | ||
BluetoothHeadphones, | ||
HeadphonesModelInfo, | ||
LRCBattery, | ||
HeadphonesSettings<HuaweiFreeBudsSE2Settings> { | ||
const HuaweiFreeBudsSE2(); | ||
|
||
@override | ||
String get vendor => "Huawei"; | ||
|
||
@override | ||
String get name => "FreeBuds SE 2"; | ||
|
||
// NOTE/WARNING: Again as in HeadphonesModelInfo - i'm not sure if it's safe | ||
// to just leave it like that, but I will 🥰🥰 | ||
@override | ||
ValueStream<String> get imageAssetPath => | ||
BehaviorSubject.seeded('assets/app_icons/ic_launcher.png'); | ||
|
||
// As I said everywhere else - i have no good idea where to put this stuff :/ | ||
// This will be a bit of chaos for now 👍👍 | ||
static final idNameRegex = | ||
RegExp(r'^(?=(HUAWEI FreeBuds SE 2))', caseSensitive: true); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:collection/collection.dart'; | ||
import 'package:rxdart/rxdart.dart'; | ||
import 'package:stream_channel/stream_channel.dart'; | ||
import 'package:the_last_bluetooth/the_last_bluetooth.dart' as tlb; | ||
|
||
import '../../logger.dart'; | ||
import '../framework/lrc_battery.dart'; | ||
import 'freebudsse2.dart'; | ||
import 'mbb.dart'; | ||
import 'settings.dart'; | ||
|
||
final class HuaweiFreeBudsSE2Impl extends HuaweiFreeBudsSE2 { | ||
final tlb.BluetoothDevice _bluetoothDevice; | ||
|
||
/// Bluetooth serial port that we communicate over | ||
final StreamChannel<MbbCommand> _mbb; | ||
|
||
// * stream controllers | ||
final _batteryLevelCtrl = BehaviorSubject<int>(); | ||
final _bluetoothAliasCtrl = BehaviorSubject<String>(); | ||
final _bluetoothNameCtrl = BehaviorSubject<String>(); | ||
final _lrcBatteryCtrl = BehaviorSubject<LRCBatteryLevels>(); | ||
final _settingsCtrl = BehaviorSubject<HuaweiFreeBudsSE2Settings>(); | ||
|
||
// stream controllers * | ||
|
||
/// This watches if we are still missing any info and re-requests it | ||
late StreamSubscription _watchdogStreamSub; | ||
|
||
HuaweiFreeBudsSE2Impl(this._mbb, this._bluetoothDevice) { | ||
// hope this will nicely play with closing, idk honestly | ||
final aliasStreamSub = _bluetoothDevice.alias | ||
.listen((alias) => _bluetoothAliasCtrl.add(alias)); | ||
_bluetoothAliasCtrl.onCancel = () => aliasStreamSub.cancel(); | ||
|
||
_mbb.stream.listen( | ||
(e) { | ||
try { | ||
_evalMbbCommand(e); | ||
} catch (e, s) { | ||
logg.e(e, stackTrace: s); | ||
} | ||
}, | ||
onError: logg.onError, | ||
onDone: () { | ||
_watchdogStreamSub.cancel(); | ||
|
||
// close all streams | ||
_batteryLevelCtrl.close(); | ||
_bluetoothAliasCtrl.close(); | ||
_bluetoothNameCtrl.close(); | ||
_lrcBatteryCtrl.close(); | ||
_settingsCtrl.close(); | ||
}, | ||
); | ||
_initRequestInfo(); | ||
_watchdogStreamSub = | ||
Stream.periodic(const Duration(seconds: 3)).listen((_) { | ||
if ([ | ||
batteryLevel.valueOrNull, | ||
// no alias because it's okay to be null 👍 | ||
lrcBattery.valueOrNull, | ||
settings.valueOrNull, | ||
].any((e) => e == null)) { | ||
_initRequestInfo(); | ||
} | ||
}); | ||
} | ||
|
||
void _evalMbbCommand(MbbCommand cmd) { | ||
final lastSettings = | ||
_settingsCtrl.valueOrNull ?? const HuaweiFreeBudsSE2Settings(); | ||
switch (cmd.args) { | ||
// # BatteryLevels | ||
case {2: var level, 3: var status} | ||
when cmd.serviceId == 1 && | ||
(cmd.commandId == 39 || cmd.commandId == 8): | ||
_lrcBatteryCtrl.add(LRCBatteryLevels( | ||
level[0] == 0 ? null : level[0], | ||
level[1] == 0 ? null : level[1], | ||
level[2] == 0 ? null : level[2], | ||
status[0] == 1, | ||
status[1] == 1, | ||
status[2] == 1, | ||
)); | ||
break; | ||
// # Settings(gestureDoubleTap) | ||
case {1: [var leftCode, ...], 2: [var rightCode, ...]} | ||
when cmd.isAbout(_Cmd.getGestureDoubleTap): | ||
_settingsCtrl.add( | ||
lastSettings.copyWith( | ||
doubleTapLeft: | ||
DoubleTap.values.firstWhereOrNull((e) => e.mbbCode == leftCode), | ||
doubleTapRight: DoubleTap.values | ||
.firstWhereOrNull((e) => e.mbbCode == rightCode), | ||
), | ||
); | ||
break; | ||
} | ||
} | ||
|
||
Future<void> _initRequestInfo() async { | ||
_mbb.sink.add(_Cmd.getBattery); | ||
_mbb.sink.add(_Cmd.getGestureDoubleTap); | ||
} | ||
|
||
@override | ||
ValueStream<int> get batteryLevel => _bluetoothDevice.battery; | ||
|
||
// i could pass btDevice.alias directly here, but Headphones take care | ||
// of closing everything | ||
@override | ||
ValueStream<String> get bluetoothAlias => _bluetoothAliasCtrl.stream; | ||
|
||
// huh, my past self thought that names will not change... and my future | ||
// (implementing TLB) thought otherwise 🤷🤷 | ||
@override | ||
String get bluetoothName => _bluetoothDevice.name.valueOrNull ?? "Unknown"; | ||
|
||
@override | ||
String get macAddress => _bluetoothDevice.mac; | ||
|
||
@override | ||
ValueStream<LRCBatteryLevels> get lrcBattery => _lrcBatteryCtrl.stream; | ||
|
||
@override | ||
ValueStream<HuaweiFreeBudsSE2Settings> get settings => _settingsCtrl.stream; | ||
|
||
@override | ||
Future<void> setSettings(newSettings) async { | ||
final prev = _settingsCtrl.valueOrNull ?? const HuaweiFreeBudsSE2Settings(); | ||
// this is VERY much a boilerplate | ||
// ...and, bloat... | ||
// and i don't think there is a need to export it somewhere else 🤷, | ||
// or make some other abstraction for it - maybe some day | ||
if ((newSettings.doubleTapLeft ?? prev.doubleTapLeft) != | ||
prev.doubleTapLeft) { | ||
_mbb.sink.add(_Cmd.gestureDoubleTap(left: newSettings.doubleTapLeft!)); | ||
_mbb.sink.add(_Cmd.getGestureDoubleTap); | ||
} | ||
if ((newSettings.doubleTapRight ?? prev.doubleTapRight) != | ||
prev.doubleTapRight) { | ||
_mbb.sink.add(_Cmd.gestureDoubleTap(right: newSettings.doubleTapRight!)); | ||
_mbb.sink.add(_Cmd.getGestureDoubleTap); | ||
} | ||
} | ||
} | ||
|
||
/// This is just a holder for magic numbers | ||
/// This isn't very pretty, or eliminates all of the boilerplate... but i | ||
/// feel like nothing will so let's love it as it is <3 | ||
abstract class _Cmd { | ||
static const getBattery = MbbCommand(1, 8); | ||
|
||
static const getGestureDoubleTap = MbbCommand(1, 32); | ||
|
||
static MbbCommand gestureDoubleTap({DoubleTap? left, DoubleTap? right}) => | ||
MbbCommand(1, 31, { | ||
if (left != null) 1: [left.mbbCode], | ||
if (right != null) 2: [right.mbbCode], | ||
}); | ||
} | ||
|
||
extension _FBSE2DoubleTap on DoubleTap { | ||
int get mbbCode => switch (this) { | ||
DoubleTap.nothing => 255, | ||
DoubleTap.voiceAssistant => 0, | ||
DoubleTap.playPause => 1, | ||
DoubleTap.next => 2, | ||
DoubleTap.previous => 7 | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import 'package:rxdart/rxdart.dart'; | ||
|
||
import '../simulators/bluetooth_headphones_sim.dart'; | ||
import '../simulators/lrc_battery_sim.dart'; | ||
import 'freebudsse2.dart'; | ||
import 'settings.dart'; | ||
|
||
final class HuaweiFreeBudsSE2Sim extends HuaweiFreeBudsSE2 | ||
with BluetoothHeadphonesSim, LRCBatteryAlwaysFullSim { | ||
// ehhhhhh... | ||
|
||
final _settingsCtrl = BehaviorSubject<HuaweiFreeBudsSE2Settings>.seeded( | ||
const HuaweiFreeBudsSE2Settings( | ||
doubleTapLeft: DoubleTap.playPause, | ||
doubleTapRight: DoubleTap.playPause, | ||
), | ||
); | ||
|
||
@override | ||
ValueStream<HuaweiFreeBudsSE2Settings> get settings => _settingsCtrl.stream; | ||
|
||
@override | ||
Future<void> setSettings(HuaweiFreeBudsSE2Settings newSettings) async { | ||
_settingsCtrl.add( | ||
_settingsCtrl.value.copyWith( | ||
doubleTapLeft: newSettings.doubleTapLeft, | ||
doubleTapRight: newSettings.doubleTapRight, | ||
), | ||
); | ||
} | ||
} | ||
|
||
/// Class to use as placeholder for Disabled() widget | ||
// this is not done with mixins because we may want to fill it with | ||
// last-remembered values in future, and we will pretty much override | ||
// all of this | ||
// | ||
// ...or not. I just don't know yet 🤷 | ||
final class HuaweiFreeBudsSE2SimPlaceholder extends HuaweiFreeBudsSE2 | ||
with BluetoothHeadphonesSimPlaceholder, LRCBatteryAlwaysFullSimPlaceholder { | ||
const HuaweiFreeBudsSE2SimPlaceholder(); | ||
|
||
@override | ||
ValueStream<HuaweiFreeBudsSE2Settings> get settings => BehaviorSubject(); | ||
|
||
@override | ||
Future<void> setSettings(HuaweiFreeBudsSE2Settings newSettings) async {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.