@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.
- package/README.md +40 -34
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +0 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +13 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +7 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +15 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/ReleaseChangelog.kt +61 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +81 -69
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
- package/build/BluetoothSdk.types.d.ts +26 -5
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.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.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +14 -0
- package/build/index.js.map +1 -1
- 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/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +1 -6
- package/ios/Source/DeviceManager.swift +13 -8
- package/ios/Source/GeneratedChangelogCatalog.swift +6 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +17 -0
- package/ios/Source/ReleaseChangelog.swift +65 -0
- package/ios/Source/sgcs/MentraLive.swift +93 -80
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
- package/ios/Tests/ReleaseChangelogTests.swift +13 -0
- package/package.json +6 -1
- package/scripts/public-ota-api.test.mjs +37 -0
- package/src/BluetoothSdk.types.ts +28 -5
- package/src/changelogs.ts +44 -0
- package/src/generated/changelogCatalog.ts +9 -0
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +24 -0
- 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.
|
|
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? {
|
package/ios/Source/Bridge.swift
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
static let releaseSetId = "mentra-3.1.0-dev.
|
|
8
|
-
static let sourceCommit = "
|
|
9
|
-
static let otaManifestUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-v3.1.0
|
|
10
|
-
static let otaManifestSha256 = "
|
|
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
|
+
}
|