@mentra/bluetooth-sdk 3.2.1-dev.314 → 3.3.0-beta.942
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -10
- package/android/build.gradle +15 -0
- package/android/lc3Lib/build.gradle +6 -1
- package/android/src/debug/java/com/mentra/bluetoothsdk/sgcs/NimoDiagnostics.kt +368 -0
- package/android/src/debug/java/com/mentra/bluetoothsdk/sgcs/NimoFramebufferProtocol.kt +156 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +63 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalyticsTracker.kt +55 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +4 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkInstallId.kt +23 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +8 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkUpdateCheck.kt +184 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkUsageTracker.kt +388 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +133 -26
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +37 -13
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +3 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +6 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +227 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +24 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/NetworkDiagnostics.kt +25 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulator.kt +13 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +3 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +33 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/G2ConnectionRecovery.kt +47 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTracker.kt +4 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioServiceDiscovery.kt +70 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizer.kt +11 -32
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +72 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/LatinTextSanitizer.kt +39 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +194 -40
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +12 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +314 -395
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCodec.kt +247 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCoordinator.kt +238 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasEncoder.kt +58 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasSession.kt +180 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +11 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SelectedDeviceAddress.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SerializedGattCallback.kt +5 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/G2Text.kt +7 -0
- package/android/src/release/java/com/mentra/bluetoothsdk/sgcs/NimoDiagnostics.kt +20 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkAnalyticsTrackerTest.kt +58 -3
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkUpdateCheckTest.kt +124 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkUsageTrackerTest.kt +263 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/DashboardContentFormatterTest.kt +2 -2
- package/android/src/test/java/com/mentra/bluetoothsdk/DeviceIdentityPairingTest.kt +59 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/DeviceManagerSceneHandoffTest.kt +666 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/G2TextTest.kt +13 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/NetworkDiagnosticsTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ScanCompletionTest.kt +5 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulatorTest.kt +71 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/debug/BleTraceLoggerTest.kt +69 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/services/G2ConnectionRecoveryTest.kt +51 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioServiceDiscoveryTest.kt +124 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1PairingTest.kt +105 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +29 -4
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G2DisplayThreadTest.kt +120 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G2PairingTargetTest.kt +90 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveClassicDisconnectTest.kt +133 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveFileTransferSessionTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveGattCallbackTest.kt +33 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveIncidentDispatchTest.kt +126 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingTargetTest.kt +65 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveWifiCredentialLogTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraNexPairingTest.kt +151 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoBitmapAcceptanceTest.kt +105 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCodecTest.kt +233 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCoordinatorTest.kt +348 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasSessionTest.kt +339 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoMicReconnectTest.kt +93 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoProtocolTest.kt +11 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/SelectedDeviceAddressTest.kt +27 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoDiagnosticsTest.kt +287 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoFramebufferProtocolTest.kt +127 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoLifecycleTest.kt +288 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoReadinessDriverTest.kt +205 -0
- package/build/BluetoothSdk.types.d.ts +16 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +1 -0
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +4 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +5 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +23 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -1
- package/build/_private/MentraLocalNetworkModule.js.map +1 -1
- package/build/_private/nativeLogConsole.d.ts +19 -0
- package/build/_private/nativeLogConsole.d.ts.map +1 -1
- package/build/_private/nativeLogConsole.js +12 -0
- package/build/_private/nativeLogConsole.js.map +1 -1
- package/build/generated/changelogCatalog.d.ts +5 -2
- package/build/generated/changelogCatalog.d.ts.map +1 -1
- package/build/generated/changelogCatalog.js +6 -2
- package/build/generated/changelogCatalog.js.map +1 -1
- package/build/generated/releaseMetadata.js +6 -6
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/ota-transport/index.d.ts +4 -0
- package/build/ota-transport/index.d.ts.map +1 -1
- package/build/ota-transport/index.js +2 -0
- package/build/ota-transport/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +17 -4
- package/ios/MentraBluetoothSDK.podspec +3 -2
- package/ios/Source/BluetoothBackgroundMode.swift +32 -0
- package/ios/Source/BluetoothSdkDefaults.swift +7 -2
- package/ios/Source/Bridge.swift +15 -2
- package/ios/Source/DeviceManager.swift +102 -66
- package/ios/Source/DeviceStore.swift +26 -7
- package/ios/Source/GeneratedChangelogCatalog.swift +3 -2
- package/ios/Source/GeneratedReleaseMetadata.swift +6 -6
- package/ios/Source/MentraBluetoothSDK.swift +313 -54
- package/ios/Source/audio/GlassesMicWatchdog.swift +33 -0
- package/ios/Source/internal/BluetoothAvailability.swift +6 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +87 -13
- package/ios/Source/internal/BluetoothSdkAnalyticsTracker.swift +54 -1
- package/ios/Source/internal/BluetoothSdkInstallId.swift +21 -0
- package/ios/Source/internal/BluetoothSdkUpdateCheck.swift +223 -0
- package/ios/Source/internal/BluetoothSdkUsageTracker.swift +428 -0
- package/ios/Source/requests/DisplayRequests.swift +1 -1
- package/ios/Source/requests/VersionInfoResponseAccumulator.swift +11 -1
- package/ios/Source/sgcs/Ar99.swift +1 -2
- package/ios/Source/sgcs/G1.swift +68 -38
- package/ios/Source/sgcs/G1ConnectionTarget.swift +13 -0
- package/ios/Source/sgcs/G2.swift +261 -247
- package/ios/Source/sgcs/G2ConnectionSession.swift +101 -0
- package/ios/Source/sgcs/Mach1.swift +19 -7
- package/ios/Source/sgcs/MentraLive.swift +272 -197
- package/ios/Source/sgcs/MentraLiveFileTransferSession.swift +137 -0
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +47 -0
- package/ios/Source/sgcs/MentraNex.swift +8 -31
- package/ios/Source/sgcs/NexConnectionTarget.swift +15 -0
- package/ios/Source/sgcs/Nimo.swift +280 -408
- package/ios/Source/sgcs/NimoCanvasCodec.swift +272 -0
- package/ios/Source/sgcs/NimoCanvasCoordinator.swift +233 -0
- package/ios/Source/sgcs/NimoCanvasImage.swift +53 -0
- package/ios/Source/sgcs/NimoCanvasSession.swift +166 -0
- package/ios/Source/sgcs/NimoDiscovery.swift +49 -0
- package/ios/Source/sgcs/SGCManager.swift +25 -1
- package/ios/Source/sgcs/VuzixConnectionTarget.swift +15 -0
- package/ios/Source/status/DeviceStatus.swift +1 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +49 -189
- package/ios/Source/utils/AudioSessionMonitor.swift +44 -8
- package/ios/Source/utils/DemandDrivenAudioWorker.swift +150 -0
- package/ios/Source/utils/G1Text.swift +2 -24
- package/ios/Source/utils/G1TextSanitizer.swift +27 -0
- package/ios/Source/utils/G2Text.swift +11 -0
- package/ios/Source/utils/LatinTextSanitizer.swift +48 -0
- package/ios/Tests/BluetoothAudioRouteTests.swift +26 -0
- package/ios/Tests/BluetoothBackgroundModeTests.swift +32 -0
- package/ios/Tests/BluetoothSdkAnalyticsTrackerTests.swift +54 -3
- package/ios/Tests/BluetoothSdkUpdateCheckTests.swift +153 -0
- package/ios/Tests/BluetoothSdkUsageTrackerTests.swift +296 -0
- package/ios/Tests/DashboardContentFormatterTests.swift +2 -2
- package/ios/Tests/DashboardRenderingTests.swift +31 -0
- package/ios/Tests/DemandDrivenAudioWorkerTests.swift +157 -0
- package/ios/Tests/DeviceIdentityPairingTests.swift +49 -0
- package/ios/Tests/DeviceManagerReconnectTests.swift +362 -0
- package/ios/Tests/G1PairingTests.swift +162 -0
- package/ios/Tests/G1TextSanitizerTests.swift +32 -0
- package/ios/Tests/G2ConnectionSessionTests.swift +172 -0
- package/ios/Tests/G2TextTests.swift +10 -0
- package/ios/Tests/IncidentLogDispatchTests.swift +111 -0
- package/ios/Tests/MentraLiveFileTransferSessionTests.swift +47 -0
- package/ios/Tests/MentraLiveReconnectPolicyTests.swift +58 -0
- package/ios/Tests/MentraLiveSessionReadinessTests.swift +163 -0
- package/ios/Tests/NimoCanvasCodecTests.swift +217 -0
- package/ios/Tests/NimoCanvasSessionTests.swift +181 -0
- package/ios/Tests/NimoCanvasTransportTests.swift +186 -0
- package/ios/Tests/NimoDeviceCommandsTests.swift +16 -0
- package/ios/Tests/NimoDiscoveryTests.swift +79 -0
- package/ios/Tests/SGCConnectionTargetTests.swift +48 -0
- package/ios/Tests/VersionInfoCommandTests.swift +194 -0
- package/ios/Tests/VersionInfoResponseAccumulatorTests.swift +65 -0
- package/ios/Tests/VideoRecordingCommandTests.swift +81 -0
- package/ios/Tests/WifiCredentialLoggingTests.swift +42 -0
- package/package.json +2 -2
- package/plugin/build/withIos.d.ts +2 -0
- package/plugin/build/withIos.js +12 -1
- package/src/BluetoothSdk.types.ts +15 -0
- package/src/_private/BluetoothSdkModule.ts +10 -1
- package/src/_private/MentraLocalNetworkModule.ts +28 -0
- package/src/_private/nativeLogConsole.ts +19 -0
- package/src/generated/changelogCatalog.ts +6 -2
- package/src/generated/releaseMetadata.ts +6 -6
- package/src/index.ts +1 -0
- package/src/ota-transport/index.ts +10 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
struct MentraLiveFileTransferSession {
|
|
4
|
+
let fileName: String
|
|
5
|
+
let fileSize: Int // NOTE: May be "fake" (inflated) due to BES firmware workaround
|
|
6
|
+
let dynamicPayload: Bool
|
|
7
|
+
var actualPackSize: Int = 0 // Actual pack size from first received packet
|
|
8
|
+
var totalPackets: Int
|
|
9
|
+
var expectedNextPacket: Int = 0
|
|
10
|
+
var receivedPackets: [Int: Data] = [:]
|
|
11
|
+
let startTime: Date
|
|
12
|
+
var isComplete: Bool = false
|
|
13
|
+
var isAnnounced: Bool = false
|
|
14
|
+
|
|
15
|
+
/// BES2700 firmware hardcodes FILE_PACK_SIZE=400 when calculating totalPack.
|
|
16
|
+
/// Android glasses "lie" about fileSize to make BES expect correct packet count.
|
|
17
|
+
private static let BES_HARDCODED_PACK_SIZE = 400
|
|
18
|
+
|
|
19
|
+
init(fileName: String, fileSize: Int, announcedPackets: Int? = nil, flags: UInt16 = 0) {
|
|
20
|
+
self.fileName = fileName
|
|
21
|
+
self.fileSize = fileSize
|
|
22
|
+
dynamicPayload = (flags & 0x0002) != 0
|
|
23
|
+
let computedPackets =
|
|
24
|
+
(fileSize + K900ProtocolUtils.FILE_PACK_SIZE - 1) / K900ProtocolUtils.FILE_PACK_SIZE
|
|
25
|
+
if let announced = announcedPackets, announced > 0 {
|
|
26
|
+
totalPackets = announced
|
|
27
|
+
isAnnounced = true
|
|
28
|
+
} else {
|
|
29
|
+
totalPackets = computedPackets
|
|
30
|
+
isAnnounced = false
|
|
31
|
+
}
|
|
32
|
+
startTime = Date()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
mutating func updateAnnouncedPackets(_ announced: Int) {
|
|
36
|
+
guard announced > 0 else { return }
|
|
37
|
+
totalPackets = announced
|
|
38
|
+
isAnnounced = true
|
|
39
|
+
if expectedNextPacket >= totalPackets {
|
|
40
|
+
expectedNextPacket = min(expectedNextPacket, max(totalPackets - 1, 0))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Recalculate total packets based on actual pack size from received packet.
|
|
45
|
+
/// Dynamic headers carry the true byte count. Only legacy small packs use inflated sizes.
|
|
46
|
+
mutating func recalculateTotalPackets(actualPackSize: Int) {
|
|
47
|
+
guard actualPackSize > 0, actualPackSize <= K900ProtocolUtils.FILE_PACK_SIZE else { return }
|
|
48
|
+
|
|
49
|
+
self.actualPackSize = actualPackSize
|
|
50
|
+
|
|
51
|
+
// Detect BES lie: if fileSize is exact multiple of 400, glasses used the lie strategy
|
|
52
|
+
let isBesLie = !dynamicPayload
|
|
53
|
+
&& (fileSize % Self.BES_HARDCODED_PACK_SIZE == 0)
|
|
54
|
+
&& (actualPackSize < Self.BES_HARDCODED_PACK_SIZE)
|
|
55
|
+
|
|
56
|
+
let newTotalPackets: Int
|
|
57
|
+
if isBesLie {
|
|
58
|
+
// BES lie detected: totalPackets = fileSize / 400
|
|
59
|
+
newTotalPackets = fileSize / Self.BES_HARDCODED_PACK_SIZE
|
|
60
|
+
Bridge.log(
|
|
61
|
+
"📦 BES Lie detected! fakeFileSize=\(fileSize), totalPackets=\(newTotalPackets), actualPackSize=\(actualPackSize)"
|
|
62
|
+
)
|
|
63
|
+
} else {
|
|
64
|
+
// Normal case: calculate based on actual pack size
|
|
65
|
+
newTotalPackets = (fileSize + actualPackSize - 1) / actualPackSize
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if newTotalPackets != totalPackets {
|
|
69
|
+
Bridge.log(
|
|
70
|
+
"📦 Recalculating totalPackets: \(totalPackets) -> \(newTotalPackets) (packSize=\(actualPackSize), fileSize=\(fileSize))"
|
|
71
|
+
)
|
|
72
|
+
totalPackets = newTotalPackets
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
mutating func addPacket(_ index: Int, data: Data) -> Bool {
|
|
77
|
+
guard index >= 0 else { return false }
|
|
78
|
+
|
|
79
|
+
// On first packet, recalculate total packets only when we do not already
|
|
80
|
+
// have an authoritative pack size from protocol metadata.
|
|
81
|
+
if receivedPackets.isEmpty && actualPackSize == 0 && !data.isEmpty {
|
|
82
|
+
recalculateTotalPackets(actualPackSize: data.count)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
guard index < totalPackets else { return false }
|
|
86
|
+
if dynamicPayload {
|
|
87
|
+
let expectedSize = min(actualPackSize, fileSize - index * actualPackSize)
|
|
88
|
+
guard expectedSize > 0, data.count == expectedSize else { return false }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
guard receivedPackets[index] == nil else {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
receivedPackets[index] = data
|
|
96
|
+
|
|
97
|
+
while receivedPackets[expectedNextPacket] != nil, expectedNextPacket < totalPackets {
|
|
98
|
+
expectedNextPacket += 1
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
isComplete = (receivedPackets.count == totalPackets)
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func isFinalPacket(_ index: Int) -> Bool {
|
|
106
|
+
index == totalPackets - 1
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func missingPacketIndices() -> [Int] {
|
|
110
|
+
guard totalPackets > receivedPackets.count else { return [] }
|
|
111
|
+
return (0 ..< totalPackets).compactMap { receivedPackets[$0] == nil ? $0 : nil }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Assemble file from received packets.
|
|
115
|
+
/// NOTE: Calculates actual file size from received data, NOT from header fileSize,
|
|
116
|
+
/// because fileSize may be "fake" (inflated) due to BES firmware workaround.
|
|
117
|
+
func assembleFile() -> Data? {
|
|
118
|
+
guard isComplete else { return nil }
|
|
119
|
+
|
|
120
|
+
// Calculate actual file size by summing all received packet sizes
|
|
121
|
+
let actualFileSize = receivedPackets.values.reduce(0) { $0 + $1.count }
|
|
122
|
+
|
|
123
|
+
Bridge.log(
|
|
124
|
+
"📦 Assembling file: headerFileSize=\(fileSize), actualFileSize=\(actualFileSize), totalPackets=\(totalPackets)"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
var fileData = Data(capacity: actualFileSize)
|
|
128
|
+
|
|
129
|
+
for i in 0 ..< totalPackets {
|
|
130
|
+
if let packet = receivedPackets[i] {
|
|
131
|
+
fileData.append(packet)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return fileData
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -61,4 +61,51 @@ enum MentraLiveConnectionAttemptPolicy {
|
|
|
61
61
|
) -> Bool {
|
|
62
62
|
!pairingYieldActive && matchesActiveAttempt
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
/// How long a pending reconnect reports CONNECTING before settling to DISCONNECTED. Matches
|
|
66
|
+
/// the span of the scan backoff it replaces (1+2+4+8+16 s, then 30 s × 5 attempts).
|
|
67
|
+
static let pendingReconnectSettleMs = 181_000
|
|
68
|
+
|
|
69
|
+
/// Whether an unexpected link loss should re-arm a pending connection to the same
|
|
70
|
+
/// peripheral. iOS completes a pending `connect` whenever the glasses advertise again,
|
|
71
|
+
/// even while the app is suspended (phone locked), and wakes the app to handle it. A scan
|
|
72
|
+
/// without service filters returns nothing in the background, and the backoff timers that
|
|
73
|
+
/// drive it stop while the app is suspended, so the scan path alone never reconnects there.
|
|
74
|
+
static func shouldReconnectDirectly(
|
|
75
|
+
isKilled: Bool,
|
|
76
|
+
pairingYieldActive: Bool,
|
|
77
|
+
bluetoothPoweredOn: Bool,
|
|
78
|
+
peripheralName: String?,
|
|
79
|
+
savedDeviceName: String?
|
|
80
|
+
) -> Bool {
|
|
81
|
+
guard !isKilled, !pairingYieldActive, bluetoothPoweredOn,
|
|
82
|
+
let peripheralName, !peripheralName.isEmpty,
|
|
83
|
+
let savedDeviceName
|
|
84
|
+
else {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
return peripheralName == savedDeviceName
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Ownership of the one armed pending reconnect. Each arm returns a new attempt number, so a
|
|
92
|
+
/// timer captured for an earlier attempt can tell it no longer owns the connection state after
|
|
93
|
+
/// a failure, a fallback connect or a re-arm.
|
|
94
|
+
struct MentraLivePendingReconnect {
|
|
95
|
+
private(set) var attempt = 0
|
|
96
|
+
private(set) var isArmed = false
|
|
97
|
+
|
|
98
|
+
mutating func arm() -> Int {
|
|
99
|
+
attempt += 1
|
|
100
|
+
isArmed = true
|
|
101
|
+
return attempt
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
mutating func clear() {
|
|
105
|
+
isArmed = false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func owns(_ candidate: Int) -> Bool {
|
|
109
|
+
isArmed && candidate == attempt
|
|
110
|
+
}
|
|
64
111
|
}
|
|
@@ -994,7 +994,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
|
|
|
994
994
|
// global "last" one. peripheralToConnectName == nil means an auto-reconnect with no
|
|
995
995
|
// specific target, so the cached device is exactly what we want — keep using it.)
|
|
996
996
|
if let targetName = peripheralToConnectName,
|
|
997
|
-
|
|
997
|
+
peripheralToConnect.name != targetName
|
|
998
998
|
{
|
|
999
999
|
Bridge.log(
|
|
1000
1000
|
"NEX-CONN: 🔵 Stored UUID is '\(peripheralToConnect.name ?? "unnamed")' but target is '\(targetName)'. Skipping UUID fast-path; will scan for the target."
|
|
@@ -1088,7 +1088,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
|
|
|
1088
1088
|
])
|
|
1089
1089
|
if let targetName = peripheralToConnectName,
|
|
1090
1090
|
let existingPeripheral = connectedPeripherals.first(where: {
|
|
1091
|
-
$0.name
|
|
1091
|
+
$0.name == targetName
|
|
1092
1092
|
})
|
|
1093
1093
|
{
|
|
1094
1094
|
Bridge.log(
|
|
@@ -2724,35 +2724,12 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
|
|
|
2724
2724
|
// Always emit the discovered device for the UI list
|
|
2725
2725
|
emitDiscoveredDevice(deviceName)
|
|
2726
2726
|
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
shouldConnect = true
|
|
2734
|
-
connectionReason = "Target device name match: \(targetName)"
|
|
2735
|
-
}
|
|
2736
|
-
// During a user-initiated discovery scan, only list devices — never auto-connect a
|
|
2737
|
-
// saved/preferred device, so the user can pick a different one.
|
|
2738
|
-
else if isDiscoveryScan {
|
|
2739
|
-
shouldConnect = false
|
|
2740
|
-
}
|
|
2741
|
-
// Check if this matches our saved device for reconnection
|
|
2742
|
-
else if let savedName = savedDeviceName, deviceName == savedName {
|
|
2743
|
-
shouldConnect = true
|
|
2744
|
-
connectionReason = "Saved device reconnection: \(savedName)"
|
|
2745
|
-
}
|
|
2746
|
-
// Check if this matches preferred device ID
|
|
2747
|
-
else if let preferredId = preferredDeviceId {
|
|
2748
|
-
if let extractedId = extractDeviceId(from: deviceName), extractedId == preferredId {
|
|
2749
|
-
shouldConnect = true
|
|
2750
|
-
connectionReason = "Preferred device ID match: \(preferredId)"
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
|
-
if shouldConnect {
|
|
2755
|
-
connectToFoundDevice(peripheral, reason: connectionReason)
|
|
2727
|
+
if NexConnectionTarget.shouldConnect(
|
|
2728
|
+
name: deviceName, target: peripheralToConnectName, discoveryOnly: isDiscoveryScan,
|
|
2729
|
+
savedName: savedDeviceName, preferredID: preferredDeviceId,
|
|
2730
|
+
discoveredID: extractDeviceId(from: deviceName)
|
|
2731
|
+
) {
|
|
2732
|
+
connectToFoundDevice(peripheral, reason: "Selected target or saved reconnect identity")
|
|
2756
2733
|
}
|
|
2757
2734
|
}
|
|
2758
2735
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum NexConnectionTarget {
|
|
4
|
+
static func shouldConnect(
|
|
5
|
+
name: String, target: String?, discoveryOnly: Bool,
|
|
6
|
+
savedName: String?, preferredID: String?, discoveredID: String?
|
|
7
|
+
) -> Bool {
|
|
8
|
+
guard !discoveryOnly else { return false }
|
|
9
|
+
// An explicit selection is authoritative even when it does not match.
|
|
10
|
+
// Saved-device fallbacks are only for reconnecting without a selection.
|
|
11
|
+
if let target { return !target.isEmpty && name == target }
|
|
12
|
+
if let savedName, !savedName.isEmpty, name == savedName { return true }
|
|
13
|
+
return preferredID != nil && preferredID == discoveredID
|
|
14
|
+
}
|
|
15
|
+
}
|