Skip to content

Commit 9987656

Browse files
committed
Release 2.2.9+20 to TestFlight
1 parent 3b26f01 commit 9987656

41 files changed

Lines changed: 6458 additions & 3606 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ios/Runner/BluetoothManager.swift

Lines changed: 19 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,6 @@ class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate
3737
/// Whether noise reduction is applied to incoming glasses audio.
3838
var noiseReductionEnabled = false
3939

40-
// MARK: - Write Coalescing
41-
/// Buffered outgoing BLE writes, keyed by destination ("L", "R", or "both").
42-
private var writeCoalesceBuffer: [String: Data] = [:]
43-
/// Timer that fires to flush the coalesced write buffer.
44-
private var writeCoalesceTimer: Timer?
45-
/// Coalescing interval in seconds.
46-
private static let writeCoalesceInterval: TimeInterval = 0.2 // 200ms
47-
/// Maximum coalesced payload size before forced flush (aligned to typical BLE MTU).
48-
private static let writeCoalesceMaxBytes = 182 // conservative MTU minus headers
49-
5040
var leftPeripheral: CBPeripheral?
5141
var leftUUIDStr: String?
5242
var rightPeripheral: CBPeripheral?
@@ -123,7 +113,6 @@ class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate
123113
func disconnectFromGlasses(result: @escaping FlutterResult) {
124114
userInitiatedDisconnect = true
125115
clearStoredConnection()
126-
flushAllCoalescedWrites()
127116

128117
for (_, devices) in connectedDevices {
129118
if let leftPeripheral = devices.0 {
@@ -293,73 +282,34 @@ class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate
293282
}
294283

295284
func writeData(writeData: Data, cbPeripheral: CBPeripheral? = nil, lr: String? = nil) {
296-
let key: String
297285
if lr == "L" {
298-
key = "L"
299-
} else if lr == "R" {
300-
key = "R"
301-
} else {
302-
key = "both"
303-
}
304-
305-
// Append to coalescing buffer
306-
if writeCoalesceBuffer[key] == nil {
307-
writeCoalesceBuffer[key] = Data()
308-
}
309-
writeCoalesceBuffer[key]!.append(writeData)
310-
311-
// Flush immediately if buffer exceeds MTU size
312-
if writeCoalesceBuffer[key]!.count >= Self.writeCoalesceMaxBytes {
313-
flushCoalescedWrite(for: key)
314-
} else {
315-
// Start coalescing timer if not already running
316-
if writeCoalesceTimer == nil {
317-
writeCoalesceTimer = Timer.scheduledTimer(
318-
withTimeInterval: Self.writeCoalesceInterval,
319-
repeats: false
320-
) { [weak self] _ in
321-
self?.flushAllCoalescedWrites()
322-
}
323-
}
324-
}
325-
}
326-
327-
/// Flush all pending coalesced writes.
328-
private func flushAllCoalescedWrites() {
329-
writeCoalesceTimer?.invalidate()
330-
writeCoalesceTimer = nil
331-
let keys = Array(writeCoalesceBuffer.keys)
332-
for key in keys {
333-
flushCoalescedWrite(for: key)
334-
}
335-
}
336-
337-
/// Flush a single coalesced write buffer for the given destination key.
338-
private func flushCoalescedWrite(for key: String) {
339-
guard let data = writeCoalesceBuffer[key], !data.isEmpty else { return }
340-
writeCoalesceBuffer.removeValue(forKey: key)
341-
342-
switch key {
343-
case "L":
344286
if let leftWChar = leftWChar {
345-
leftPeripheral?.writeValue(data, for: leftWChar, type: .withoutResponse)
346-
}
347-
case "R":
348-
if let rightWChar = rightWChar {
349-
rightPeripheral?.writeValue(data, for: rightWChar, type: .withoutResponse)
350-
}
351-
default:
352-
// Write to both peripherals
353-
if let leftWChar = leftWChar {
354-
leftPeripheral?.writeValue(data, for: leftWChar, type: .withoutResponse)
287+
leftPeripheral?.writeValue(writeData, for: leftWChar, type: .withoutResponse)
355288
} else {
356289
print("writeData leftWChar is nil, cannot write data to left peripheral.")
357290
}
291+
return
292+
}
293+
294+
if lr == "R" {
358295
if let rightWChar = rightWChar {
359-
rightPeripheral?.writeValue(data, for: rightWChar, type: .withoutResponse)
296+
rightPeripheral?.writeValue(writeData, for: rightWChar, type: .withoutResponse)
360297
} else {
361298
print("writeData rightWChar is nil, cannot write data to right peripheral.")
362299
}
300+
return
301+
}
302+
303+
if let leftWChar = leftWChar {
304+
leftPeripheral?.writeValue(writeData, for: leftWChar, type: .withoutResponse)
305+
} else {
306+
print("writeData leftWChar is nil, cannot write data to left peripheral.")
307+
}
308+
309+
if let rightWChar = rightWChar {
310+
rightPeripheral?.writeValue(writeData, for: rightWChar, type: .withoutResponse)
311+
} else {
312+
print("writeData rightWChar is nil, cannot write data to right peripheral.")
363313
}
364314
}
365315

ios/Runner/OpenAIRealtimeTranscriber.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class OpenAIRealtimeTranscriber: NSObject, URLSessionWebSocketDelegate {
2727
var onError: ((String) -> Void)?
2828
var onAudioOutput: ((Data) -> Void)?
2929
var onAudioOutputDone: (() -> Void)?
30+
var onUsage: (([String: Any]) -> Void)?
3031

3132
var isActive: Bool { webSocketTask != nil }
3233

@@ -494,6 +495,7 @@ class OpenAIRealtimeTranscriber: NSObject, URLSessionWebSocketDelegate {
494495
self.onTranscriptWithId?(transcript, true, itemId)
495496
}
496497
}
498+
emitUsageIfPresent(json, operationType: "transcription")
497499

498500
case "response.text.delta":
499501
if let delta = json["delta"] as? String, !delta.isEmpty {
@@ -506,6 +508,7 @@ class OpenAIRealtimeTranscriber: NSObject, URLSessionWebSocketDelegate {
506508
DispatchQueue.main.async {
507509
self.onResponse?("", true)
508510
}
511+
emitUsageIfPresent(json, operationType: "response")
509512

510513
case "response.audio.delta":
511514
if let delta = json["delta"] as? String,
@@ -619,6 +622,16 @@ class OpenAIRealtimeTranscriber: NSObject, URLSessionWebSocketDelegate {
619622
}
620623
return "Unknown API error"
621624
}
625+
626+
private func emitUsageIfPresent(_ json: [String: Any], operationType: String) {
627+
guard let usage = json["usage"] as? [String: Any] else { return }
628+
var payload = usage
629+
payload["operationType"] = operationType
630+
payload["model"] = model
631+
DispatchQueue.main.async { [weak self] in
632+
self?.onUsage?(payload)
633+
}
634+
}
622635
}
623636

624637
extension OpenAIRealtimeTranscriber {

ios/Runner/SpeechStreamRecognizer.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,9 @@ class SpeechStreamRecognizer {
482482
openaiTranscriber.onResponse = { [weak self] text, isFinal in
483483
self?.emitAIResponse(text, isFinal: isFinal)
484484
}
485+
openaiTranscriber.onUsage = { [weak self] usage in
486+
self?.emitUsage(usage)
487+
}
485488
openaiTranscriber.onAudioOutput = { [weak self] audioData in
486489
self?.onRealtimeAudioOutput?(audioData)
487490
}
@@ -1182,6 +1185,15 @@ class SpeechStreamRecognizer {
11821185
])
11831186
}
11841187

1188+
private func emitUsage(_ usage: [String: Any]) {
1189+
emitSpeechEvent([
1190+
"usage": usage,
1191+
"usageOperationType": usage["operationType"] as? String ?? "",
1192+
"usageModel": usage["model"] as? String ?? "",
1193+
"isUsageEvent": true,
1194+
])
1195+
}
1196+
11851197
private func failToStart(
11861198
_ error: Error,
11871199
messageOverride: String? = nil

lib/ble_manager.dart

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22
import 'dart:typed_data';
3+
import 'package:flutter/foundation.dart';
34
import 'package:flutter/services.dart';
45
import 'services/ble.dart';
56
import 'services/evenai.dart';
@@ -65,6 +66,23 @@ class BleManager {
6566

6667
void _init() {}
6768

69+
@visibleForTesting
70+
void debugSetConnectionState({
71+
required bool leftConnected,
72+
required bool rightConnected,
73+
}) {
74+
_isLeftConnected = leftConnected;
75+
_isRightConnected = rightConnected;
76+
isConnected = leftConnected || rightConnected;
77+
connectionStatus = isConnected ? 'Connected (debug)' : 'Not connected';
78+
_updateConnectionState(
79+
isConnected
80+
? BleConnectionState.connected
81+
: BleConnectionState.disconnected,
82+
);
83+
onStatusChanged?.call();
84+
}
85+
6886
/// Get current health metrics
6987
BleHealthMetrics getHealthMetrics() => _healthMetrics;
7088

@@ -284,8 +302,9 @@ class BleManager {
284302
}
285303

286304
void _onGlassesDisconnected([dynamic arguments]) {
287-
final disconnectedSide =
288-
arguments is Map ? arguments['disconnectedSide'] as String? : null;
305+
final disconnectedSide = arguments is Map
306+
? arguments['disconnectedSide'] as String?
307+
: null;
289308

290309
if (disconnectedSide == 'L') {
291310
_isLeftConnected = false;
@@ -424,7 +443,9 @@ class BleManager {
424443
int retry = 3,
425444
}) async {
426445
BleReceive ret;
427-
for (var i = 0; i < retry; i++) {
446+
final attempts = BleTransportPolicy.attemptsForRetryCount(retry);
447+
final target = lr ?? Proto.lR();
448+
for (var i = 0; i < attempts; i++) {
428449
if (i > 0) {
429450
// Record retry attempts (not for first attempt)
430451
_instance?._healthMetrics = _instance!._healthMetrics.recordRetry();
@@ -440,7 +461,7 @@ class BleManager {
440461
if (!ret.isTimeout) {
441462
return ret;
442463
}
443-
if (!BleManager.isBothConnected()) {
464+
if (!_isTargetConnected(target)) {
444465
break;
445466
}
446467
}
@@ -599,7 +620,8 @@ class BleManager {
599620
}
600621

601622
static bool isBothConnected() {
602-
return _instance?.isConnected ?? false;
623+
return (_instance?._isLeftConnected ?? false) &&
624+
(_instance?._isRightConnected ?? false);
603625
}
604626

605627
static bool isConnectedL() {
@@ -610,6 +632,17 @@ class BleManager {
610632
return _instance?._isRightConnected ?? false;
611633
}
612634

635+
static bool _isTargetConnected(String lr) {
636+
switch (lr) {
637+
case 'L':
638+
return isConnectedL();
639+
case 'R':
640+
return isConnectedR();
641+
default:
642+
return isBothConnected();
643+
}
644+
}
645+
613646
static Future<bool> requestList(
614647
List<Uint8List> sendList, {
615648
String? lr,
@@ -649,7 +682,9 @@ class BleManager {
649682
var resp = await request(pack, lr: lr, timeoutMs: timeoutMs ?? 350);
650683
if (resp.isTimeout) {
651684
return false;
652-
} else if (resp.data.length >= 2 && resp.data[1].toInt() != 0xc9 && resp.data[1].toInt() != 0xcB) {
685+
} else if (resp.data.length >= 2 &&
686+
resp.data[1].toInt() != 0xc9 &&
687+
resp.data[1].toInt() != 0xcB) {
653688
return false;
654689
}
655690
}

0 commit comments

Comments
 (0)