@glassly/bluetooth-sdk 0.1.0-dev.99 → 0.2.8-dev.444
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 +47 -6
- package/android/build.gradle +7 -7
- package/android/lc3Lib/build.gradle +2 -2
- package/android/src/main/assets/G2Firmware/README.md +27 -2
- package/android/src/main/assets/G2Firmware/{g2-stock-2.2.9.22.evenota.bin → g2-glassly-cfw-2.3.0.24.evenota.bin} +0 -0
- package/android/src/main/assets/G2Firmware/{g2-glassly-cfw-2.2.9.22.evenota.bin → g2-stock-2.3.0.24.evenota.bin} +0 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +38 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +50 -14
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +18 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +139 -10
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +28 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/DisplayPacer.kt +199 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +10 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +4 -4
- package/android/src/main/java/com/glassly/bluetoothsdk/GlassesNotifications.kt +155 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +1065 -255
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2BleLink.kt +119 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +29 -28
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +138 -33
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwScene.kt +673 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +235 -162
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransport.kt +402 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnostics.kt +84 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +3 -3
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +9 -9
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Keyboard.kt +689 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardBridge.kt +452 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardHid.kt +208 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFile.kt +181 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2RingBattery.kt +20 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Sensors.kt +67 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2TouchInput.kt +39 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +5 -5
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +14 -14
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/{GlasslyBle.java → NexProto.java} +6462 -7618
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIds.kt +58 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +77 -2
- package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +55 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +24 -149
- package/android/src/test/java/com/glassly/bluetoothsdk/DisplayPacerTest.kt +277 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/G2BleLinkTest.kt +70 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +45 -56
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +78 -5
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwSceneTest.kt +489 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +100 -43
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransportTest.kt +252 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnosticsTest.kt +64 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardTest.kt +271 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFileTest.kt +204 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2RingBatteryTest.kt +56 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2SensorsTest.kt +56 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2TouchInputTest.kt +60 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NexProtoDescriptorTest.kt +32 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIdsTest.kt +58 -0
- package/build/BluetoothSdk.types.d.ts +209 -20
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +1 -1
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +30 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/generated/changelogCatalog.d.ts +28 -1
- package/build/generated/changelogCatalog.d.ts.map +1 -1
- package/build/generated/changelogCatalog.js +37 -1
- package/build/generated/changelogCatalog.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- 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/ios/BluetoothSdkModule.swift +102 -13
- package/ios/GlasslyBluetoothSDK.podspec +1 -1
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +8 -3
- package/ios/Source/BluetoothSDK.swift +53 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +13 -0
- package/ios/Source/DeviceManager.swift +151 -33
- package/ios/Source/DeviceStore.swift +28 -14
- package/ios/Source/DisplayPacer.swift +180 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +10 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +4 -4
- package/ios/Source/GlassesNotifications.swift +181 -0
- package/ios/Source/ObservableStore.swift +14 -2
- package/ios/Source/Resources/G2Firmware/README.md +27 -2
- package/ios/Source/Resources/G2Firmware/{g2-stock-2.2.9.22.evenota.bin → g2-glassly-cfw-2.3.0.24.evenota.bin} +0 -0
- package/ios/Source/Resources/G2Firmware/{g2-glassly-cfw-2.2.9.22.evenota.bin → g2-stock-2.3.0.24.evenota.bin} +0 -0
- package/ios/Source/controllers/R1.swift +0 -1
- package/ios/Source/internal/BluetoothAvailability.swift +31 -0
- package/ios/Source/sgcs/G2.swift +1420 -495
- package/ios/Source/sgcs/G2AncsRelay.swift +268 -0
- package/ios/Source/sgcs/G2BleLink.swift +102 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +45 -27
- package/ios/Source/sgcs/G2CfwRenderer.swift +166 -33
- package/ios/Source/sgcs/G2CfwScene.swift +779 -0
- package/ios/Source/sgcs/G2CfwTextureCache.swift +244 -117
- package/ios/Source/sgcs/G2CfwTransport.swift +485 -0
- package/ios/Source/sgcs/G2DisplayDiagnostics.swift +154 -0
- package/ios/Source/sgcs/G2FlashProtocol.swift +2 -2
- package/ios/Source/sgcs/G2Flasher.swift +9 -9
- package/ios/Source/sgcs/G2Keyboard.swift +780 -0
- package/ios/Source/sgcs/G2KeyboardBridge.swift +438 -0
- package/ios/Source/sgcs/G2KeyboardHid.swift +290 -0
- package/ios/Source/sgcs/G2NotificationTuning.swift +24 -0
- package/ios/Source/sgcs/G2RingBattery.swift +22 -0
- package/ios/Source/sgcs/G2Sensors.swift +75 -0
- package/ios/Source/sgcs/G2TouchInput.swift +51 -0
- package/ios/Source/sgcs/G2WritePacing.swift +18 -0
- package/ios/Source/sgcs/MentraNex.swift +44 -44
- package/ios/Source/sgcs/SGCManager.swift +120 -13
- package/ios/Source/sgcs/{glassly_ble.pb.swift → nex_ble.pb.swift} +405 -465
- package/ios/Source/speakerid/SpeakerIdEngine.swift +2 -2
- package/ios/Source/status/DeviceStatus.swift +27 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +15 -14
- package/ios/Source/utils/Enums.swift +0 -1
- package/ios/Source/utils/G1Text.swift +0 -1
- package/ios/Source/utils/G2NotifyFile.swift +20 -10
- package/ios/Source/utils/JSCExperiment.swift +6 -6
- package/ios/Source/utils/Models.swift +0 -1
- package/ios/Tests/BluetoothSDKTests/BluetoothAvailabilityTests.swift +29 -0
- package/ios/Tests/BluetoothSDKTests/DisplayPacerTests.swift +255 -0
- package/ios/Tests/BluetoothSDKTests/G2AncsRelayTests.swift +186 -0
- package/ios/Tests/BluetoothSDKTests/G2BleLinkTests.swift +52 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +46 -51
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +164 -5
- package/ios/Tests/BluetoothSDKTests/G2CfwSceneTests.swift +497 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +108 -44
- package/ios/Tests/BluetoothSDKTests/G2CfwTransportTests.swift +259 -0
- package/ios/Tests/BluetoothSDKTests/G2DisplayDiagnosticsTests.swift +89 -0
- package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +3 -2
- package/ios/Tests/BluetoothSDKTests/G2ImageAckTests.swift +104 -0
- package/ios/Tests/BluetoothSDKTests/G2KeyboardTests.swift +274 -0
- package/ios/Tests/BluetoothSDKTests/G2NotifyFileTests.swift +21 -5
- package/ios/Tests/BluetoothSDKTests/G2RingBatteryTests.swift +56 -0
- package/ios/Tests/BluetoothSDKTests/G2SensorsTests.swift +56 -0
- package/ios/Tests/BluetoothSDKTests/G2TouchInputTests.swift +71 -0
- package/ios/Tests/BluetoothSDKTests/G2WritePacingTests.swift +35 -0
- package/ios/Tests/BluetoothSDKTests/GlassesNotificationTests.swift +95 -0
- package/package.json +1 -1
- package/plugin/build/withAndroid.js +2 -2
- package/plugin/build/withIos.js +1 -1
- package/scripts/gen-nex-proto.sh +12 -0
- package/scripts/inject-ios-sdk-version.mjs +1 -1
- package/src/BluetoothSdk.types.ts +214 -19
- package/src/_internal.ts +1 -1
- package/src/_private/BluetoothSdkModule.ts +40 -0
- package/src/generated/changelogCatalog.ts +37 -1
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +24 -0
|
@@ -17,6 +17,7 @@ import com.glassly.bluetoothsdk.services.PhoneMic
|
|
|
17
17
|
import com.glassly.bluetoothsdk.sgcs.Ar99
|
|
18
18
|
import com.glassly.bluetoothsdk.sgcs.G1
|
|
19
19
|
import com.glassly.bluetoothsdk.sgcs.G2
|
|
20
|
+
import com.glassly.bluetoothsdk.sgcs.G2KeyboardBridge
|
|
20
21
|
import com.glassly.bluetoothsdk.sgcs.SceneElement
|
|
21
22
|
import com.glassly.bluetoothsdk.sgcs.SceneFrame
|
|
22
23
|
import com.glassly.bluetoothsdk.sgcs.Mach1
|
|
@@ -33,6 +34,7 @@ import com.glassly.bluetoothsdk.utils.PhoneAudioMonitor
|
|
|
33
34
|
import com.glassly.lc3Lib.Lc3Cpp
|
|
34
35
|
import com.glassly.bluetoothsdk.speakerid.SpeakerIdEngine
|
|
35
36
|
import com.glassly.bluetoothsdk.stt.SherpaOnnxTranscriber
|
|
37
|
+
import com.glassly.bluetoothsdk.stt.VadGateSpeechPolicy
|
|
36
38
|
import kotlinx.coroutines.CoroutineScope
|
|
37
39
|
import kotlinx.coroutines.Dispatchers
|
|
38
40
|
import kotlinx.coroutines.launch
|
|
@@ -267,7 +269,7 @@ class DeviceManager {
|
|
|
267
269
|
// VAD
|
|
268
270
|
private val vadBuffer = mutableListOf<ByteArray>()
|
|
269
271
|
private var isSpeaking = false
|
|
270
|
-
private var vadPolicy:
|
|
272
|
+
private var vadPolicy: VadGateSpeechPolicy? = null
|
|
271
273
|
|
|
272
274
|
// STT
|
|
273
275
|
private var transcriber: SherpaOnnxTranscriber? = null
|
|
@@ -306,7 +308,7 @@ class DeviceManager {
|
|
|
306
308
|
try {
|
|
307
309
|
vadPolicy?.stop()
|
|
308
310
|
val ctx = Bridge.getContext()
|
|
309
|
-
val policy =
|
|
311
|
+
val policy = VadGateSpeechPolicy(ctx)
|
|
310
312
|
policy.init(blockSizeSamples = 512)
|
|
311
313
|
policy.onSpeechStateChanged = { speaking ->
|
|
312
314
|
isSpeaking = speaking
|
|
@@ -703,6 +705,27 @@ class DeviceManager {
|
|
|
703
705
|
// native re-dispatch coherent (nav exit re-applies a complete scene, not
|
|
704
706
|
// whatever element happened to arrive last).
|
|
705
707
|
private var sceneState: SceneFrame? = null
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Paces scene frames and legacy layouts to display_max_fps; clears bypass it.
|
|
711
|
+
* CFW scene drivers coalesce off the link themselves, so they bypass it too.
|
|
712
|
+
*/
|
|
713
|
+
private val pacer =
|
|
714
|
+
DisplayPacer(dispatchScene = { dispatchSceneFrame(it) }, sendLegacy = { sendCurrentState() }).also {
|
|
715
|
+
it.setMaxFps((DeviceStore.store.get("bluetooth", "display_max_fps") as? Number)?.toInt() ?: DisplayPacer.DEFAULT_FPS)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
fun setDisplayMaxFps(fps: Int) = pacer.setMaxFps(fps)
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* CFW identity can land after device-ready, so this is re-read per update;
|
|
722
|
+
* the flush variants fold any frame already held into the immediate send.
|
|
723
|
+
*/
|
|
724
|
+
private val pacerBypassed: Boolean
|
|
725
|
+
get() = sgc?.supportsCfwSceneRendering == true
|
|
726
|
+
|
|
727
|
+
/** Drop paced display work (screen off): the retained state repaints on re-enable. */
|
|
728
|
+
fun cancelPendingDisplay() = pacer.cancel()
|
|
706
729
|
// MARK: - End Unique
|
|
707
730
|
|
|
708
731
|
// MARK: - Voice Data Handling
|
|
@@ -1208,6 +1231,7 @@ class DeviceManager {
|
|
|
1208
1231
|
if (sgc != null && sgc?.type != wearable) {
|
|
1209
1232
|
Bridge.log("MAN: Manager already initialized, cleaning up previous sgc")
|
|
1210
1233
|
Bridge.log("MAN: Cleaning up previous sgc type: ${sgc?.type}")
|
|
1234
|
+
pacer.cancel()
|
|
1211
1235
|
sgc?.cleanup()
|
|
1212
1236
|
sgc = null
|
|
1213
1237
|
resetSystemTimeSync()
|
|
@@ -1276,9 +1300,10 @@ class DeviceManager {
|
|
|
1276
1300
|
|
|
1277
1301
|
fun restartTranscriber() {
|
|
1278
1302
|
Bridge.log("MAN: Restarting transcriber via command")
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1303
|
+
check(ensureTranscriberInitialized()) { "Offline speech recognizer initialization failed" }
|
|
1304
|
+
val activeTranscriber = checkNotNull(transcriber)
|
|
1305
|
+
activeTranscriber.restart()
|
|
1306
|
+
check(activeTranscriber.isInitialized()) { "Offline speech recognizer restart failed" }
|
|
1282
1307
|
}
|
|
1283
1308
|
|
|
1284
1309
|
// MARK: - Speaker identification
|
|
@@ -1580,6 +1605,7 @@ class DeviceManager {
|
|
|
1580
1605
|
|
|
1581
1606
|
fun clearDisplay() {
|
|
1582
1607
|
Bridge.log("MAN: Clearing Display")
|
|
1608
|
+
pacer.cancel()
|
|
1583
1609
|
sgc?.clearDisplay()
|
|
1584
1610
|
}
|
|
1585
1611
|
|
|
@@ -1647,7 +1673,11 @@ class DeviceManager {
|
|
|
1647
1673
|
}
|
|
1648
1674
|
|
|
1649
1675
|
viewState = newViewState
|
|
1650
|
-
|
|
1676
|
+
if (layoutType == "clear_view" || pacerBypassed) {
|
|
1677
|
+
pacer.flushLegacy()
|
|
1678
|
+
} else {
|
|
1679
|
+
pacer.submitLegacy()
|
|
1680
|
+
}
|
|
1651
1681
|
}
|
|
1652
1682
|
|
|
1653
1683
|
/** Parse + store a scene frame, then dispatch it. */
|
|
@@ -1682,15 +1712,28 @@ class DeviceManager {
|
|
|
1682
1712
|
frame.copy(replay = true, elements = frame.elements.map { it.copy(change = "created") })
|
|
1683
1713
|
viewState = ViewState(" ", " ", " ", "scene", " ", null, null)
|
|
1684
1714
|
|
|
1685
|
-
|
|
1715
|
+
if (frame.elements.isEmpty() || pacerBypassed) {
|
|
1716
|
+
pacer.flushScene(frame)
|
|
1717
|
+
} else {
|
|
1718
|
+
pacer.submitScene(frame)
|
|
1719
|
+
}
|
|
1686
1720
|
}
|
|
1687
1721
|
|
|
1688
1722
|
/** Guarded scene dispatch — mirrors sendCurrentState's send conditions. */
|
|
1689
1723
|
private fun dispatchSceneFrame(frame: SceneFrame) {
|
|
1690
|
-
|
|
1691
|
-
|
|
1724
|
+
// Each early-out tells the host the frame won't show now; the SGC
|
|
1725
|
+
// reports the rest once the device has it.
|
|
1726
|
+
if (screenDisabled) {
|
|
1727
|
+
frame.reportPresented(false, "screen off")
|
|
1728
|
+
return
|
|
1729
|
+
}
|
|
1730
|
+
if (sgc?.type?.contains(DeviceTypes.SIMULATED) == true) {
|
|
1731
|
+
frame.reportPresented(false, "simulated glasses")
|
|
1732
|
+
return
|
|
1733
|
+
}
|
|
1692
1734
|
if (sgc?.fullyBooted != true) {
|
|
1693
1735
|
Bridge.log("MAN: dispatchSceneFrame(): sgc not ready")
|
|
1736
|
+
frame.reportPresented(false, if (sgc == null) "no glasses" else "glasses not ready")
|
|
1694
1737
|
return
|
|
1695
1738
|
}
|
|
1696
1739
|
sgc?.applySceneFrame(frame)
|
|
@@ -1703,6 +1746,15 @@ class DeviceManager {
|
|
|
1703
1746
|
elementsRaw.mapNotNull { el ->
|
|
1704
1747
|
val box = el["box"] as? Map<String, Any> ?: return@mapNotNull null
|
|
1705
1748
|
val style = el["style"] as? Map<String, Any>
|
|
1749
|
+
val transition = el["transition"] as? Map<String, Any>
|
|
1750
|
+
val points =
|
|
1751
|
+
(el["points"] as? List<*>)?.flatMap { p ->
|
|
1752
|
+
val point = p as? Map<*, *>
|
|
1753
|
+
listOf((point?.get("x") as? Number)?.toInt() ?: 0, (point?.get("y") as? Number)?.toInt() ?: 0)
|
|
1754
|
+
} ?: emptyList()
|
|
1755
|
+
// Open strokes carry their width as `width`; closed shapes and boxes as `border`.
|
|
1756
|
+
val border =
|
|
1757
|
+
(style?.get("border") as? Number)?.toInt() ?: (style?.get("width") as? Number)?.toInt() ?: 0
|
|
1706
1758
|
SceneElement(
|
|
1707
1759
|
id = el["id"] as? String ?: return@mapNotNull null,
|
|
1708
1760
|
type = el["type"] as? String ?: return@mapNotNull null,
|
|
@@ -1712,15 +1764,30 @@ class DeviceManager {
|
|
|
1712
1764
|
h = (box["h"] as? Number)?.toInt() ?: 0,
|
|
1713
1765
|
text = (el["text"] as? String)?.let { parsePlaceholders(it) },
|
|
1714
1766
|
data = el["data"] as? String,
|
|
1715
|
-
border =
|
|
1767
|
+
border = border,
|
|
1716
1768
|
radius = (style?.get("radius") as? Number)?.toInt() ?: 0,
|
|
1717
1769
|
change = el["change"] as? String ?: "created",
|
|
1718
1770
|
contentHash = el["contentHash"] as? String ?: "",
|
|
1719
1771
|
font = style?.get("font") as? String ?: "default",
|
|
1720
1772
|
fontSize = (style?.get("fontSize") as? Number)?.toInt() ?: 0,
|
|
1773
|
+
fill = style?.get("fill") as? Boolean ?: false,
|
|
1774
|
+
color = (style?.get("color") as? Number)?.toInt() ?: -1,
|
|
1775
|
+
points = points,
|
|
1776
|
+
startDeg = (el["startDeg"] as? Number)?.toInt() ?: 0,
|
|
1777
|
+
endDeg = (el["endDeg"] as? Number)?.toInt() ?: 0,
|
|
1778
|
+
transitionMs = (transition?.get("durationMs") as? Number)?.toInt() ?: 0,
|
|
1779
|
+
easing = transition?.get("easing") as? String ?: "ease",
|
|
1780
|
+
path = (el["path"] as? List<*>)?.map { (it as? Number)?.toInt()?.coerceIn(0, 255) ?: 0 } ?: emptyList(),
|
|
1781
|
+
pathScale = (el["pathScale"] as? Number)?.toInt() ?: 256,
|
|
1782
|
+
fillRule = if (el["fillRule"] == "evenodd") 1 else 0,
|
|
1783
|
+
rotationQ8 = (((el["rotation"] as? Number)?.toDouble() ?: 0.0) * 256).coerceIn(-9216000.0, 9216000.0).toInt(),
|
|
1784
|
+
pivotX = ((el["pivot"] as? Map<*, *>)?.get("x") as? Number)?.toInt(),
|
|
1785
|
+
pivotY = ((el["pivot"] as? Map<*, *>)?.get("y") as? Number)?.toInt(),
|
|
1786
|
+
depth = (el["depth"] as? Number)?.toInt() ?: 0,
|
|
1721
1787
|
)
|
|
1722
1788
|
}
|
|
1723
1789
|
val canvas = sceneMap["canvas"] as? Map<String, Any>
|
|
1790
|
+
|
|
1724
1791
|
return SceneFrame(
|
|
1725
1792
|
appId = sceneMap["appId"] as? String ?: "",
|
|
1726
1793
|
epoch = (sceneMap["sceneEpoch"] as? Number)?.toInt() ?: 0,
|
|
@@ -1731,6 +1798,7 @@ class DeviceManager {
|
|
|
1731
1798
|
?: SceneFrame.DEFAULT_CANVAS_WIDTH,
|
|
1732
1799
|
canvasHeight = (canvas?.get("height") as? Number)?.toInt()?.takeIf { it > 0 }
|
|
1733
1800
|
?: SceneFrame.DEFAULT_CANVAS_HEIGHT,
|
|
1801
|
+
seq = (sceneMap["seq"] as? Number)?.toInt() ?: 0,
|
|
1734
1802
|
)
|
|
1735
1803
|
}
|
|
1736
1804
|
|
|
@@ -1740,6 +1808,9 @@ class DeviceManager {
|
|
|
1740
1808
|
|
|
1741
1809
|
// ---------- Native navigation (glasses-rendered nav UI, e.g. G2 service 0x08) ----------
|
|
1742
1810
|
|
|
1811
|
+
suspend fun glassesSensorControl(sensor: String, operation: String, options: Map<String, Double>): Boolean =
|
|
1812
|
+
(sgc as? G2)?.glassesSensorControl(sensor, operation, options) ?: false
|
|
1813
|
+
|
|
1743
1814
|
fun navigationModeStart() {
|
|
1744
1815
|
sgc?.enterNavigationMode()
|
|
1745
1816
|
}
|
|
@@ -1792,6 +1863,19 @@ class DeviceManager {
|
|
|
1792
1863
|
}
|
|
1793
1864
|
}
|
|
1794
1865
|
|
|
1866
|
+
fun setGlassesNotificationConfig(config: GlassesNotificationConfig) {
|
|
1867
|
+
val manager = sgc ?: throw IllegalStateException("Glasses are not connected")
|
|
1868
|
+
manager.setGlassesNotificationConfig(config)
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
fun glassesNotificationStatus(): GlassesNotificationStatus =
|
|
1872
|
+
sgc?.glassesNotificationStatus() ?: GlassesNotificationStatus.unsupported()
|
|
1873
|
+
|
|
1874
|
+
fun uploadPhoneNotification(upload: PhoneNotificationUpload) {
|
|
1875
|
+
val manager = sgc ?: throw IllegalStateException("Glasses are not connected")
|
|
1876
|
+
manager.uploadPhoneNotification(upload)
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1795
1879
|
fun ping() {
|
|
1796
1880
|
sgc?.ping()
|
|
1797
1881
|
}
|
|
@@ -1806,6 +1890,16 @@ class DeviceManager {
|
|
|
1806
1890
|
sgc?.dbg2()
|
|
1807
1891
|
}
|
|
1808
1892
|
|
|
1893
|
+
fun dbg3() {
|
|
1894
|
+
Bridge.log("MAN: dbg3()")
|
|
1895
|
+
sgc?.dbg3()
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
fun dbg4() {
|
|
1899
|
+
Bridge.log("MAN: dbg4()")
|
|
1900
|
+
sgc?.dbg4()
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1809
1903
|
fun startStream(message: MutableMap<String, Any>) {
|
|
1810
1904
|
Bridge.log("MAN: startStream")
|
|
1811
1905
|
sgc?.startStream(message)
|
|
@@ -2066,6 +2160,40 @@ class DeviceManager {
|
|
|
2066
2160
|
return g2.startFirmwareFlash(target)
|
|
2067
2161
|
}
|
|
2068
2162
|
|
|
2163
|
+
// ---------- BLE keyboard bridge (G2 Glassly CFW revision 39+) ----------
|
|
2164
|
+
|
|
2165
|
+
private fun requireG2ForKeyboard(): G2 =
|
|
2166
|
+
sgc as? G2 ?: throw G2KeyboardBridge.BridgeException("The keyboard bridge needs connected G2 glasses.")
|
|
2167
|
+
|
|
2168
|
+
suspend fun g2KeyboardScan(): List<Map<String, Any>> = requireG2ForKeyboard().keyboardScan()
|
|
2169
|
+
|
|
2170
|
+
suspend fun g2KeyboardConnect(address: String, addressType: Int, name: String) =
|
|
2171
|
+
requireG2ForKeyboard().keyboardConnect(address, addressType, name)
|
|
2172
|
+
|
|
2173
|
+
suspend fun g2KeyboardReconnect() = requireG2ForKeyboard().keyboardReconnect()
|
|
2174
|
+
|
|
2175
|
+
suspend fun g2KeyboardDisconnect() {
|
|
2176
|
+
(sgc as? G2)?.keyboardDisconnect()
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
suspend fun g2KeyboardForget() {
|
|
2180
|
+
val g2 = sgc as? G2
|
|
2181
|
+
if (g2 != null) {
|
|
2182
|
+
g2.keyboardForget()
|
|
2183
|
+
} else {
|
|
2184
|
+
// No live G2 driver: drop the remembered identity directly.
|
|
2185
|
+
G2KeyboardBridge.forgetSaved(Bridge.getContext().getSharedPreferences("G2Prefs", Context.MODE_PRIVATE))
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
suspend fun g2KeyboardPasskey(pin: Int) = requireG2ForKeyboard().keyboardPasskey(pin)
|
|
2190
|
+
|
|
2191
|
+
suspend fun g2KeyboardCompare(accept: Boolean) = requireG2ForKeyboard().keyboardCompare(accept)
|
|
2192
|
+
|
|
2193
|
+
suspend fun g2KeyboardStatus(): Map<String, Any> =
|
|
2194
|
+
(sgc as? G2)?.keyboardStatus()
|
|
2195
|
+
?: mapOf("keyboardBridge" to false, "keyboardConnected" to false, "keyboardState" to "idle")
|
|
2196
|
+
|
|
2069
2197
|
/** Send shutdown command to glasses. This will initiate a graceful shutdown of the device. */
|
|
2070
2198
|
fun sendShutdown() {
|
|
2071
2199
|
Bridge.log("MAN: 🔌 Sending shutdown command to glasses")
|
|
@@ -2280,6 +2408,7 @@ class DeviceManager {
|
|
|
2280
2408
|
}
|
|
2281
2409
|
|
|
2282
2410
|
fun disconnect(clearPendingIdentity: Boolean = true) {
|
|
2411
|
+
pacer.cancel()
|
|
2283
2412
|
sgc?.clearDisplay()
|
|
2284
2413
|
sgc?.disconnect()
|
|
2285
2414
|
sgc = null // Clear the SGC reference after disconnect
|
|
@@ -72,8 +72,19 @@ object DeviceStore {
|
|
|
72
72
|
store.set("glasses", "caseRemoved", true)
|
|
73
73
|
store.set("glasses", "caseOpen", true)
|
|
74
74
|
store.set("glasses", "caseCharging", false)
|
|
75
|
+
store.set("glasses", "ringConnected", false)
|
|
76
|
+
store.set("glasses", "ringBatteryLevel", -1)
|
|
77
|
+
store.set("glasses", "ringCharging", false)
|
|
75
78
|
store.set("glasses", "caseBatteryLevel", -1)
|
|
76
79
|
store.set("glasses", "headUp", false)
|
|
80
|
+
// G2 Glassly CFW keyboard bridge (revision 39+): availability on the
|
|
81
|
+
// connected firmware, the live keyboard link, and the remembered keyboard.
|
|
82
|
+
store.set("glasses", "keyboardBridge", false)
|
|
83
|
+
store.set("glasses", "keyboardConnected", false)
|
|
84
|
+
store.set("glasses", "keyboardName", "")
|
|
85
|
+
store.set("glasses", "keyboardSaved", false)
|
|
86
|
+
store.set("glasses", "keyboardState", "idle")
|
|
87
|
+
store.set("glasses", "keyboardError", "")
|
|
77
88
|
store.set("glasses", "bluetoothMacAddress", "")
|
|
78
89
|
store.set("glasses", "wifiMacAddress", "")
|
|
79
90
|
store.set("glasses", "leftMacAddress", "")
|
|
@@ -132,6 +143,9 @@ object DeviceStore {
|
|
|
132
143
|
store.set("bluetooth", "voice_activity_detection_enabled", BluetoothSdkDefaults.VOICE_ACTIVITY_DETECTION_ENABLED)
|
|
133
144
|
store.set("bluetooth", "loudness_gate_enabled", BluetoothSdkDefaults.LOUDNESS_GATE_ENABLED)
|
|
134
145
|
store.set("bluetooth", "screen_disabled", false)
|
|
146
|
+
store.set("bluetooth", "display_max_fps", DisplayPacer.DEFAULT_FPS)
|
|
147
|
+
// Debug: G2 CFW fast BLE link profile (off = stock connection behaviour).
|
|
148
|
+
store.set("bluetooth", "cfw_fast_ble", false)
|
|
135
149
|
store.set("bluetooth", "button_photo_size", "max")
|
|
136
150
|
store.set("bluetooth", "button_max_recording_time", 10)
|
|
137
151
|
store.set("bluetooth", "camera_fov", mapOf("fov" to 118, "roi_position" to 0))
|
|
@@ -145,7 +159,6 @@ object DeviceStore {
|
|
|
145
159
|
store.set("bluetooth", "should_send_pcm", false)
|
|
146
160
|
store.set("bluetooth", "should_send_lc3", false)
|
|
147
161
|
store.set("bluetooth", "should_send_transcript", false)
|
|
148
|
-
store.set("bluetooth", "use_native_dashboard", false)
|
|
149
162
|
// Mentra Nex feature flags (off by default; toggled from Nex Developer Settings):
|
|
150
163
|
store.set("bluetooth", "nex_chinese_captions", false)
|
|
151
164
|
store.set("bluetooth", "nex_lc3_audio_playback", false)
|
|
@@ -241,6 +254,19 @@ object DeviceStore {
|
|
|
241
254
|
}
|
|
242
255
|
}
|
|
243
256
|
}
|
|
257
|
+
"bluetooth" to "cfw_fast_ble" -> {
|
|
258
|
+
val enabled = value as? Boolean
|
|
259
|
+
if (enabled != null && (oldValue as? Boolean) != enabled) {
|
|
260
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
261
|
+
DeviceManager.getInstance().sgc?.cfwFastBleToggled(enabled)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
"bluetooth" to "display_max_fps" -> {
|
|
266
|
+
(value as? Number)?.let { fps ->
|
|
267
|
+
DeviceManager.getInstance().setDisplayMaxFps(fps.toInt())
|
|
268
|
+
}
|
|
269
|
+
}
|
|
244
270
|
"bluetooth" to "dashboard_height" -> {
|
|
245
271
|
scheduleDashboardHeightToGlasses()
|
|
246
272
|
}
|
|
@@ -294,6 +320,7 @@ object DeviceStore {
|
|
|
294
320
|
"bluetooth" to "screen_disabled" -> {
|
|
295
321
|
(value as? Boolean)?.let { disabled ->
|
|
296
322
|
if (disabled) {
|
|
323
|
+
DeviceManager.getInstance().cancelPendingDisplay()
|
|
297
324
|
DeviceManager.getInstance().sgc?.exit()
|
|
298
325
|
} else {
|
|
299
326
|
DeviceManager.getInstance().sgc?.clearDisplay()
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
package com.glassly.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import com.glassly.bluetoothsdk.sgcs.SceneFrame
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Paces display updates leaving the phone to at most [setMaxFps] per second.
|
|
9
|
+
*
|
|
10
|
+
* Updates that arrive inside the current slot are held and sent at the next
|
|
11
|
+
* one, last-wins: a newer scene frame replaces a pending one (with the pending
|
|
12
|
+
* frame's per-element annotations folded in so per-element devices never miss
|
|
13
|
+
* a change), a legacy layout replaces a pending scene and vice versa. Clears
|
|
14
|
+
* ([flushScene] with an empty frame, [flushLegacy]) bypass the pacer and drop
|
|
15
|
+
* pending work.
|
|
16
|
+
*
|
|
17
|
+
* Safe to call from any thread; the trailing send runs on the scheduler's
|
|
18
|
+
* thread (main looper by default). Sinks are always invoked outside the lock.
|
|
19
|
+
*/
|
|
20
|
+
class DisplayPacer(
|
|
21
|
+
private val dispatchScene: (SceneFrame) -> Unit,
|
|
22
|
+
private val sendLegacy: () -> Unit,
|
|
23
|
+
private val clock: () -> Long = { System.nanoTime() / 1_000_000 },
|
|
24
|
+
private val scheduler: Scheduler = MainScheduler(),
|
|
25
|
+
) {
|
|
26
|
+
/** Runs [block] after [delayMs]; the returned function cancels it. */
|
|
27
|
+
fun interface Scheduler {
|
|
28
|
+
fun schedule(delayMs: Long, block: () -> Unit): () -> Unit
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private class MainScheduler : Scheduler {
|
|
32
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
33
|
+
|
|
34
|
+
override fun schedule(delayMs: Long, block: () -> Unit): () -> Unit {
|
|
35
|
+
val runnable = Runnable { block() }
|
|
36
|
+
handler.postDelayed(runnable, delayMs)
|
|
37
|
+
return { handler.removeCallbacks(runnable) }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
companion object {
|
|
42
|
+
const val MIN_FPS = 2
|
|
43
|
+
const val MAX_FPS = 20
|
|
44
|
+
const val DEFAULT_FPS = 8
|
|
45
|
+
|
|
46
|
+
fun intervalFor(fps: Int): Long = 1000L / fps.coerceIn(MIN_FPS, MAX_FPS)
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Fold pending frame [previous] into [next] (both unsent). The device
|
|
50
|
+
* only ever sees the merged frame, so an element the pending frame
|
|
51
|
+
* changed must not read as "unchanged" in the merged one, and ids the
|
|
52
|
+
* pending frame removed must still be removed unless [next] re-creates
|
|
53
|
+
* them. A different app is already a from-scratch replay.
|
|
54
|
+
*/
|
|
55
|
+
fun merge(previous: SceneFrame?, next: SceneFrame): SceneFrame {
|
|
56
|
+
if (previous == null || previous.appId != next.appId) return next
|
|
57
|
+
val pending = previous.elements.associateBy { it.id }
|
|
58
|
+
val elements =
|
|
59
|
+
next.elements.map { el ->
|
|
60
|
+
val prior = pending[el.id] ?: return@map el
|
|
61
|
+
when {
|
|
62
|
+
prior.change == "created" -> el.copy(change = "created")
|
|
63
|
+
prior.change != "unchanged" && el.change == "unchanged" -> el.copy(change = "updated")
|
|
64
|
+
else -> el
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
val painted = next.elements.mapTo(HashSet()) { it.id }
|
|
68
|
+
val removed = (previous.removed + next.removed).distinct().filter { it !in painted }
|
|
69
|
+
return next.copy(replay = previous.replay || next.replay, elements = elements, removed = removed)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private val lock = Any()
|
|
74
|
+
private var intervalMs: Long = intervalFor(DEFAULT_FPS)
|
|
75
|
+
private var lastSendAt: Long = Long.MIN_VALUE / 2
|
|
76
|
+
private var pendingScene: SceneFrame? = null
|
|
77
|
+
private var pendingLegacy = false
|
|
78
|
+
private var cancelTimer: (() -> Unit)? = null
|
|
79
|
+
|
|
80
|
+
fun setMaxFps(fps: Int) {
|
|
81
|
+
var send: (() -> Unit)? = null
|
|
82
|
+
synchronized(lock) {
|
|
83
|
+
intervalMs = intervalFor(fps)
|
|
84
|
+
if (!hasPending()) return
|
|
85
|
+
stopTimer()
|
|
86
|
+
send = armOrTake(clock())
|
|
87
|
+
}
|
|
88
|
+
send?.invoke()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Send [frame] at the next slot; replaces any pending update. */
|
|
92
|
+
fun submitScene(frame: SceneFrame) {
|
|
93
|
+
var send: (() -> Unit)? = null
|
|
94
|
+
synchronized(lock) {
|
|
95
|
+
val now = clock()
|
|
96
|
+
val merged = merge(pendingScene, frame)
|
|
97
|
+
pendingScene = merged
|
|
98
|
+
pendingLegacy = false
|
|
99
|
+
if (now - lastSendAt >= intervalMs) {
|
|
100
|
+
stopTimer()
|
|
101
|
+
send = take(now)
|
|
102
|
+
} else if (cancelTimer == null) {
|
|
103
|
+
armTimer(now)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
send?.invoke()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Send the current legacy view state at the next slot; replaces any pending update. */
|
|
110
|
+
fun submitLegacy() {
|
|
111
|
+
var send: (() -> Unit)? = null
|
|
112
|
+
synchronized(lock) {
|
|
113
|
+
val now = clock()
|
|
114
|
+
pendingScene = null
|
|
115
|
+
pendingLegacy = true
|
|
116
|
+
if (now - lastSendAt >= intervalMs) {
|
|
117
|
+
stopTimer()
|
|
118
|
+
send = take(now)
|
|
119
|
+
} else if (cancelTimer == null) {
|
|
120
|
+
armTimer(now)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
send?.invoke()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Immediate send for an empty (clearing) frame; pending removes ride along. */
|
|
127
|
+
fun flushScene(frame: SceneFrame) {
|
|
128
|
+
val merged: SceneFrame
|
|
129
|
+
synchronized(lock) {
|
|
130
|
+
stopTimer()
|
|
131
|
+
merged = merge(pendingScene, frame)
|
|
132
|
+
pendingScene = null
|
|
133
|
+
pendingLegacy = false
|
|
134
|
+
lastSendAt = clock()
|
|
135
|
+
}
|
|
136
|
+
dispatchScene(merged)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Immediate legacy send (clear_view); drops any pending scene. */
|
|
140
|
+
fun flushLegacy() {
|
|
141
|
+
synchronized(lock) {
|
|
142
|
+
stopTimer()
|
|
143
|
+
pendingScene = null
|
|
144
|
+
pendingLegacy = false
|
|
145
|
+
lastSendAt = clock()
|
|
146
|
+
}
|
|
147
|
+
sendLegacy()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Drop pending work without sending (disconnect, screen off, clearDisplay). */
|
|
151
|
+
fun cancel(reason: String = "cancelled") {
|
|
152
|
+
synchronized(lock) {
|
|
153
|
+
stopTimer()
|
|
154
|
+
pendingScene?.reportPresented(false, reason)
|
|
155
|
+
pendingScene = null
|
|
156
|
+
pendingLegacy = false
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun hasPending() = pendingScene != null || pendingLegacy
|
|
161
|
+
|
|
162
|
+
private fun stopTimer() {
|
|
163
|
+
cancelTimer?.invoke()
|
|
164
|
+
cancelTimer = null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Lock held. Either sends now (returns the send) or arms the slot timer. */
|
|
168
|
+
private fun armOrTake(now: Long): (() -> Unit)? {
|
|
169
|
+
if (now - lastSendAt >= intervalMs) return take(now)
|
|
170
|
+
armTimer(now)
|
|
171
|
+
return null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Lock held. Consume pending work into a send closure and stamp the slot. */
|
|
175
|
+
private fun take(now: Long): (() -> Unit)? {
|
|
176
|
+
val scene = pendingScene
|
|
177
|
+
val legacy = pendingLegacy
|
|
178
|
+
pendingScene = null
|
|
179
|
+
pendingLegacy = false
|
|
180
|
+
if (scene == null && !legacy) return null
|
|
181
|
+
lastSendAt = now
|
|
182
|
+
return if (scene != null) ({ dispatchScene(scene) }) else ({ sendLegacy() })
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Lock held. Arm the single trailing timer for the next slot. */
|
|
186
|
+
private fun armTimer(now: Long) {
|
|
187
|
+
val delay = (lastSendAt + intervalMs - now).coerceAtLeast(0)
|
|
188
|
+
cancelTimer = scheduler.schedule(delay) { onTimer() }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private fun onTimer() {
|
|
192
|
+
var send: (() -> Unit)? = null
|
|
193
|
+
synchronized(lock) {
|
|
194
|
+
cancelTimer = null
|
|
195
|
+
send = take(clock())
|
|
196
|
+
}
|
|
197
|
+
send?.invoke()
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -4,6 +4,15 @@ package com.glassly.bluetoothsdk
|
|
|
4
4
|
internal val GENERATED_RELEASE_CHANGELOGS: List<ReleaseChangelog> =
|
|
5
5
|
listOf(
|
|
6
6
|
ReleaseChangelog(version = "3.2.0", markdown = "This release is under active development. User-facing changes will be documented as they land."),
|
|
7
|
-
ReleaseChangelog(version = "3.1.0", markdown = "Software updates are more reliable, with clearer progress and recovery when the glasses restart.\n\n-
|
|
7
|
+
ReleaseChangelog(version = "3.1.0", markdown = "Software updates are more reliable, with clearer progress and recovery when the glasses restart.\n\n- Glassly, Glassly 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."),
|
|
8
|
+
ReleaseChangelog(version = "0.2.8", markdown = "This release is under active development. User-facing changes will be documented as they land."),
|
|
9
|
+
ReleaseChangelog(version = "0.2.7", markdown = "This release is under active development. User-facing changes will be documented as they land."),
|
|
10
|
+
ReleaseChangelog(version = "0.2.6", markdown = "- Terminal follows light and dark mode, removes the on-glasses status badge, and uses font-scaled line spacing in the glasses session picker.\n- Notifications uses the explicit 5-second popup default and prevents text selection on iOS.\n- The session.display example removes the SVG and rotation demo."),
|
|
11
|
+
ReleaseChangelog(version = "0.2.5", markdown = "This release is under active development. User-facing changes will be documented as they land."),
|
|
12
|
+
ReleaseChangelog(version = "0.2.4", markdown = "This release is under active development. User-facing changes will be documented as they land."),
|
|
13
|
+
ReleaseChangelog(version = "0.2.3", markdown = "This release is under active development. User-facing changes will be documented as they land."),
|
|
14
|
+
ReleaseChangelog(version = "0.2.2", markdown = "This release is under active development. User-facing changes will be documented as they land."),
|
|
15
|
+
ReleaseChangelog(version = "0.2.1", markdown = "This release is under active development. User-facing changes will be documented as they land."),
|
|
16
|
+
ReleaseChangelog(version = "0.2.0", markdown = "The glasses UI on Glassly CFW is now a set of pages over one overlay: the dashboard, side menu, calendar, notification viewer and debug menu each stand on their own.\n\n- A phone notification shows the viewer on its own and closes back to whatever was on the glasses; it no longer opens the dashboard first, and the ignore-while-active hold is honoured everywhere on custom firmware.\n- Double tap on any page returns to the main content; head-down closes only the dashboard.\n- A debug menu on the dashboard (Debug Settings toggle) queries phone-side state such as isPhoneActive().\n- The metric toggle moved to Appearance, and miniapps can read the host's preferences.\n- Messenger group threads show on the notification title row."),
|
|
8
17
|
ReleaseChangelog(version = "0.1.0", markdown = "First coordinated Glassly release: the app, Glassly Engine, and the Bluetooth SDK now ship one shared release version.\n\n- The Glassly App, Glassly Engine, and the Bluetooth SDK are versioned and published together.\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."),
|
|
9
18
|
)
|
|
@@ -2,10 +2,10 @@ package com.glassly.bluetoothsdk
|
|
|
2
2
|
|
|
3
3
|
/** Generated by release CI. Do not edit in a release checkout. */
|
|
4
4
|
internal object GeneratedReleaseMetadata {
|
|
5
|
-
const val FAMILY_BASE_VERSION: String = "0.
|
|
6
|
-
const val RELEASE_IDENTITY: String = "0.
|
|
7
|
-
const val RELEASE_SET_ID: String = "glassly-0.
|
|
8
|
-
const val SOURCE_COMMIT: String = "
|
|
5
|
+
const val FAMILY_BASE_VERSION: String = "0.2.8"
|
|
6
|
+
const val RELEASE_IDENTITY: String = "0.2.8-dev.444"
|
|
7
|
+
const val RELEASE_SET_ID: String = "glassly-0.2.8-dev.444"
|
|
8
|
+
const val SOURCE_COMMIT: String = "9ad99e635c0320d07373d42bb7422daf5289d49d"
|
|
9
9
|
const val OTA_MANIFEST_URL: String = ""
|
|
10
10
|
const val OTA_MANIFEST_SHA256: String = ""
|
|
11
11
|
}
|