@mentra/bluetooth-sdk 0.1.20 → 0.1.21-beta.0
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 +5 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +127 -24
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +65 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +46 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +369 -97
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +333 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +3 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +16 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +183 -30
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +4 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +19 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +15 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +115 -47
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +15 -0
- package/build/BluetoothSdk.types.d.ts +46 -1
- 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 +23 -2
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +20 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +37 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -0
- package/build/_private/MentraLocalNetworkModule.js +3 -0
- package/build/_private/MentraLocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -1
- package/build/_private/cameraRequestPayload.js +1 -0
- package/build/_private/cameraRequestPayload.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +1 -0
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -0
- package/build/index.js.map +1 -1
- package/expo-module.config.json +1 -0
- package/ios/BluetoothSdkModule.swift +72 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +72 -18
- package/ios/Source/DeviceManager.swift +51 -3
- package/ios/Source/DeviceStore.swift +1 -1
- package/ios/Source/MentraBluetoothSDK.swift +289 -25
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +8 -3
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +19 -7
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +318 -37
- package/ios/Source/sgcs/MentraNex.swift +1 -1
- package/ios/Source/sgcs/Nimo.swift +1 -1
- package/ios/Source/sgcs/SGCManager.swift +5 -2
- package/ios/Source/sgcs/Simulated.swift +1 -1
- package/ios/Source/status/WifiHotspotStatus.swift +21 -2
- package/ios/Source/streaming/StreamModels.swift +9 -0
- package/ios/Source/tts/TTSTools.swift +1 -1
- package/package.json +1 -2
- package/src/BluetoothSdk.types.ts +49 -0
- package/src/_internal.ts +2 -0
- package/src/_private/BluetoothSdkModule.ts +64 -13
- package/src/_private/MentraLocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +1 -0
- package/src/_private/photoRequestPayload.ts +2 -3
- package/src/index.ts +4 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
import android.net.ConnectivityManager
|
|
4
|
+
import android.net.Network
|
|
5
|
+
import android.net.NetworkCapabilities
|
|
6
|
+
import android.net.NetworkRequest
|
|
7
|
+
import android.net.wifi.WifiNetworkSpecifier
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import android.util.Base64
|
|
10
|
+
import android.util.Log
|
|
11
|
+
import expo.modules.kotlin.Promise
|
|
12
|
+
import expo.modules.kotlin.modules.Module
|
|
13
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
14
|
+
import java.io.File
|
|
15
|
+
import java.io.FileOutputStream
|
|
16
|
+
import java.net.HttpURLConnection
|
|
17
|
+
import java.net.URL
|
|
18
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
19
|
+
import java.util.concurrent.Executors
|
|
20
|
+
|
|
21
|
+
/** MentraOS-internal transport for glasses-local traffic without process network binding. */
|
|
22
|
+
class MentraLocalNetworkModule : Module() {
|
|
23
|
+
private companion object {
|
|
24
|
+
const val TAG = "MentraLocalNetwork"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private val lock = Any()
|
|
28
|
+
private val executor = Executors.newCachedThreadPool()
|
|
29
|
+
private val activeConnections = ConcurrentHashMap<String, HttpURLConnection>()
|
|
30
|
+
private var localNetwork: Network? = null
|
|
31
|
+
private var networkCallback: ConnectivityManager.NetworkCallback? = null
|
|
32
|
+
private var pendingConnectPromise: Promise? = null
|
|
33
|
+
|
|
34
|
+
override fun definition() = ModuleDefinition {
|
|
35
|
+
Name("MentraLocalNetwork")
|
|
36
|
+
Events("downloadProgress", "networkLost")
|
|
37
|
+
|
|
38
|
+
AsyncFunction("connect") { ssid: String, password: String, promise: Promise ->
|
|
39
|
+
connect(ssid, password, promise)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
AsyncFunction("request") {
|
|
43
|
+
requestId: String,
|
|
44
|
+
url: String,
|
|
45
|
+
method: String,
|
|
46
|
+
headers: Map<String, String>,
|
|
47
|
+
body: String?,
|
|
48
|
+
timeoutMs: Int,
|
|
49
|
+
promise: Promise ->
|
|
50
|
+
executor.execute { performRequest(requestId, url, method, headers, body, timeoutMs, promise) }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
AsyncFunction("download") {
|
|
54
|
+
requestId: String,
|
|
55
|
+
url: String,
|
|
56
|
+
destination: String,
|
|
57
|
+
headers: Map<String, String>,
|
|
58
|
+
connectionTimeoutMs: Int,
|
|
59
|
+
readTimeoutMs: Int,
|
|
60
|
+
promise: Promise ->
|
|
61
|
+
executor.execute {
|
|
62
|
+
performDownload(
|
|
63
|
+
requestId,
|
|
64
|
+
url,
|
|
65
|
+
destination,
|
|
66
|
+
headers,
|
|
67
|
+
connectionTimeoutMs,
|
|
68
|
+
readTimeoutMs,
|
|
69
|
+
promise,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
AsyncFunction("cancel") { requestId: String -> cancel(requestId) }
|
|
75
|
+
AsyncFunction("disconnect") { disconnect() }
|
|
76
|
+
|
|
77
|
+
OnDestroy { disconnect() }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private fun connectivityManager(): ConnectivityManager =
|
|
81
|
+
requireNotNull(appContext.reactContext?.getSystemService(ConnectivityManager::class.java)) {
|
|
82
|
+
"ConnectivityManager is unavailable"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
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
|
+
disconnect()
|
|
92
|
+
val manager = connectivityManager()
|
|
93
|
+
val specifier =
|
|
94
|
+
WifiNetworkSpecifier.Builder()
|
|
95
|
+
.setSsid(ssid)
|
|
96
|
+
.setWpa2Passphrase(password)
|
|
97
|
+
.build()
|
|
98
|
+
val request =
|
|
99
|
+
NetworkRequest.Builder()
|
|
100
|
+
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
|
101
|
+
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
102
|
+
.setNetworkSpecifier(specifier)
|
|
103
|
+
.build()
|
|
104
|
+
|
|
105
|
+
val callback =
|
|
106
|
+
object : ConnectivityManager.NetworkCallback() {
|
|
107
|
+
override fun onAvailable(network: Network) {
|
|
108
|
+
val connectPromise: Promise?
|
|
109
|
+
synchronized(lock) {
|
|
110
|
+
if (networkCallback !== this) {
|
|
111
|
+
Log.d(TAG, "Ignoring onAvailable from a stale network request")
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
localNetwork = network
|
|
115
|
+
connectPromise = pendingConnectPromise
|
|
116
|
+
pendingConnectPromise = null
|
|
117
|
+
}
|
|
118
|
+
Log.i(
|
|
119
|
+
TAG,
|
|
120
|
+
"Captured glasses WiFi network=$network processBoundNetwork=${manager.boundNetworkForProcess}",
|
|
121
|
+
)
|
|
122
|
+
connectPromise?.resolve(mapOf("connected" to true, "ssid" to ssid))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override fun onUnavailable() {
|
|
126
|
+
synchronized(lock) {
|
|
127
|
+
if (networkCallback !== this) {
|
|
128
|
+
Log.d(TAG, "Ignoring onUnavailable from a stale network request")
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
rejectPendingConnect("ERR_LOCAL_WIFI_UNAVAILABLE", "Could not connect to $ssid")
|
|
133
|
+
clearNetworkCallback(this)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
override fun onLost(network: Network) {
|
|
137
|
+
synchronized(lock) {
|
|
138
|
+
if (networkCallback !== this || localNetwork != network) {
|
|
139
|
+
Log.d(TAG, "Ignoring onLost from a stale network request")
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
localNetwork = null
|
|
143
|
+
}
|
|
144
|
+
cancelAll()
|
|
145
|
+
clearNetworkCallback(this)
|
|
146
|
+
sendEvent("networkLost", mapOf("ssid" to ssid))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
synchronized(lock) {
|
|
151
|
+
pendingConnectPromise = promise
|
|
152
|
+
networkCallback = callback
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
manager.requestNetwork(request, callback, 30_000)
|
|
156
|
+
} catch (error: Exception) {
|
|
157
|
+
clearNetworkCallback(callback)
|
|
158
|
+
rejectPendingConnect("ERR_LOCAL_WIFI_REQUEST", error.message ?: "Local WiFi request failed", error)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private fun performRequest(
|
|
163
|
+
requestId: String,
|
|
164
|
+
url: String,
|
|
165
|
+
method: String,
|
|
166
|
+
headers: Map<String, String>,
|
|
167
|
+
body: String?,
|
|
168
|
+
timeoutMs: Int,
|
|
169
|
+
promise: Promise,
|
|
170
|
+
) {
|
|
171
|
+
var connection: HttpURLConnection? = null
|
|
172
|
+
try {
|
|
173
|
+
connection = openConnection(requestId, url)
|
|
174
|
+
connection.requestMethod = method
|
|
175
|
+
connection.connectTimeout = timeoutMs
|
|
176
|
+
connection.readTimeout = timeoutMs
|
|
177
|
+
headers.forEach(connection::setRequestProperty)
|
|
178
|
+
if (body != null) {
|
|
179
|
+
connection.doOutput = true
|
|
180
|
+
connection.outputStream.use { it.write(body.toByteArray(Charsets.UTF_8)) }
|
|
181
|
+
}
|
|
182
|
+
val status = connection.responseCode
|
|
183
|
+
val stream = if (status >= 400) connection.errorStream else connection.inputStream
|
|
184
|
+
val bytes = stream?.use { it.readBytes() } ?: ByteArray(0)
|
|
185
|
+
promise.resolve(
|
|
186
|
+
mapOf(
|
|
187
|
+
"status" to status,
|
|
188
|
+
"headers" to responseHeaders(connection),
|
|
189
|
+
"bodyBase64" to Base64.encodeToString(bytes, Base64.NO_WRAP),
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
} catch (error: Exception) {
|
|
193
|
+
promise.reject("ERR_LOCAL_HTTP", error.message ?: "Local HTTP request failed", error)
|
|
194
|
+
} finally {
|
|
195
|
+
activeConnections.remove(requestId)
|
|
196
|
+
connection?.disconnect()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private fun performDownload(
|
|
201
|
+
requestId: String,
|
|
202
|
+
url: String,
|
|
203
|
+
destination: String,
|
|
204
|
+
headers: Map<String, String>,
|
|
205
|
+
connectionTimeoutMs: Int,
|
|
206
|
+
readTimeoutMs: Int,
|
|
207
|
+
promise: Promise,
|
|
208
|
+
) {
|
|
209
|
+
var connection: HttpURLConnection? = null
|
|
210
|
+
try {
|
|
211
|
+
connection = openConnection(requestId, url)
|
|
212
|
+
connection.requestMethod = "GET"
|
|
213
|
+
connection.connectTimeout = connectionTimeoutMs
|
|
214
|
+
connection.readTimeout = readTimeoutMs
|
|
215
|
+
headers.forEach(connection::setRequestProperty)
|
|
216
|
+
val status = connection.responseCode
|
|
217
|
+
if (status !in 200..299) throw IllegalStateException("HTTP $status")
|
|
218
|
+
val contentLength = connection.contentLengthLong
|
|
219
|
+
val responseHeaders = responseHeaders(connection)
|
|
220
|
+
|
|
221
|
+
// Match RNFS's begin callback contract before the first response byte. Gallery
|
|
222
|
+
// resumable downloads need the real status and Content-Range to validate segments.
|
|
223
|
+
sendEvent(
|
|
224
|
+
"downloadProgress",
|
|
225
|
+
mapOf(
|
|
226
|
+
"requestId" to requestId,
|
|
227
|
+
"bytesWritten" to 0L,
|
|
228
|
+
"contentLength" to contentLength,
|
|
229
|
+
"statusCode" to status,
|
|
230
|
+
"headers" to responseHeaders,
|
|
231
|
+
),
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
val destinationFile = File(destination)
|
|
235
|
+
destinationFile.parentFile?.mkdirs()
|
|
236
|
+
var bytesWritten = 0L
|
|
237
|
+
connection.inputStream.use { input ->
|
|
238
|
+
FileOutputStream(destinationFile, false).use { output ->
|
|
239
|
+
val buffer = ByteArray(64 * 1024)
|
|
240
|
+
while (true) {
|
|
241
|
+
val count = input.read(buffer)
|
|
242
|
+
if (count < 0) break
|
|
243
|
+
output.write(buffer, 0, count)
|
|
244
|
+
bytesWritten += count
|
|
245
|
+
sendEvent(
|
|
246
|
+
"downloadProgress",
|
|
247
|
+
mapOf(
|
|
248
|
+
"requestId" to requestId,
|
|
249
|
+
"bytesWritten" to bytesWritten,
|
|
250
|
+
"contentLength" to contentLength,
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
promise.resolve(
|
|
257
|
+
mapOf(
|
|
258
|
+
"statusCode" to status,
|
|
259
|
+
"bytesWritten" to bytesWritten,
|
|
260
|
+
"headers" to responseHeaders,
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
} catch (error: Exception) {
|
|
264
|
+
promise.reject("ERR_LOCAL_DOWNLOAD", error.message ?: "Local download failed", error)
|
|
265
|
+
} finally {
|
|
266
|
+
activeConnections.remove(requestId)
|
|
267
|
+
connection?.disconnect()
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private fun openConnection(requestId: String, url: String): HttpURLConnection {
|
|
272
|
+
val network = synchronized(lock) { localNetwork }
|
|
273
|
+
?: throw IllegalStateException("Local WiFi is not connected")
|
|
274
|
+
val connection = network.openConnection(URL(url)) as HttpURLConnection
|
|
275
|
+
activeConnections[requestId] = connection
|
|
276
|
+
return connection
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private fun responseHeaders(connection: HttpURLConnection): Map<String, String> =
|
|
280
|
+
connection.headerFields
|
|
281
|
+
.filterKeys { it != null }
|
|
282
|
+
.mapValues { (_, values) -> values.joinToString(", ") }
|
|
283
|
+
|
|
284
|
+
private fun cancel(requestId: String) {
|
|
285
|
+
activeConnections.remove(requestId)?.disconnect()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private fun cancelAll() {
|
|
289
|
+
activeConnections.keys.toList().forEach(::cancel)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private fun disconnect() {
|
|
293
|
+
cancelAll()
|
|
294
|
+
val manager = appContext.reactContext?.getSystemService(ConnectivityManager::class.java)
|
|
295
|
+
val callback: ConnectivityManager.NetworkCallback?
|
|
296
|
+
synchronized(lock) {
|
|
297
|
+
callback = networkCallback
|
|
298
|
+
networkCallback = null
|
|
299
|
+
localNetwork = null
|
|
300
|
+
}
|
|
301
|
+
if (callback != null && manager != null) {
|
|
302
|
+
try {
|
|
303
|
+
manager.unregisterNetworkCallback(callback)
|
|
304
|
+
} catch (_: IllegalArgumentException) {
|
|
305
|
+
// Already unregistered by the platform.
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
rejectPendingConnect("ERR_LOCAL_WIFI_CANCELLED", "Local WiFi connection cancelled")
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private fun clearNetworkCallback(callback: ConnectivityManager.NetworkCallback) {
|
|
312
|
+
val manager = appContext.reactContext?.getSystemService(ConnectivityManager::class.java)
|
|
313
|
+
synchronized(lock) {
|
|
314
|
+
if (networkCallback == callback) networkCallback = null
|
|
315
|
+
}
|
|
316
|
+
if (manager != null) {
|
|
317
|
+
try {
|
|
318
|
+
manager.unregisterNetworkCallback(callback)
|
|
319
|
+
} catch (_: IllegalArgumentException) {
|
|
320
|
+
// Callback was not registered or has already been removed.
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private fun rejectPendingConnect(code: String, message: String, cause: Throwable? = null) {
|
|
326
|
+
val promise: Promise?
|
|
327
|
+
synchronized(lock) {
|
|
328
|
+
promise = pendingConnectPromise
|
|
329
|
+
pendingConnectPromise = null
|
|
330
|
+
}
|
|
331
|
+
promise?.reject(code, message, cause)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -200,6 +200,8 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
200
200
|
val ispAnalogGain: String? = null,
|
|
201
201
|
val resetCaptureTuning: Boolean? = null,
|
|
202
202
|
val mode: PhotoMode = PhotoMode.PHOTO,
|
|
203
|
+
/** `ble` skips direct upload; `auto` tries Wi-Fi with BLE fallback. */
|
|
204
|
+
val transferMethod: String = "auto",
|
|
203
205
|
) {
|
|
204
206
|
companion object {
|
|
205
207
|
/** Mirrors iOS `BluetoothSdkModule` defaults for keys omitted from the JS bridge. */
|
|
@@ -238,6 +240,7 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
238
240
|
save = boolValue(values, "save", "saveToGallery") ?: false,
|
|
239
241
|
sound = boolValue(values, "sound") ?: true,
|
|
240
242
|
mode = PhotoMode.fromValue(stringValue(values, "mode")),
|
|
243
|
+
transferMethod = if (stringValue(values, "transferMethod") == "ble") "ble" else "auto",
|
|
241
244
|
exposureTimeNs = exposureTimeNs,
|
|
242
245
|
iso = iso,
|
|
243
246
|
aeExposureDivisor = aeDivisor,
|
|
@@ -575,16 +575,18 @@ private object DevSettingsProto {
|
|
|
575
575
|
/// TimeSync submessage: f1 = (Unix seconds + TZ offset seconds) as Int32, no TZ field.
|
|
576
576
|
/// Firmware appears to ignore the TZ field, so we pre-shift the timestamp itself
|
|
577
577
|
/// to make UTC interpretation read as local. Empirically confirmed via probe variants in dbg1().
|
|
578
|
-
fun timeSync(
|
|
578
|
+
fun timeSync(
|
|
579
|
+
magicRandom: Int,
|
|
580
|
+
timestampMs: Long = System.currentTimeMillis()
|
|
581
|
+
): ByteArray {
|
|
579
582
|
val w = ProtobufWriter()
|
|
580
583
|
w.writeInt32Field(1, DevCfgCommandId.TIME_SYNC.value)
|
|
581
584
|
w.writeInt32Field(2, magicRandom)
|
|
582
585
|
|
|
583
586
|
val tsW = ProtobufWriter()
|
|
584
|
-
val
|
|
585
|
-
val
|
|
586
|
-
|
|
587
|
-
tsW.writeInt32Field(1, (nowSec + tzSec).toInt())
|
|
587
|
+
val timestampSec = timestampMs / 1000
|
|
588
|
+
val tzSec = (TimeZone.getDefault().getOffset(timestampMs) / 1000).toLong()
|
|
589
|
+
tsW.writeInt32Field(1, (timestampSec + tzSec).toInt())
|
|
588
590
|
w.writeMessageField(128, tsW.toByteArray())
|
|
589
591
|
return w.toByteArray()
|
|
590
592
|
}
|
|
@@ -1707,7 +1709,7 @@ class G2 : SGCManager() {
|
|
|
1707
1709
|
payload = payload,
|
|
1708
1710
|
reserveFlag = true
|
|
1709
1711
|
)
|
|
1710
|
-
sendToGlasses(packets)
|
|
1712
|
+
sendToGlasses(packets, left = true, right = true)
|
|
1711
1713
|
}
|
|
1712
1714
|
|
|
1713
1715
|
// ---------- Authentication Sequence ----------
|
|
@@ -1732,7 +1734,7 @@ class G2 : SGCManager() {
|
|
|
1732
1734
|
|
|
1733
1735
|
delay(200)
|
|
1734
1736
|
val timeSync = DevSettingsProto.timeSync(sendManager.nextMagicRandom())
|
|
1735
|
-
sendDevSettingsCommand(timeSync)
|
|
1737
|
+
sendDevSettingsCommand(timeSync, left = true, right = true)
|
|
1736
1738
|
|
|
1737
1739
|
// Skip onboarding on connect
|
|
1738
1740
|
delay(200)
|
|
@@ -3763,7 +3765,12 @@ class G2 : SGCManager() {
|
|
|
3763
3765
|
/// time-zone travel, or a long sleep where the glasses' clock has drifted.
|
|
3764
3766
|
fun syncTime() {
|
|
3765
3767
|
Bridge.log("G2: syncTime()")
|
|
3766
|
-
|
|
3768
|
+
sendSetSystemTime(System.currentTimeMillis())
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3771
|
+
override fun sendSetSystemTime(timestampMs: Long) {
|
|
3772
|
+
Bridge.log("G2: sendSetSystemTime()")
|
|
3773
|
+
val msg = DevSettingsProto.timeSync(sendManager.nextMagicRandom(), timestampMs)
|
|
3767
3774
|
sendDevSettingsCommand(msg, left = true, right = true)
|
|
3768
3775
|
}
|
|
3769
3776
|
|
|
@@ -3782,7 +3789,7 @@ class G2 : SGCManager() {
|
|
|
3782
3789
|
|
|
3783
3790
|
// ---------- SGCManager: Network (G2 has no WiFi) ----------
|
|
3784
3791
|
|
|
3785
|
-
override fun requestWifiScan() {}
|
|
3792
|
+
override fun requestWifiScan(scanId: String?) {}
|
|
3786
3793
|
override fun sendWifiCredentials(ssid: String, password: String) {}
|
|
3787
3794
|
override fun forgetWifiNetwork(ssid: String) {}
|
|
3788
3795
|
override fun sendHotspotState(enabled: Boolean) {}
|