@mentra/bluetooth-sdk 0.1.21-beta.3 → 3.1.0-beta.100
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 +67 -25
- package/android/build.gradle +7 -4
- package/android/lc3Lib/build.gradle +18 -4
- package/android/silero/build.gradle +9 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +16 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +35 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +216 -23
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +2 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +7 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +127 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +93 -12
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +5 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/ReleaseChangelog.kt +61 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +15 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +24 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/net/LocalIpv4.kt +57 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/otaserver/MentraOtaServerModule.kt +120 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +2 -49
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +61 -30
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +1393 -284
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +8 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +12 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +74 -79
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +3 -3
- package/android/src/test/java/com/mentra/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +148 -7
- package/build/BluetoothSdk.types.d.ts +84 -2
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +2 -0
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js +2 -0
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +4 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +1 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -1
- package/build/_private/MentraLocalNetworkModule.js.map +1 -1
- package/build/_private/sdkOtaManifest.d.ts +3 -12
- package/build/_private/sdkOtaManifest.d.ts.map +1 -1
- package/build/_private/sdkOtaManifest.js +5 -17
- package/build/_private/sdkOtaManifest.js.map +1 -1
- package/build/changelogs.d.ts +7 -0
- package/build/changelogs.d.ts.map +1 -0
- package/build/changelogs.js +44 -0
- package/build/changelogs.js.map +1 -0
- package/build/generated/changelogCatalog.d.ts +6 -0
- package/build/generated/changelogCatalog.d.ts.map +1 -0
- package/build/generated/changelogCatalog.js +8 -0
- package/build/generated/changelogCatalog.js.map +1 -0
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +24 -2
- package/build/index.js.map +1 -1
- package/build/ota-server/MentraOtaServer.types.d.ts +14 -0
- package/build/ota-server/MentraOtaServer.types.d.ts.map +1 -0
- package/build/ota-server/MentraOtaServer.types.js +2 -0
- package/build/ota-server/MentraOtaServer.types.js.map +1 -0
- package/build/ota-server/MentraOtaServerModule.d.ts +21 -0
- package/build/ota-server/MentraOtaServerModule.d.ts.map +1 -0
- package/build/ota-server/MentraOtaServerModule.js +3 -0
- package/build/ota-server/MentraOtaServerModule.js.map +1 -0
- package/build/ota-server/MentraOtaServerModule.web.d.ts +10 -0
- package/build/ota-server/MentraOtaServerModule.web.d.ts.map +1 -0
- package/build/ota-server/MentraOtaServerModule.web.js +12 -0
- package/build/ota-server/MentraOtaServerModule.web.js.map +1 -0
- package/build/ota-server/index.d.ts +3 -0
- package/build/ota-server/index.d.ts.map +1 -0
- package/build/ota-server/index.js +3 -0
- package/build/ota-server/index.js.map +1 -0
- package/build/ota-transport/index.d.ts +67 -0
- package/build/ota-transport/index.d.ts.map +1 -0
- package/build/ota-transport/index.js +58 -0
- package/build/ota-transport/index.js.map +1 -0
- package/expo-module.config.json +3 -2
- package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
- package/ios/BluetoothSdkModule.swift +16 -2
- package/ios/LocalIPv4.swift +150 -0
- package/ios/LocalOtaServer.swift +309 -0
- package/ios/MentraBluetoothSDK.podspec +5 -1
- package/ios/MentraOtaServerModule.swift +147 -0
- package/ios/MentraPhotoReceiverModule.swift +1 -81
- package/ios/Source/BluetoothSdkDefaults.swift +4 -1
- package/ios/Source/Bridge.swift +43 -4
- package/ios/Source/DeviceManager.swift +154 -19
- package/ios/Source/DeviceStore.swift +2 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +6 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
- package/ios/Source/MentraBluetoothSDK.swift +91 -6
- package/ios/Source/OtaManifest.swift +5 -7
- package/ios/Source/ReleaseChangelog.swift +65 -0
- package/ios/Source/audio/AudioModels.swift +17 -0
- package/ios/Source/camera/CameraModels.swift +1 -0
- package/ios/Source/events/BluetoothEvents.swift +27 -0
- package/ios/Source/sgcs/MentraLive.swift +512 -78
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
- package/ios/Source/sgcs/SGCManager.swift +11 -0
- package/ios/Source/status/DeviceStatus.swift +23 -0
- package/ios/Source/types/DeviceModels.swift +23 -2
- package/ios/Source/utils/BleJsonCompact.swift +73 -71
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
- package/ios/Tests/ReleaseChangelogTests.swift +13 -0
- package/package.json +10 -1
- package/plugin/build/withIos.d.ts +3 -1
- package/plugin/build/withIos.js +22 -10
- package/scripts/public-ota-api.test.mjs +37 -0
- package/scripts/verify-release-package.mjs +34 -0
- package/scripts/write-release-metadata.mjs +154 -0
- package/scripts/write-release-metadata.test.mjs +29 -0
- package/src/BluetoothSdk.types.ts +97 -3
- package/src/_internal.ts +2 -0
- package/src/_private/BluetoothSdkModule.ts +5 -5
- package/src/_private/MentraLocalNetworkModule.ts +1 -1
- package/src/_private/sdkOtaManifest.ts +5 -17
- package/src/changelogs.ts +44 -0
- package/src/generated/changelogCatalog.ts +9 -0
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +43 -2
- package/src/ota-server/MentraOtaServer.types.ts +10 -0
- package/src/ota-server/MentraOtaServerModule.ts +23 -0
- package/src/ota-server/MentraOtaServerModule.web.ts +17 -0
- package/src/ota-server/index.ts +2 -0
- package/src/ota-transport/index.ts +116 -0
|
@@ -309,6 +309,9 @@ public final class MentraBluetoothSDK {
|
|
|
309
309
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "device_name", device.name)
|
|
310
310
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "device_address", device.identifier ?? "")
|
|
311
311
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "project_name", device.projectName ?? "")
|
|
312
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
|
|
313
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
|
|
314
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
|
|
312
315
|
finishDefaultDeviceApply(generation: generation)
|
|
313
316
|
}
|
|
314
317
|
|
|
@@ -320,6 +323,9 @@ public final class MentraBluetoothSDK {
|
|
|
320
323
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "device_name", "")
|
|
321
324
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "device_address", "")
|
|
322
325
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "project_name", "")
|
|
326
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
|
|
327
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
|
|
328
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
|
|
323
329
|
finishDefaultDeviceApply(generation: generation)
|
|
324
330
|
}
|
|
325
331
|
|
|
@@ -394,6 +400,14 @@ public final class MentraBluetoothSDK {
|
|
|
394
400
|
}
|
|
395
401
|
if options.saveAsDefault && !isController {
|
|
396
402
|
setDefaultDevice(device)
|
|
403
|
+
} else if !isController {
|
|
404
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", device.name)
|
|
405
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", device.identifier ?? "")
|
|
406
|
+
DeviceStore.shared.apply(
|
|
407
|
+
ObservableStore.bluetoothCategory,
|
|
408
|
+
"pending_device_secure_pairing_capable",
|
|
409
|
+
device.securePairingCapable ?? ""
|
|
410
|
+
)
|
|
397
411
|
}
|
|
398
412
|
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_wearable", device.model.deviceType)
|
|
399
413
|
DeviceManager.shared.connectByName(device.name)
|
|
@@ -418,6 +432,9 @@ public final class MentraBluetoothSDK {
|
|
|
418
432
|
|
|
419
433
|
public func cancelConnectionAttempt() {
|
|
420
434
|
clearBluetoothRestoreIntent()
|
|
435
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
|
|
436
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
|
|
437
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
|
|
421
438
|
DeviceManager.shared.disconnect()
|
|
422
439
|
}
|
|
423
440
|
|
|
@@ -428,6 +445,9 @@ public final class MentraBluetoothSDK {
|
|
|
428
445
|
|
|
429
446
|
public func disconnect() {
|
|
430
447
|
clearBluetoothRestoreIntent()
|
|
448
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
|
|
449
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
|
|
450
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
|
|
431
451
|
DeviceManager.shared.disconnect()
|
|
432
452
|
}
|
|
433
453
|
|
|
@@ -890,6 +910,13 @@ public final class MentraBluetoothSDK {
|
|
|
890
910
|
}
|
|
891
911
|
}
|
|
892
912
|
|
|
913
|
+
/// Fire-and-forget Mentra Live Wi-Fi ADB toggle. Glasses do not ack this
|
|
914
|
+
/// command, so there is no pending-response wait (unlike hotspot / Wi-Fi forget).
|
|
915
|
+
public func setWifiAdbState(enabled: Bool) async throws {
|
|
916
|
+
try requireGlassesConnected(operation: "toggle Wi-Fi ADB")
|
|
917
|
+
DeviceManager.shared.setWifiAdbState(enabled)
|
|
918
|
+
}
|
|
919
|
+
|
|
893
920
|
func setSystemTime(timestampMs: Int64) {
|
|
894
921
|
DeviceManager.shared.setSystemTime(timestampMs)
|
|
895
922
|
}
|
|
@@ -1141,6 +1168,36 @@ public final class MentraBluetoothSDK {
|
|
|
1141
1168
|
}
|
|
1142
1169
|
}
|
|
1143
1170
|
|
|
1171
|
+
public func queryVideoRecordingStatus(requestId: String) async throws -> VideoRecordingStatusEvent {
|
|
1172
|
+
guard !requestId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
1173
|
+
throw BluetoothSdkError(
|
|
1174
|
+
code: "missing_request_id",
|
|
1175
|
+
message: "requestId is required to query video recording status."
|
|
1176
|
+
)
|
|
1177
|
+
}
|
|
1178
|
+
try requireGlassesConnected(operation: "query video recording status")
|
|
1179
|
+
let pending = PendingResponse<VideoRecordingStatusEvent>(operation: "query video recording status \(requestId)")
|
|
1180
|
+
guard pendingVideoRecordingRequests[requestId] == nil else {
|
|
1181
|
+
throw BluetoothSdkError(
|
|
1182
|
+
code: "request_in_flight",
|
|
1183
|
+
message: "A video recording command is already waiting for requestId \(requestId)."
|
|
1184
|
+
)
|
|
1185
|
+
}
|
|
1186
|
+
pendingVideoRecordingRequests[requestId] = PendingVideoRecordingRequest(
|
|
1187
|
+
expectedStatus: "recording_status",
|
|
1188
|
+
pending: pending
|
|
1189
|
+
)
|
|
1190
|
+
DeviceManager.shared.queryVideoRecordingStatus(requestId)
|
|
1191
|
+
do {
|
|
1192
|
+
let event = try await pending.wait()
|
|
1193
|
+
pendingVideoRecordingRequests.removeValue(forKey: requestId)
|
|
1194
|
+
return event
|
|
1195
|
+
} catch {
|
|
1196
|
+
pendingVideoRecordingRequests.removeValue(forKey: requestId)
|
|
1197
|
+
throw error
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1144
1201
|
public func requestVersionInfo() async throws -> VersionInfoResult {
|
|
1145
1202
|
guard pendingVersionInfo == nil else {
|
|
1146
1203
|
throw BluetoothSdkError(
|
|
@@ -1165,11 +1222,13 @@ public final class MentraBluetoothSDK {
|
|
|
1165
1222
|
}
|
|
1166
1223
|
}
|
|
1167
1224
|
|
|
1168
|
-
|
|
1225
|
+
/// Select the HTTP(S) manifest used by subsequent OTA checks and installs.
|
|
1226
|
+
public func setOtaVersionUrl(_ otaVersionUrl: String) throws {
|
|
1169
1227
|
configuredOtaVersionUrl = try OtaManifestChecker.normalizeHttpUrl(otaVersionUrl)
|
|
1170
1228
|
}
|
|
1171
1229
|
|
|
1172
|
-
|
|
1230
|
+
/// Return the configured OTA manifest, or this SDK release's default manifest.
|
|
1231
|
+
public func getOtaVersionUrl() throws -> String {
|
|
1173
1232
|
try configuredOtaVersionUrl ?? OtaManifestDefaults.defaultOtaVersionUrl()
|
|
1174
1233
|
}
|
|
1175
1234
|
|
|
@@ -1200,6 +1259,14 @@ public final class MentraBluetoothSDK {
|
|
|
1200
1259
|
)
|
|
1201
1260
|
}
|
|
1202
1261
|
|
|
1262
|
+
/// Return bundled release changelogs crossed between two coordinated product versions, newest first.
|
|
1263
|
+
public func getReleaseChangelogs(
|
|
1264
|
+
fromVersion: String? = nil,
|
|
1265
|
+
toVersion: String? = nil
|
|
1266
|
+
) throws -> [ReleaseChangelog] {
|
|
1267
|
+
try ReleaseChangelogCatalog.select(fromVersion: fromVersion, toVersion: toVersion)
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1203
1270
|
/// Ask connected Mentra Live glasses to report the current OTA install/session status.
|
|
1204
1271
|
private func queryOtaStatus() async throws -> OtaQueryResult {
|
|
1205
1272
|
try await performOtaQuery(operation: "OTA status query") {
|
|
@@ -2057,6 +2124,8 @@ private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
|
2057
2124
|
if !event.lc3.isEmpty {
|
|
2058
2125
|
delegate?.mentraBluetoothSDK(self, didReceiveMicLc3: event)
|
|
2059
2126
|
}
|
|
2127
|
+
case "mic_health":
|
|
2128
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .micHealth(MicHealthEvent(values: data)))
|
|
2060
2129
|
case "local_transcription":
|
|
2061
2130
|
let event = LocalTranscriptionEvent(
|
|
2062
2131
|
text: data["text"] as? String ?? "",
|
|
@@ -2105,6 +2174,10 @@ private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
|
2105
2174
|
let event = GalleryStatusEvent(values: data)
|
|
2106
2175
|
pendingGalleryStatus?.resolve(event)
|
|
2107
2176
|
delegate?.mentraBluetoothSDK(self, didReceive: .raw(name: "gallery_status", values: event.values))
|
|
2177
|
+
case "pairing_info":
|
|
2178
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .raw(name: "pairing_info", values: data))
|
|
2179
|
+
case "entering_pairing_mode", "owner_replaced":
|
|
2180
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .raw(name: eventName, values: data))
|
|
2108
2181
|
case "photo_response":
|
|
2109
2182
|
let event = PhotoResponseEvent(values: data)
|
|
2110
2183
|
handlePhotoResponseForRequests(event)
|
|
@@ -2147,7 +2220,13 @@ private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
|
2147
2220
|
var resultValues = data
|
|
2148
2221
|
resultValues["type"] = "ota_status"
|
|
2149
2222
|
pendingOtaQuery?.resolve(OtaQueryResult(values: resultValues))
|
|
2150
|
-
|
|
2223
|
+
let event = OtaStatusEvent(values: resultValues)
|
|
2224
|
+
if let errorCode = otaStartRejectionErrorCode(event) {
|
|
2225
|
+
pendingOtaStart?.reject(
|
|
2226
|
+
BluetoothSdkError(code: errorCode, message: "Glasses rejected OTA start: \(errorCode)")
|
|
2227
|
+
)
|
|
2228
|
+
}
|
|
2229
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .otaStatus(event))
|
|
2151
2230
|
case "settings_ack":
|
|
2152
2231
|
let event = SettingsAckEvent(values: data)
|
|
2153
2232
|
handleSettingsAckForRequests(event)
|
|
@@ -2172,6 +2251,12 @@ private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
|
2172
2251
|
}
|
|
2173
2252
|
}
|
|
2174
2253
|
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2254
|
+
/// OTA status messages are not request-correlated, so only known pre-ack failures settle a start.
|
|
2255
|
+
private func otaStartRejectionErrorCode(_ event: OtaStatusEvent) -> String? {
|
|
2256
|
+
guard event.status.lowercased() == "failed",
|
|
2257
|
+
event.errorMessage?.lowercased() == "battery_low"
|
|
2258
|
+
else {
|
|
2259
|
+
return nil
|
|
2260
|
+
}
|
|
2261
|
+
return "battery_low"
|
|
2262
|
+
}
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
enum OtaManifestDefaults {
|
|
4
|
-
private static let sdkOtaReleaseBaseUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/bluetooth-sdk-ota"
|
|
5
4
|
// ASG builds before 39 ignore ota_start.ota_version_url, so SDK checks must
|
|
6
5
|
// use the same legacy production manifest those glasses will install from.
|
|
7
6
|
static let legacyProdOtaVersionUrl = "https://ota.mentraglass.com/prod_live_version.json"
|
|
8
7
|
|
|
9
8
|
static func defaultOtaVersionUrl() throws -> String {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
else {
|
|
9
|
+
let manifestUrl = GeneratedReleaseMetadata.otaManifestUrl.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
10
|
+
guard !manifestUrl.isEmpty else {
|
|
13
11
|
throw BluetoothSdkError(
|
|
14
|
-
code: "
|
|
15
|
-
message: "
|
|
12
|
+
code: "ota_manifest_unconfigured",
|
|
13
|
+
message: "This source-built Bluetooth SDK has no embedded OTA manifest. Configure one with the debug API."
|
|
16
14
|
)
|
|
17
15
|
}
|
|
18
|
-
return
|
|
16
|
+
return manifestUrl
|
|
19
17
|
}
|
|
20
18
|
}
|
|
21
19
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// One coordinated Mentra release note authored in /changelogs/<version>.md.
|
|
4
|
+
public struct ReleaseChangelog: Equatable, Sendable {
|
|
5
|
+
public let version: String
|
|
6
|
+
public let markdown: String
|
|
7
|
+
|
|
8
|
+
public init(version: String, markdown: String) {
|
|
9
|
+
self.version = version
|
|
10
|
+
self.markdown = markdown
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
enum ReleaseChangelogCatalog {
|
|
15
|
+
static func select(fromVersion: String?, toVersion: String?) throws -> [ReleaseChangelog] {
|
|
16
|
+
let fallbackTarget = GeneratedReleaseMetadata.familyBaseVersion.isEmpty
|
|
17
|
+
? (generatedReleaseChangelogs.first?.version ?? "")
|
|
18
|
+
: GeneratedReleaseMetadata.familyBaseVersion
|
|
19
|
+
if fallbackTarget.isEmpty, toVersion == nil { return [] }
|
|
20
|
+
let target = try baseVersion(toVersion ?? fallbackTarget, label: "toVersion")
|
|
21
|
+
guard generatedReleaseChangelogs.contains(where: { $0.version == target }) else {
|
|
22
|
+
throw BluetoothSdkError(code: "missing_changelog", message: "No changelog is bundled for target version \(target).")
|
|
23
|
+
}
|
|
24
|
+
guard let fromVersion else {
|
|
25
|
+
return generatedReleaseChangelogs.filter { $0.version == target }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let source = try baseVersion(fromVersion, label: "fromVersion")
|
|
29
|
+
let direction = compareVersions(target, source)
|
|
30
|
+
return generatedReleaseChangelogs.filter { entry in
|
|
31
|
+
if direction == 0 { return entry.version == target }
|
|
32
|
+
if direction > 0 {
|
|
33
|
+
return compareVersions(entry.version, source) > 0 && compareVersions(entry.version, target) <= 0
|
|
34
|
+
}
|
|
35
|
+
return compareVersions(entry.version, source) < 0 && compareVersions(entry.version, target) >= 0
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private static func baseVersion(_ version: String, label: String) throws -> String {
|
|
40
|
+
let value = version.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
41
|
+
let suffixStart = value.firstIndex(where: { $0 == "-" || $0 == "+" }) ?? value.endIndex
|
|
42
|
+
let components = value[..<suffixStart].split(separator: ".", omittingEmptySubsequences: false)
|
|
43
|
+
let isValid = components.count == 3 && components.allSatisfy { component in
|
|
44
|
+
!component.isEmpty
|
|
45
|
+
&& (component == "0" || !component.hasPrefix("0"))
|
|
46
|
+
&& component.utf8.allSatisfy { byte in byte >= 48 && byte <= 57 }
|
|
47
|
+
}
|
|
48
|
+
guard isValid else {
|
|
49
|
+
throw BluetoothSdkError(
|
|
50
|
+
code: "invalid_changelog_version",
|
|
51
|
+
message: "\(label) must be a semantic version such as 3.1.0, 3.1.0-dev.4, or 3.1.0-beta.2."
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
return components.joined(separator: ".")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private static func compareVersions(_ left: String, _ right: String) -> Int {
|
|
58
|
+
let a = left.split(separator: ".").map { Int($0) ?? 0 }
|
|
59
|
+
let b = right.split(separator: ".").map { Int($0) ?? 0 }
|
|
60
|
+
for index in 0 ..< 3 where a[index] != b[index] {
|
|
61
|
+
return a[index] - b[index]
|
|
62
|
+
}
|
|
63
|
+
return 0
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -7,6 +7,23 @@ public enum MicPreference: String {
|
|
|
7
7
|
case bluetooth
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
struct MicHealth {
|
|
11
|
+
let sequenceGapEvents: Int64
|
|
12
|
+
let decodeFailures: Int64
|
|
13
|
+
let lastLc3ReceivedAt: Int64?
|
|
14
|
+
let lastPcmProducedAt: Int64?
|
|
15
|
+
|
|
16
|
+
var dictionary: [String: Any] {
|
|
17
|
+
var values: [String: Any] = [
|
|
18
|
+
"sequenceGapEvents": sequenceGapEvents,
|
|
19
|
+
"decodeFailures": decodeFailures,
|
|
20
|
+
]
|
|
21
|
+
if let lastLc3ReceivedAt { values["lastLc3ReceivedAt"] = lastLc3ReceivedAt }
|
|
22
|
+
if let lastPcmProducedAt { values["lastPcmProducedAt"] = lastPcmProducedAt }
|
|
23
|
+
return values
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
public struct LocalTranscriptionEvent: CustomStringConvertible {
|
|
11
28
|
public let text: String
|
|
12
29
|
public let isFinal: Bool
|
|
@@ -73,6 +73,30 @@ public struct SpeakingStatusEvent: CustomStringConvertible {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
public struct MicHealthEvent: CustomStringConvertible {
|
|
77
|
+
public let reason: String
|
|
78
|
+
public let sequenceGapEvents: Int
|
|
79
|
+
public let decodeFailures: Int
|
|
80
|
+
public let lastLc3ReceivedAt: Int?
|
|
81
|
+
public let lastPcmProducedAt: Int?
|
|
82
|
+
public let timestamp: Int
|
|
83
|
+
public let values: [String: Any]
|
|
84
|
+
|
|
85
|
+
public init(values: [String: Any]) {
|
|
86
|
+
reason = stringValue(values, "reason") ?? ""
|
|
87
|
+
sequenceGapEvents = intValue(values["sequenceGapEvents"]) ?? 0
|
|
88
|
+
decodeFailures = intValue(values["decodeFailures"]) ?? 0
|
|
89
|
+
lastLc3ReceivedAt = intValue(values["lastLc3ReceivedAt"])
|
|
90
|
+
lastPcmProducedAt = intValue(values["lastPcmProducedAt"])
|
|
91
|
+
timestamp = intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
|
|
92
|
+
self.values = values
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public var description: String {
|
|
96
|
+
"MicHealthEvent(reason: \(reason), sequenceGapEvents: \(sequenceGapEvents), decodeFailures: \(decodeFailures))"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
76
100
|
public struct OtaStartAckEvent: CustomStringConvertible {
|
|
77
101
|
public let timestamp: Int?
|
|
78
102
|
public let values: [String: Any]
|
|
@@ -216,6 +240,7 @@ public enum BluetoothEvent: CustomStringConvertible {
|
|
|
216
240
|
case touch(TouchEvent)
|
|
217
241
|
case voiceActivityDetectionStatus(VoiceActivityDetectionStatusEvent)
|
|
218
242
|
case speakingStatus(SpeakingStatusEvent)
|
|
243
|
+
case micHealth(MicHealthEvent)
|
|
219
244
|
case wifiStatus(WifiStatusEvent)
|
|
220
245
|
case hotspotStatus(HotspotStatusEvent)
|
|
221
246
|
case hotspotError(HotspotErrorEvent)
|
|
@@ -244,6 +269,8 @@ public enum BluetoothEvent: CustomStringConvertible {
|
|
|
244
269
|
event.description
|
|
245
270
|
case let .speakingStatus(event):
|
|
246
271
|
event.description
|
|
272
|
+
case let .micHealth(event):
|
|
273
|
+
event.description
|
|
247
274
|
case let .wifiStatus(event):
|
|
248
275
|
event.description
|
|
249
276
|
case let .hotspotStatus(event):
|