@mentra/bluetooth-sdk 3.1.0-dev.7 → 3.1.0-dev.70

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.
Files changed (51) hide show
  1. package/README.md +40 -34
  2. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +0 -5
  3. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +13 -8
  4. package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +7 -0
  5. package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
  6. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +15 -0
  7. package/android/src/main/java/com/mentra/bluetoothsdk/ReleaseChangelog.kt +61 -0
  8. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +81 -69
  9. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
  10. package/android/src/test/java/com/mentra/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
  11. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
  12. package/build/BluetoothSdk.types.d.ts +26 -5
  13. package/build/BluetoothSdk.types.d.ts.map +1 -1
  14. package/build/BluetoothSdk.types.js.map +1 -1
  15. package/build/changelogs.d.ts +7 -0
  16. package/build/changelogs.d.ts.map +1 -0
  17. package/build/changelogs.js +44 -0
  18. package/build/changelogs.js.map +1 -0
  19. package/build/generated/changelogCatalog.d.ts +6 -0
  20. package/build/generated/changelogCatalog.d.ts.map +1 -0
  21. package/build/generated/changelogCatalog.js +8 -0
  22. package/build/generated/changelogCatalog.js.map +1 -0
  23. package/build/generated/releaseMetadata.js +5 -5
  24. package/build/generated/releaseMetadata.js.map +1 -1
  25. package/build/index.d.ts +1 -1
  26. package/build/index.d.ts.map +1 -1
  27. package/build/index.js +14 -0
  28. package/build/index.js.map +1 -1
  29. package/build/ota-transport/index.d.ts +67 -0
  30. package/build/ota-transport/index.d.ts.map +1 -0
  31. package/build/ota-transport/index.js +58 -0
  32. package/build/ota-transport/index.js.map +1 -0
  33. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  34. package/ios/Source/Bridge.swift +1 -6
  35. package/ios/Source/DeviceManager.swift +13 -8
  36. package/ios/Source/GeneratedChangelogCatalog.swift +6 -0
  37. package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
  38. package/ios/Source/MentraBluetoothSDK.swift +17 -0
  39. package/ios/Source/ReleaseChangelog.swift +65 -0
  40. package/ios/Source/sgcs/MentraLive.swift +93 -80
  41. package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
  42. package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
  43. package/ios/Tests/ReleaseChangelogTests.swift +13 -0
  44. package/package.json +6 -1
  45. package/scripts/public-ota-api.test.mjs +37 -0
  46. package/src/BluetoothSdk.types.ts +28 -5
  47. package/src/changelogs.ts +44 -0
  48. package/src/generated/changelogCatalog.ts +9 -0
  49. package/src/generated/releaseMetadata.ts +5 -5
  50. package/src/index.ts +24 -0
  51. package/src/ota-transport/index.ts +116 -0
@@ -0,0 +1,67 @@
1
+ export type OtaTransportSubscription = {
2
+ remove(): void;
3
+ };
4
+ export type OtaLocalNetworkConnection = {
5
+ connected: boolean;
6
+ ssid: string;
7
+ localAddress?: string;
8
+ };
9
+ export type OtaLocalNetworkLostEvent = {
10
+ ssid: string;
11
+ };
12
+ export type OtaLocalNetworkResponse = {
13
+ status: number;
14
+ headers: Record<string, string>;
15
+ bodyBase64: string;
16
+ };
17
+ export type OtaLocalNetworkDownloadResult = {
18
+ statusCode: number;
19
+ bytesWritten: number;
20
+ headers: Record<string, string>;
21
+ };
22
+ export type OtaLocalNetworkDownloadProgress = {
23
+ requestId: string;
24
+ bytesWritten: number;
25
+ contentLength: number;
26
+ statusCode?: number;
27
+ headers?: Record<string, string>;
28
+ };
29
+ export type OtaArtifactDownloadProgress = {
30
+ destination: string;
31
+ bytesWritten: number;
32
+ contentLength: number;
33
+ };
34
+ export type OtaServerResult = {
35
+ baseUrl: string;
36
+ host: string;
37
+ manifestUrl: string;
38
+ port: number;
39
+ };
40
+ /**
41
+ * Scoped networking for traffic that must stay on a Mentra Live hotspot while
42
+ * the phone's default route remains on cellular or another Wi-Fi network.
43
+ *
44
+ * The module is optional because iOS uses its system Wi-Fi join flow instead.
45
+ */
46
+ export declare const otaLocalNetwork: Readonly<{
47
+ isAvailable: () => boolean;
48
+ connect: (ssid: string, password: string) => Promise<OtaLocalNetworkConnection>;
49
+ request: (requestId: string, url: string, method: string, headers: Record<string, string>, body: string | null, timeoutMs: number) => Promise<OtaLocalNetworkResponse>;
50
+ download: (requestId: string, url: string, destination: string, headers: Record<string, string>, connectionTimeoutMs: number, readTimeoutMs: number) => Promise<OtaLocalNetworkDownloadResult>;
51
+ cancel: (requestId: string) => Promise<void>;
52
+ disconnect: () => Promise<void>;
53
+ onDownloadProgress: (listener: (event: OtaLocalNetworkDownloadProgress) => void) => OtaTransportSubscription;
54
+ onNetworkLost: (listener: (event: OtaLocalNetworkLostEvent) => void) => OtaTransportSubscription;
55
+ }>;
56
+ /** Native HTTP server and artifact downloader used by the Engine OTA coordinator. */
57
+ export declare const otaServer: Readonly<{
58
+ start: (manifestJson: string, artifactPaths: Record<string, string>, host?: string | null) => Promise<OtaServerResult>;
59
+ stop: () => Promise<void>;
60
+ waitForWifiAddress: (gateway: string, timeoutMs: number) => Promise<string>;
61
+ downloadArtifact: (source: string, destination: string) => Promise<{
62
+ statusCode: number;
63
+ bytesWritten: number;
64
+ }>;
65
+ onArtifactDownloadProgress: (listener: (event: OtaArtifactDownloadProgress) => void) => OtaTransportSubscription;
66
+ }>;
67
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ota-transport/index.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,wBAAwB,GAAG;IAAC,MAAM,IAAI,IAAI,CAAA;CAAC,CAAA;AAEvD,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAA;AAErD,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe;uBACT,OAAO;oBACF,MAAM,YAAY,MAAM,KAAG,OAAO,CAAC,yBAAyB,CAAC;yBAKtE,MAAM,OACZ,MAAM,UACH,MAAM,WACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QACzB,MAAM,GAAG,IAAI,aACR,MAAM,KAChB,OAAO,CAAC,uBAAuB,CAAC;0BAKtB,MAAM,OACZ,MAAM,eACE,MAAM,WACV,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,uBACV,MAAM,iBACZ,MAAM,KACpB,OAAO,CAAC,6BAA6B,CAAC;wBAIf,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;sBAI1B,OAAO,CAAC,IAAI,CAAC;mCAIJ,CAAC,KAAK,EAAE,+BAA+B,KAAK,IAAI,KAAG,wBAAwB;8BAIhF,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,KAAG,wBAAwB;EAI9F,CAAA;AAEF,qFAAqF;AACrF,eAAO,MAAM,SAAS;0BAEJ,MAAM,iBACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,SAC9B,MAAM,GAAG,IAAI,KACnB,OAAO,CAAC,eAAe,CAAC;gBACjB,OAAO,CAAC,IAAI,CAAC;kCACO,MAAM,aAAa,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;+BAE9C,MAAM,eAAe,MAAM,KAAG,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,CAAC;2CAErE,CAAC,KAAK,EAAE,2BAA2B,KAAK,IAAI,KAAG,wBAAwB;EAE9G,CAAA"}
@@ -0,0 +1,58 @@
1
+ // This public adapter is the package-owned boundary over its native implementation.
2
+ // eslint-disable-next-line no-restricted-imports
3
+ import MentraLocalNetwork from "../_private/MentraLocalNetworkModule";
4
+ // eslint-disable-next-line no-restricted-imports
5
+ import MentraOtaServer from "../ota-server";
6
+ /**
7
+ * Scoped networking for traffic that must stay on a Mentra Live hotspot while
8
+ * the phone's default route remains on cellular or another Wi-Fi network.
9
+ *
10
+ * The module is optional because iOS uses its system Wi-Fi join flow instead.
11
+ */
12
+ export const otaLocalNetwork = Object.freeze({
13
+ isAvailable: () => MentraLocalNetwork != null,
14
+ connect: async (ssid, password) => {
15
+ if (!MentraLocalNetwork)
16
+ throw new Error("Scoped local networking is not available in this native build");
17
+ return MentraLocalNetwork.connect(ssid, password);
18
+ },
19
+ request: async (requestId, url, method, headers, body, timeoutMs) => {
20
+ if (!MentraLocalNetwork)
21
+ throw new Error("Scoped local networking is not available in this native build");
22
+ return MentraLocalNetwork.request(requestId, url, method, headers, body, timeoutMs);
23
+ },
24
+ download: async (requestId, url, destination, headers, connectionTimeoutMs, readTimeoutMs) => {
25
+ if (!MentraLocalNetwork)
26
+ throw new Error("Scoped local networking is not available in this native build");
27
+ return MentraLocalNetwork.download(requestId, url, destination, headers, connectionTimeoutMs, readTimeoutMs);
28
+ },
29
+ cancel: async (requestId) => {
30
+ if (!MentraLocalNetwork)
31
+ return;
32
+ await MentraLocalNetwork.cancel(requestId);
33
+ },
34
+ disconnect: async () => {
35
+ if (!MentraLocalNetwork)
36
+ return;
37
+ await MentraLocalNetwork.disconnect();
38
+ },
39
+ onDownloadProgress: (listener) => {
40
+ if (!MentraLocalNetwork)
41
+ return { remove() { } };
42
+ return MentraLocalNetwork.addListener("downloadProgress", listener);
43
+ },
44
+ onNetworkLost: (listener) => {
45
+ if (!MentraLocalNetwork)
46
+ return { remove() { } };
47
+ return MentraLocalNetwork.addListener("networkLost", listener);
48
+ },
49
+ });
50
+ /** Native HTTP server and artifact downloader used by the Engine OTA coordinator. */
51
+ export const otaServer = Object.freeze({
52
+ start: (manifestJson, artifactPaths, host) => MentraOtaServer.startOtaServer(manifestJson, artifactPaths, host),
53
+ stop: () => MentraOtaServer.stopOtaServer(),
54
+ waitForWifiAddress: (gateway, timeoutMs) => MentraOtaServer.waitForWifiAddress(gateway, timeoutMs),
55
+ downloadArtifact: (source, destination) => MentraOtaServer.downloadArtifact(source, destination),
56
+ onArtifactDownloadProgress: (listener) => MentraOtaServer.addListener("artifactDownloadProgress", listener),
57
+ });
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ota-transport/index.ts"],"names":[],"mappings":"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","sourcesContent":["// This public adapter is the package-owned boundary over its native implementation.\n// eslint-disable-next-line no-restricted-imports\nimport MentraLocalNetwork from \"../_private/MentraLocalNetworkModule\"\n// eslint-disable-next-line no-restricted-imports\nimport MentraOtaServer from \"../ota-server\"\n\nexport type OtaTransportSubscription = {remove(): void}\n\nexport type OtaLocalNetworkConnection = {\n connected: boolean\n ssid: string\n localAddress?: string\n}\n\nexport type OtaLocalNetworkLostEvent = {ssid: string}\n\nexport type OtaLocalNetworkResponse = {\n status: number\n headers: Record<string, string>\n bodyBase64: string\n}\n\nexport type OtaLocalNetworkDownloadResult = {\n statusCode: number\n bytesWritten: number\n headers: Record<string, string>\n}\n\nexport type OtaLocalNetworkDownloadProgress = {\n requestId: string\n bytesWritten: number\n contentLength: number\n statusCode?: number\n headers?: Record<string, string>\n}\n\nexport type OtaArtifactDownloadProgress = {\n destination: string\n bytesWritten: number\n contentLength: number\n}\n\nexport type OtaServerResult = {\n baseUrl: string\n host: string\n manifestUrl: string\n port: number\n}\n\n/**\n * Scoped networking for traffic that must stay on a Mentra Live hotspot while\n * the phone's default route remains on cellular or another Wi-Fi network.\n *\n * The module is optional because iOS uses its system Wi-Fi join flow instead.\n */\nexport const otaLocalNetwork = Object.freeze({\n isAvailable: (): boolean => MentraLocalNetwork != null,\n connect: async (ssid: string, password: string): Promise<OtaLocalNetworkConnection> => {\n if (!MentraLocalNetwork) throw new Error(\"Scoped local networking is not available in this native build\")\n return MentraLocalNetwork.connect(ssid, password)\n },\n request: async (\n requestId: string,\n url: string,\n method: string,\n headers: Record<string, string>,\n body: string | null,\n timeoutMs: number,\n ): Promise<OtaLocalNetworkResponse> => {\n if (!MentraLocalNetwork) throw new Error(\"Scoped local networking is not available in this native build\")\n return MentraLocalNetwork.request(requestId, url, method, headers, body, timeoutMs)\n },\n download: async (\n requestId: string,\n url: string,\n destination: string,\n headers: Record<string, string>,\n connectionTimeoutMs: number,\n readTimeoutMs: number,\n ): Promise<OtaLocalNetworkDownloadResult> => {\n if (!MentraLocalNetwork) throw new Error(\"Scoped local networking is not available in this native build\")\n return MentraLocalNetwork.download(requestId, url, destination, headers, connectionTimeoutMs, readTimeoutMs)\n },\n cancel: async (requestId: string): Promise<void> => {\n if (!MentraLocalNetwork) return\n await MentraLocalNetwork.cancel(requestId)\n },\n disconnect: async (): Promise<void> => {\n if (!MentraLocalNetwork) return\n await MentraLocalNetwork.disconnect()\n },\n onDownloadProgress: (listener: (event: OtaLocalNetworkDownloadProgress) => void): OtaTransportSubscription => {\n if (!MentraLocalNetwork) return {remove() {}}\n return MentraLocalNetwork.addListener(\"downloadProgress\", listener)\n },\n onNetworkLost: (listener: (event: OtaLocalNetworkLostEvent) => void): OtaTransportSubscription => {\n if (!MentraLocalNetwork) return {remove() {}}\n return MentraLocalNetwork.addListener(\"networkLost\", listener)\n },\n})\n\n/** Native HTTP server and artifact downloader used by the Engine OTA coordinator. */\nexport const otaServer = Object.freeze({\n start: (\n manifestJson: string,\n artifactPaths: Record<string, string>,\n host?: string | null,\n ): Promise<OtaServerResult> => MentraOtaServer.startOtaServer(manifestJson, artifactPaths, host),\n stop: (): Promise<void> => MentraOtaServer.stopOtaServer(),\n waitForWifiAddress: (gateway: string, timeoutMs: number): Promise<string> =>\n MentraOtaServer.waitForWifiAddress(gateway, timeoutMs),\n downloadArtifact: (source: string, destination: string): Promise<{statusCode: number; bytesWritten: number}> =>\n MentraOtaServer.downloadArtifact(source, destination),\n onArtifactDownloadProgress: (listener: (event: OtaArtifactDownloadProgress) => void): OtaTransportSubscription =>\n MentraOtaServer.addListener(\"artifactDownloadProgress\", listener),\n})\n"]}
@@ -11,7 +11,7 @@ enum BluetoothSdkDefaults {
11
11
  static let voiceActivityDetectionEnabled = false
12
12
  static let loudnessGateEnabled = true
13
13
  private static let infoSdkVersionKey = "MentraBluetoothSdkVersion"
14
- private static let swiftPackageSdkVersion = "3.1.0-dev.7"
14
+ private static let swiftPackageSdkVersion = "3.1.0-dev.70"
15
15
  private static let swiftPackageSdkVersionPlaceholder = "__MENTRA" + "_BLUETOOTH_SDK_VERSION__"
16
16
 
17
17
  private static func normalizedSdkVersion(_ value: String?) -> String? {
@@ -100,12 +100,10 @@ class Bridge {
100
100
 
101
101
  static func sendPairingInfo(
102
102
  hadPreviousBond: Bool,
103
- transferId: String? = nil,
104
103
  pairingCode: String? = nil,
105
104
  classicBondReady: Bool = false,
106
105
  securePairingCapable: Bool = true,
107
- protocolVersion: Int = 1,
108
- binding: String? = nil
106
+ protocolVersion: Int = 1
109
107
  ) {
110
108
  var body: [String: Any] = [
111
109
  "had_previous_bond": hadPreviousBond,
@@ -113,9 +111,7 @@ class Bridge {
113
111
  "secure_pairing_capable": securePairingCapable,
114
112
  "protocol_version": protocolVersion,
115
113
  ]
116
- if let transferId { body["transfer_id"] = transferId }
117
114
  if let pairingCode { body["pairing_code"] = pairingCode }
118
- if let binding { body["binding"] = binding }
119
115
  Bridge.sendTypedMessage("pairing_info", body: body)
120
116
  }
121
117
 
@@ -662,4 +658,3 @@ class Bridge {
662
658
 
663
659
 
664
660
 
665
-
@@ -1012,9 +1012,7 @@ struct ViewState {
1012
1012
  if !pendingDeviceAddress.isEmpty {
1013
1013
  deviceAddress = pendingDeviceAddress
1014
1014
  }
1015
- pendingDeviceName = ""
1016
- pendingDeviceAddress = ""
1017
- pendingWearable = ""
1015
+ clearPendingConnection()
1018
1016
  defaultWearable = sgc.type
1019
1017
  searching = false
1020
1018
 
@@ -1792,7 +1790,7 @@ struct ViewState {
1792
1790
  }
1793
1791
 
1794
1792
  Task {
1795
- disconnect()
1793
+ disconnect(clearPendingConnection: false)
1796
1794
  try? await Task.sleep(nanoseconds: 100 * 1_000_000) // 100ms
1797
1795
  self.searching = true
1798
1796
  self.pendingDeviceName = name
@@ -1826,7 +1824,7 @@ struct ViewState {
1826
1824
  handleDeviceReady()
1827
1825
  }
1828
1826
 
1829
- func disconnect() {
1827
+ func disconnect(clearPendingConnection: Bool = true) {
1830
1828
  sgc?.clearDisplay() // clear the screen
1831
1829
  sgc?.disconnect()
1832
1830
  sgc = nil // Clear the SGC reference after disconnect
@@ -1855,6 +1853,15 @@ struct ViewState {
1855
1853
  DeviceStore.shared.apply("glasses", "controllerConnected", false)
1856
1854
  controller?.disconnect()
1857
1855
  controller = nil // Clear the controller reference after disconnect
1856
+ if clearPendingConnection {
1857
+ self.clearPendingConnection()
1858
+ }
1859
+ }
1860
+
1861
+ private func clearPendingConnection() {
1862
+ pendingDeviceName = ""
1863
+ pendingDeviceAddress = ""
1864
+ pendingWearable = ""
1858
1865
  }
1859
1866
 
1860
1867
  func disconnectController() {
@@ -1874,9 +1881,7 @@ struct ViewState {
1874
1881
  defaultWearable = ""
1875
1882
  deviceName = ""
1876
1883
  deviceAddress = ""
1877
- pendingDeviceName = ""
1878
- pendingDeviceAddress = ""
1879
- pendingWearable = ""
1884
+ clearPendingConnection()
1880
1885
  Bridge.saveSetting("default_wearable", "")
1881
1886
  Bridge.saveSetting("device_name", "")
1882
1887
  Bridge.saveSetting("device_address", "")
@@ -0,0 +1,6 @@
1
+ import Foundation
2
+
3
+ /// Generated from /changelogs. Do not edit directly.
4
+ let generatedReleaseChangelogs: [ReleaseChangelog] = [
5
+ ReleaseChangelog(version: "3.1.0", markdown: "Software updates are more reliable, with clearer progress and recovery when the glasses restart.\n\n- MentraOS, Mentra Engine, the Bluetooth SDK, and the glasses client now share one coordinated release version.\n- Mentra Live updates can continue across APK, system, and firmware restarts without asking the user to start the same update again.\n- Bluetooth photo capture and transfer diagnostics are more precise and less disruptive to normal glasses traffic."),
6
+ ]
@@ -3,9 +3,9 @@ import Foundation
3
3
  /// Generated by release CI. Do not edit in a release checkout.
4
4
  enum GeneratedReleaseMetadata {
5
5
  static let familyBaseVersion = "3.1.0"
6
- static let releaseIdentity = "3.1.0-dev.7"
7
- static let releaseSetId = "mentra-3.1.0-dev.7"
8
- static let sourceCommit = "872697a6395c31d30ec75d64f039eaa763dcbb51"
9
- static let otaManifestUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0-dev.7/mentra-live-ota-3.1.0-dev.7.json"
10
- static let otaManifestSha256 = "8fbea7589345658dc5dffcb394229ffc5c52fbe75352acb8f6827d83290ce750"
6
+ static let releaseIdentity = "3.1.0-dev.70"
7
+ static let releaseSetId = "mentra-3.1.0-dev.70"
8
+ static let sourceCommit = "1554856d69f4cd5d14c1ea769171a444d3e0c4b4"
9
+ static let otaManifestUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.1.0/mentra-live-ota-3.1.0-dev.70.json"
10
+ static let otaManifestSha256 = "593990d9b25a7a47dd80c6a7e3486964b4349191a7abe885fcc24135e1604c69"
11
11
  }
@@ -311,6 +311,7 @@ public final class MentraBluetoothSDK {
311
311
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "project_name", device.projectName ?? "")
312
312
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
313
313
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
314
+ DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
314
315
  finishDefaultDeviceApply(generation: generation)
315
316
  }
316
317
 
@@ -324,6 +325,7 @@ public final class MentraBluetoothSDK {
324
325
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "project_name", "")
325
326
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
326
327
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
328
+ DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
327
329
  finishDefaultDeviceApply(generation: generation)
328
330
  }
329
331
 
@@ -401,6 +403,11 @@ public final class MentraBluetoothSDK {
401
403
  } else if !isController {
402
404
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", device.name)
403
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
+ )
404
411
  }
405
412
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_wearable", device.model.deviceType)
406
413
  DeviceManager.shared.connectByName(device.name)
@@ -427,6 +434,7 @@ public final class MentraBluetoothSDK {
427
434
  clearBluetoothRestoreIntent()
428
435
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
429
436
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
437
+ DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
430
438
  DeviceManager.shared.disconnect()
431
439
  }
432
440
 
@@ -439,6 +447,7 @@ public final class MentraBluetoothSDK {
439
447
  clearBluetoothRestoreIntent()
440
448
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_name", "")
441
449
  DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_address", "")
450
+ DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_device_secure_pairing_capable", "")
442
451
  DeviceManager.shared.disconnect()
443
452
  }
444
453
 
@@ -1250,6 +1259,14 @@ public final class MentraBluetoothSDK {
1250
1259
  )
1251
1260
  }
1252
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
+
1253
1270
  /// Ask connected Mentra Live glasses to report the current OTA install/session status.
1254
1271
  private func queryOtaStatus() async throws -> OtaQueryResult {
1255
1272
  try await performOtaQuery(operation: "OTA status query") {
@@ -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
+ }