@mentra/bluetooth-sdk 0.1.21-beta.5 → 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 +205 -19
- 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/MentraLive.kt +1186 -191
- 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/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 +82 -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 +143 -13
- 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 +453 -31
- 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 +95 -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
|
@@ -5,7 +5,8 @@ import android.net.Network
|
|
|
5
5
|
import android.net.NetworkCapabilities
|
|
6
6
|
import android.net.NetworkRequest
|
|
7
7
|
import android.net.wifi.WifiNetworkSpecifier
|
|
8
|
-
import android.os.
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
9
10
|
import android.util.Base64
|
|
10
11
|
import android.util.Log
|
|
11
12
|
import expo.modules.kotlin.Promise
|
|
@@ -14,6 +15,7 @@ import expo.modules.kotlin.modules.ModuleDefinition
|
|
|
14
15
|
import java.io.File
|
|
15
16
|
import java.io.FileOutputStream
|
|
16
17
|
import java.net.HttpURLConnection
|
|
18
|
+
import java.net.Inet4Address
|
|
17
19
|
import java.net.URL
|
|
18
20
|
import java.util.concurrent.ConcurrentHashMap
|
|
19
21
|
import java.util.concurrent.Executors
|
|
@@ -22,14 +24,18 @@ import java.util.concurrent.Executors
|
|
|
22
24
|
class MentraLocalNetworkModule : Module() {
|
|
23
25
|
private companion object {
|
|
24
26
|
const val TAG = "MentraLocalNetwork"
|
|
27
|
+
const val NETWORK_READINESS_TIMEOUT_MS = 5_000L
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
private val lock = Any()
|
|
28
31
|
private val executor = Executors.newCachedThreadPool()
|
|
32
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
29
33
|
private val activeConnections = ConcurrentHashMap<String, HttpURLConnection>()
|
|
34
|
+
private val networkReadiness = ScopedNetworkReadiness<Network>()
|
|
30
35
|
private var localNetwork: Network? = null
|
|
31
36
|
private var networkCallback: ConnectivityManager.NetworkCallback? = null
|
|
32
37
|
private var pendingConnectPromise: Promise? = null
|
|
38
|
+
private var pendingReadinessTimeout: Runnable? = null
|
|
33
39
|
|
|
34
40
|
override fun definition() = ModuleDefinition {
|
|
35
41
|
Name("MentraLocalNetwork")
|
|
@@ -74,7 +80,9 @@ class MentraLocalNetworkModule : Module() {
|
|
|
74
80
|
AsyncFunction("cancel") { requestId: String -> cancel(requestId) }
|
|
75
81
|
AsyncFunction("disconnect") { disconnect() }
|
|
76
82
|
|
|
77
|
-
OnDestroy {
|
|
83
|
+
OnDestroy {
|
|
84
|
+
disconnect()
|
|
85
|
+
}
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
private fun connectivityManager(): ConnectivityManager =
|
|
@@ -83,11 +91,6 @@ class MentraLocalNetworkModule : Module() {
|
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
private fun connect(ssid: String, password: String, promise: Promise) {
|
|
86
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
87
|
-
promise.reject("ERR_UNSUPPORTED_ANDROID", "Scoped local WiFi requires Android 10 or newer", null)
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
|
|
91
94
|
disconnect()
|
|
92
95
|
val manager = connectivityManager()
|
|
93
96
|
val specifier =
|
|
@@ -105,21 +108,55 @@ class MentraLocalNetworkModule : Module() {
|
|
|
105
108
|
val callback =
|
|
106
109
|
object : ConnectivityManager.NetworkCallback() {
|
|
107
110
|
override fun onAvailable(network: Network) {
|
|
108
|
-
val connectPromise: Promise?
|
|
109
111
|
synchronized(lock) {
|
|
110
112
|
if (networkCallback !== this) {
|
|
111
113
|
Log.d(TAG, "Ignoring onAvailable from a stale network request")
|
|
112
114
|
return
|
|
113
115
|
}
|
|
116
|
+
networkReadiness.onAvailable(network)
|
|
117
|
+
}
|
|
118
|
+
Log.i(
|
|
119
|
+
TAG,
|
|
120
|
+
"Captured glasses WiFi network=$network; waiting for foreground capability " +
|
|
121
|
+
"processBoundNetwork=${manager.boundNetworkForProcess}",
|
|
122
|
+
)
|
|
123
|
+
scheduleReadinessTimeout(this, ssid)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) {
|
|
127
|
+
val connectPromise: Promise?
|
|
128
|
+
synchronized(lock) {
|
|
129
|
+
if (networkCallback !== this || localNetwork != null || pendingConnectPromise == null) return
|
|
130
|
+
if (!networkReadiness.isReady(
|
|
131
|
+
network,
|
|
132
|
+
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND),
|
|
133
|
+
)
|
|
134
|
+
) {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
114
137
|
localNetwork = network
|
|
115
138
|
connectPromise = pendingConnectPromise
|
|
116
139
|
pendingConnectPromise = null
|
|
140
|
+
cancelReadinessTimeoutLocked()
|
|
117
141
|
}
|
|
118
142
|
Log.i(
|
|
119
143
|
TAG,
|
|
120
|
-
"
|
|
144
|
+
"Glasses WiFi network=$network is foreground and ready " +
|
|
145
|
+
"processBoundNetwork=${manager.boundNetworkForProcess}",
|
|
146
|
+
)
|
|
147
|
+
val localAddress =
|
|
148
|
+
manager.getLinkProperties(network)
|
|
149
|
+
?.linkAddresses
|
|
150
|
+
?.firstOrNull { it.address is Inet4Address }
|
|
151
|
+
?.address
|
|
152
|
+
?.hostAddress
|
|
153
|
+
connectPromise?.resolve(
|
|
154
|
+
buildMap<String, Any> {
|
|
155
|
+
put("connected", true)
|
|
156
|
+
put("ssid", ssid)
|
|
157
|
+
if (!localAddress.isNullOrBlank()) put("localAddress", localAddress)
|
|
158
|
+
},
|
|
121
159
|
)
|
|
122
|
-
connectPromise?.resolve(mapOf("connected" to true, "ssid" to ssid))
|
|
123
160
|
}
|
|
124
161
|
|
|
125
162
|
override fun onUnavailable() {
|
|
@@ -135,13 +172,17 @@ class MentraLocalNetworkModule : Module() {
|
|
|
135
172
|
|
|
136
173
|
override fun onLost(network: Network) {
|
|
137
174
|
synchronized(lock) {
|
|
138
|
-
if (networkCallback !== this ||
|
|
175
|
+
if (networkCallback !== this ||
|
|
176
|
+
(localNetwork != network && !networkReadiness.contains(network))
|
|
177
|
+
) {
|
|
139
178
|
Log.d(TAG, "Ignoring onLost from a stale network request")
|
|
140
179
|
return
|
|
141
180
|
}
|
|
142
181
|
localNetwork = null
|
|
182
|
+
networkReadiness.clear()
|
|
143
183
|
}
|
|
144
184
|
cancelAll()
|
|
185
|
+
rejectPendingConnect("ERR_LOCAL_WIFI_LOST", "Lost connection to $ssid")
|
|
145
186
|
clearNetworkCallback(this)
|
|
146
187
|
sendEvent("networkLost", mapOf("ssid" to ssid))
|
|
147
188
|
}
|
|
@@ -289,6 +330,39 @@ class MentraLocalNetworkModule : Module() {
|
|
|
289
330
|
activeConnections.keys.toList().forEach(::cancel)
|
|
290
331
|
}
|
|
291
332
|
|
|
333
|
+
private fun scheduleReadinessTimeout(
|
|
334
|
+
callback: ConnectivityManager.NetworkCallback,
|
|
335
|
+
ssid: String,
|
|
336
|
+
) {
|
|
337
|
+
val timeout =
|
|
338
|
+
Runnable {
|
|
339
|
+
val connectPromise = synchronized(lock) {
|
|
340
|
+
if (networkCallback !== callback || pendingConnectPromise == null) return@Runnable
|
|
341
|
+
pendingReadinessTimeout = null
|
|
342
|
+
val promise = pendingConnectPromise
|
|
343
|
+
pendingConnectPromise = null
|
|
344
|
+
promise
|
|
345
|
+
}
|
|
346
|
+
connectPromise?.reject(
|
|
347
|
+
"ERR_LOCAL_WIFI_NOT_READY",
|
|
348
|
+
"Connected to $ssid but the network did not become ready",
|
|
349
|
+
null,
|
|
350
|
+
)
|
|
351
|
+
clearNetworkCallback(callback)
|
|
352
|
+
}
|
|
353
|
+
synchronized(lock) {
|
|
354
|
+
if (networkCallback !== callback || pendingConnectPromise == null) return
|
|
355
|
+
cancelReadinessTimeoutLocked()
|
|
356
|
+
pendingReadinessTimeout = timeout
|
|
357
|
+
}
|
|
358
|
+
handler.postDelayed(timeout, NETWORK_READINESS_TIMEOUT_MS)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private fun cancelReadinessTimeoutLocked() {
|
|
362
|
+
pendingReadinessTimeout?.let(handler::removeCallbacks)
|
|
363
|
+
pendingReadinessTimeout = null
|
|
364
|
+
}
|
|
365
|
+
|
|
292
366
|
private fun disconnect() {
|
|
293
367
|
cancelAll()
|
|
294
368
|
val manager = appContext.reactContext?.getSystemService(ConnectivityManager::class.java)
|
|
@@ -297,6 +371,8 @@ class MentraLocalNetworkModule : Module() {
|
|
|
297
371
|
callback = networkCallback
|
|
298
372
|
networkCallback = null
|
|
299
373
|
localNetwork = null
|
|
374
|
+
networkReadiness.clear()
|
|
375
|
+
cancelReadinessTimeoutLocked()
|
|
300
376
|
}
|
|
301
377
|
if (callback != null && manager != null) {
|
|
302
378
|
try {
|
|
@@ -311,7 +387,12 @@ class MentraLocalNetworkModule : Module() {
|
|
|
311
387
|
private fun clearNetworkCallback(callback: ConnectivityManager.NetworkCallback) {
|
|
312
388
|
val manager = appContext.reactContext?.getSystemService(ConnectivityManager::class.java)
|
|
313
389
|
synchronized(lock) {
|
|
314
|
-
if (networkCallback == callback)
|
|
390
|
+
if (networkCallback == callback) {
|
|
391
|
+
networkCallback = null
|
|
392
|
+
localNetwork = null
|
|
393
|
+
networkReadiness.clear()
|
|
394
|
+
cancelReadinessTimeoutLocked()
|
|
395
|
+
}
|
|
315
396
|
}
|
|
316
397
|
if (manager != null) {
|
|
317
398
|
try {
|
|
@@ -7,21 +7,19 @@ import org.json.JSONArray
|
|
|
7
7
|
import org.json.JSONObject
|
|
8
8
|
|
|
9
9
|
internal object OtaManifestDefaults {
|
|
10
|
-
private const val SDK_OTA_RELEASE_BASE_URL =
|
|
11
|
-
"https://github.com/Mentra-Community/MentraOS/releases/download/bluetooth-sdk-ota"
|
|
12
10
|
// ASG builds before 39 ignore ota_start.ota_version_url, so SDK checks must
|
|
13
11
|
// use the same legacy production manifest those glasses will install from.
|
|
14
12
|
const val LEGACY_PROD_OTA_VERSION_URL = "https://ota.mentraglass.com/prod_live_version.json"
|
|
15
13
|
|
|
16
14
|
fun defaultOtaVersionUrl(): String {
|
|
17
|
-
val
|
|
18
|
-
if (
|
|
15
|
+
val manifestUrl = GeneratedReleaseMetadata.OTA_MANIFEST_URL.trim()
|
|
16
|
+
if (manifestUrl.isBlank()) {
|
|
19
17
|
throw BluetoothSdkException(
|
|
20
|
-
"
|
|
21
|
-
"
|
|
18
|
+
"ota_manifest_unconfigured",
|
|
19
|
+
"This source-built Bluetooth SDK has no embedded OTA manifest. Configure one with the debug API.",
|
|
22
20
|
)
|
|
23
21
|
}
|
|
24
|
-
return
|
|
22
|
+
return manifestUrl
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
/** One coordinated Mentra release note authored in /changelogs/<version>.md. */
|
|
4
|
+
data class ReleaseChangelog(
|
|
5
|
+
val version: String,
|
|
6
|
+
val markdown: String,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
internal object ReleaseChangelogCatalog {
|
|
10
|
+
private val baseVersionPattern = Regex("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:[-+].*)?$")
|
|
11
|
+
|
|
12
|
+
fun select(
|
|
13
|
+
fromVersion: String?,
|
|
14
|
+
toVersion: String?,
|
|
15
|
+
): List<ReleaseChangelog> {
|
|
16
|
+
val fallbackTarget =
|
|
17
|
+
GeneratedReleaseMetadata.FAMILY_BASE_VERSION.ifBlank {
|
|
18
|
+
GENERATED_RELEASE_CHANGELOGS.firstOrNull()?.version.orEmpty()
|
|
19
|
+
}
|
|
20
|
+
if (fallbackTarget.isBlank() && toVersion == null) return emptyList()
|
|
21
|
+
val target = baseVersion(toVersion ?: fallbackTarget, "toVersion")
|
|
22
|
+
if (GENERATED_RELEASE_CHANGELOGS.none { it.version == target }) {
|
|
23
|
+
throw BluetoothSdkException("missing_changelog", "No changelog is bundled for target version $target.")
|
|
24
|
+
}
|
|
25
|
+
if (fromVersion == null) return GENERATED_RELEASE_CHANGELOGS.filter { it.version == target }
|
|
26
|
+
|
|
27
|
+
val source = baseVersion(fromVersion, "fromVersion")
|
|
28
|
+
val direction = compareVersions(target, source)
|
|
29
|
+
return GENERATED_RELEASE_CHANGELOGS.filter { entry ->
|
|
30
|
+
when {
|
|
31
|
+
direction == 0 -> entry.version == target
|
|
32
|
+
direction > 0 -> compareVersions(entry.version, source) > 0 && compareVersions(entry.version, target) <= 0
|
|
33
|
+
else -> compareVersions(entry.version, source) < 0 && compareVersions(entry.version, target) >= 0
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private fun baseVersion(
|
|
39
|
+
version: String,
|
|
40
|
+
label: String,
|
|
41
|
+
): String {
|
|
42
|
+
val match = baseVersionPattern.matchEntire(version.trim())
|
|
43
|
+
?: throw BluetoothSdkException(
|
|
44
|
+
"invalid_changelog_version",
|
|
45
|
+
"$label must be a semantic version such as 3.1.0, 3.1.0-dev.4, or 3.1.0-beta.2.",
|
|
46
|
+
)
|
|
47
|
+
return "${match.groupValues[1]}.${match.groupValues[2]}.${match.groupValues[3]}"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private fun compareVersions(
|
|
51
|
+
left: String,
|
|
52
|
+
right: String,
|
|
53
|
+
): Int {
|
|
54
|
+
val a = left.split(".").map(String::toInt)
|
|
55
|
+
val b = right.split(".").map(String::toInt)
|
|
56
|
+
for (index in 0..2) {
|
|
57
|
+
if (a[index] != b[index]) return a[index] - b[index]
|
|
58
|
+
}
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
internal class ScopedNetworkReadiness<T> {
|
|
4
|
+
private var candidate: T? = null
|
|
5
|
+
|
|
6
|
+
fun onAvailable(network: T) {
|
|
7
|
+
candidate = network
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fun isReady(network: T, isForeground: Boolean): Boolean = candidate == network && isForeground
|
|
11
|
+
|
|
12
|
+
fun contains(network: T): Boolean = candidate == network
|
|
13
|
+
|
|
14
|
+
fun clear() {
|
|
15
|
+
candidate = null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -94,6 +94,21 @@ data class MicLc3Event(
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
internal data class MicHealth(
|
|
98
|
+
val sequenceGapEvents: Long,
|
|
99
|
+
val decodeFailures: Long,
|
|
100
|
+
val lastLc3ReceivedAt: Long?,
|
|
101
|
+
val lastPcmProducedAt: Long?,
|
|
102
|
+
) {
|
|
103
|
+
fun toMap(): Map<String, Any> =
|
|
104
|
+
buildMap {
|
|
105
|
+
put("sequenceGapEvents", sequenceGapEvents)
|
|
106
|
+
put("decodeFailures", decodeFailures)
|
|
107
|
+
lastLc3ReceivedAt?.let { put("lastLc3ReceivedAt", it) }
|
|
108
|
+
lastPcmProducedAt?.let { put("lastPcmProducedAt", it) }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
data class LocalTranscriptionEvent(
|
|
98
113
|
val text: String,
|
|
99
114
|
val isFinal: Boolean,
|
|
@@ -39,6 +39,29 @@ data class SpeakingStatusEvent(
|
|
|
39
39
|
val values: Map<String, Any>,
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
+
data class MicHealthEvent(
|
|
43
|
+
val reason: String,
|
|
44
|
+
val sequenceGapEvents: Long,
|
|
45
|
+
val decodeFailures: Long,
|
|
46
|
+
val lastLc3ReceivedAt: Long?,
|
|
47
|
+
val lastPcmProducedAt: Long?,
|
|
48
|
+
val timestamp: Long,
|
|
49
|
+
val values: Map<String, Any>,
|
|
50
|
+
) {
|
|
51
|
+
companion object {
|
|
52
|
+
internal fun fromMap(values: Map<String, Any>): MicHealthEvent =
|
|
53
|
+
MicHealthEvent(
|
|
54
|
+
reason = stringValue(values, "reason") ?: "",
|
|
55
|
+
sequenceGapEvents = longValue(values, "sequenceGapEvents") ?: 0,
|
|
56
|
+
decodeFailures = longValue(values, "decodeFailures") ?: 0,
|
|
57
|
+
lastLc3ReceivedAt = longValue(values, "lastLc3ReceivedAt"),
|
|
58
|
+
lastPcmProducedAt = longValue(values, "lastPcmProducedAt"),
|
|
59
|
+
timestamp = longValue(values, "timestamp") ?: System.currentTimeMillis(),
|
|
60
|
+
values = values,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
42
65
|
data class OtaStartAckEvent(
|
|
43
66
|
val timestamp: Long?,
|
|
44
67
|
val values: Map<String, Any>,
|
|
@@ -144,6 +167,7 @@ interface MentraBluetoothSdkListener {
|
|
|
144
167
|
fun onVersionInfo(event: VersionInfoResult) {}
|
|
145
168
|
fun onMicPcm(event: MicPcmEvent) {}
|
|
146
169
|
fun onMicLc3(event: MicLc3Event) {}
|
|
170
|
+
fun onMicHealth(event: MicHealthEvent) {}
|
|
147
171
|
fun onLocalTranscription(event: LocalTranscriptionEvent) {}
|
|
148
172
|
fun onDefaultDeviceChanged(device: Device?) {}
|
|
149
173
|
fun onLog(message: String) {}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk.net
|
|
2
|
+
|
|
3
|
+
import java.net.Inet4Address
|
|
4
|
+
import java.net.NetworkInterface
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Best-effort local IPv4 discovery for phone-hosted servers the glasses reach over WiFi
|
|
8
|
+
* (photo upload receiver, hotspot-served OTA). Prefers WiFi-ish interfaces carrying RFC1918
|
|
9
|
+
* addresses — under a glasses-hotspot session wlan0 holds the 192.168.43.x client address.
|
|
10
|
+
*/
|
|
11
|
+
object LocalIpv4 {
|
|
12
|
+
fun bestLocalIpv4Address(): String? {
|
|
13
|
+
val wifiPrivateCandidates = mutableListOf<Inet4Address>()
|
|
14
|
+
val wifiCandidates = mutableListOf<Inet4Address>()
|
|
15
|
+
val privateCandidates = mutableListOf<Inet4Address>()
|
|
16
|
+
val otherCandidates = mutableListOf<Inet4Address>()
|
|
17
|
+
val interfaces = NetworkInterface.getNetworkInterfaces()?.toList().orEmpty()
|
|
18
|
+
for (networkInterface in interfaces) {
|
|
19
|
+
if (!networkInterface.isUp || networkInterface.isLoopback) {
|
|
20
|
+
continue
|
|
21
|
+
}
|
|
22
|
+
val addresses = networkInterface.inetAddresses.toList()
|
|
23
|
+
.filterIsInstance<Inet4Address>()
|
|
24
|
+
.filter { address ->
|
|
25
|
+
!address.isLoopbackAddress &&
|
|
26
|
+
!address.isLinkLocalAddress
|
|
27
|
+
}
|
|
28
|
+
if (isWifiInterface(networkInterface.name)) {
|
|
29
|
+
wifiPrivateCandidates += addresses.filter { isPrivateIpv4(it.hostAddress.orEmpty()) }
|
|
30
|
+
wifiCandidates += addresses
|
|
31
|
+
} else {
|
|
32
|
+
privateCandidates += addresses.filter { isPrivateIpv4(it.hostAddress.orEmpty()) }
|
|
33
|
+
otherCandidates += addresses
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
wifiPrivateCandidates.firstOrNull() ?:
|
|
39
|
+
wifiCandidates.firstOrNull() ?:
|
|
40
|
+
privateCandidates.firstOrNull() ?:
|
|
41
|
+
otherCandidates.firstOrNull()
|
|
42
|
+
)?.hostAddress
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private fun isWifiInterface(name: String): Boolean {
|
|
46
|
+
return name.startsWith("wlan") ||
|
|
47
|
+
name.startsWith("p2p") ||
|
|
48
|
+
name.startsWith("ap") ||
|
|
49
|
+
name.startsWith("swlan")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private fun isPrivateIpv4(host: String): Boolean {
|
|
53
|
+
return host.startsWith("192.168.") ||
|
|
54
|
+
host.startsWith("10.") ||
|
|
55
|
+
host.matches(Regex("^172\\.(1[6-9]|2[0-9]|3[0-1])\\..*"))
|
|
56
|
+
}
|
|
57
|
+
}
|