@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.
Files changed (155) hide show
  1. package/README.md +47 -6
  2. package/android/build.gradle +7 -7
  3. package/android/lc3Lib/build.gradle +2 -2
  4. package/android/src/main/assets/G2Firmware/README.md +27 -2
  5. 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
  6. 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
  7. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +38 -0
  8. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +50 -14
  9. package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +18 -0
  10. package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +139 -10
  11. package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +28 -1
  12. package/android/src/main/java/com/glassly/bluetoothsdk/DisplayPacer.kt +199 -0
  13. package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +10 -1
  14. package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +4 -4
  15. package/android/src/main/java/com/glassly/bluetoothsdk/GlassesNotifications.kt +155 -0
  16. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +1065 -255
  17. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2BleLink.kt +119 -0
  18. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +29 -28
  19. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +138 -33
  20. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwScene.kt +673 -0
  21. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +235 -162
  22. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransport.kt +402 -0
  23. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnostics.kt +84 -0
  24. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +3 -3
  25. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +9 -9
  26. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Keyboard.kt +689 -0
  27. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardBridge.kt +452 -0
  28. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardHid.kt +208 -0
  29. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFile.kt +181 -0
  30. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2RingBattery.kt +20 -0
  31. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Sensors.kt +67 -0
  32. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2TouchInput.kt +39 -0
  33. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +5 -5
  34. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +14 -14
  35. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/{GlasslyBle.java → NexProto.java} +6462 -7618
  36. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIds.kt +58 -0
  37. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +77 -2
  38. package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +1 -1
  39. package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +55 -0
  40. package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +1 -1
  41. package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -1
  42. package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +1 -1
  43. package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +24 -149
  44. package/android/src/test/java/com/glassly/bluetoothsdk/DisplayPacerTest.kt +277 -0
  45. package/android/src/test/java/com/glassly/bluetoothsdk/G2BleLinkTest.kt +70 -0
  46. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +45 -56
  47. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +78 -5
  48. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwSceneTest.kt +489 -0
  49. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +100 -43
  50. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransportTest.kt +252 -0
  51. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnosticsTest.kt +64 -0
  52. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardTest.kt +271 -0
  53. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFileTest.kt +204 -0
  54. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2RingBatteryTest.kt +56 -0
  55. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2SensorsTest.kt +56 -0
  56. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2TouchInputTest.kt +60 -0
  57. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NexProtoDescriptorTest.kt +32 -0
  58. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIdsTest.kt +58 -0
  59. package/build/BluetoothSdk.types.d.ts +209 -20
  60. package/build/BluetoothSdk.types.d.ts.map +1 -1
  61. package/build/BluetoothSdk.types.js.map +1 -1
  62. package/build/_internal.d.ts +1 -1
  63. package/build/_internal.d.ts.map +1 -1
  64. package/build/_internal.js.map +1 -1
  65. package/build/_private/BluetoothSdkModule.d.ts +30 -1
  66. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  67. package/build/_private/BluetoothSdkModule.js.map +1 -1
  68. package/build/generated/changelogCatalog.d.ts +28 -1
  69. package/build/generated/changelogCatalog.d.ts.map +1 -1
  70. package/build/generated/changelogCatalog.js +37 -1
  71. package/build/generated/changelogCatalog.js.map +1 -1
  72. package/build/generated/releaseMetadata.js +4 -4
  73. package/build/generated/releaseMetadata.js.map +1 -1
  74. package/build/index.d.ts +1 -1
  75. package/build/index.d.ts.map +1 -1
  76. package/build/index.js +14 -0
  77. package/build/index.js.map +1 -1
  78. package/ios/BluetoothSdkModule.swift +102 -13
  79. package/ios/GlasslyBluetoothSDK.podspec +1 -1
  80. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +8 -3
  81. package/ios/Source/BluetoothSDK.swift +53 -0
  82. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  83. package/ios/Source/Bridge.swift +13 -0
  84. package/ios/Source/DeviceManager.swift +151 -33
  85. package/ios/Source/DeviceStore.swift +28 -14
  86. package/ios/Source/DisplayPacer.swift +180 -0
  87. package/ios/Source/GeneratedChangelogCatalog.swift +10 -1
  88. package/ios/Source/GeneratedReleaseMetadata.swift +4 -4
  89. package/ios/Source/GlassesNotifications.swift +181 -0
  90. package/ios/Source/ObservableStore.swift +14 -2
  91. package/ios/Source/Resources/G2Firmware/README.md +27 -2
  92. package/ios/Source/Resources/G2Firmware/{g2-stock-2.2.9.22.evenota.bin → g2-glassly-cfw-2.3.0.24.evenota.bin} +0 -0
  93. package/ios/Source/Resources/G2Firmware/{g2-glassly-cfw-2.2.9.22.evenota.bin → g2-stock-2.3.0.24.evenota.bin} +0 -0
  94. package/ios/Source/controllers/R1.swift +0 -1
  95. package/ios/Source/internal/BluetoothAvailability.swift +31 -0
  96. package/ios/Source/sgcs/G2.swift +1420 -495
  97. package/ios/Source/sgcs/G2AncsRelay.swift +268 -0
  98. package/ios/Source/sgcs/G2BleLink.swift +102 -0
  99. package/ios/Source/sgcs/G2CfwClassification.swift +45 -27
  100. package/ios/Source/sgcs/G2CfwRenderer.swift +166 -33
  101. package/ios/Source/sgcs/G2CfwScene.swift +779 -0
  102. package/ios/Source/sgcs/G2CfwTextureCache.swift +244 -117
  103. package/ios/Source/sgcs/G2CfwTransport.swift +485 -0
  104. package/ios/Source/sgcs/G2DisplayDiagnostics.swift +154 -0
  105. package/ios/Source/sgcs/G2FlashProtocol.swift +2 -2
  106. package/ios/Source/sgcs/G2Flasher.swift +9 -9
  107. package/ios/Source/sgcs/G2Keyboard.swift +780 -0
  108. package/ios/Source/sgcs/G2KeyboardBridge.swift +438 -0
  109. package/ios/Source/sgcs/G2KeyboardHid.swift +290 -0
  110. package/ios/Source/sgcs/G2NotificationTuning.swift +24 -0
  111. package/ios/Source/sgcs/G2RingBattery.swift +22 -0
  112. package/ios/Source/sgcs/G2Sensors.swift +75 -0
  113. package/ios/Source/sgcs/G2TouchInput.swift +51 -0
  114. package/ios/Source/sgcs/G2WritePacing.swift +18 -0
  115. package/ios/Source/sgcs/MentraNex.swift +44 -44
  116. package/ios/Source/sgcs/SGCManager.swift +120 -13
  117. package/ios/Source/sgcs/{glassly_ble.pb.swift → nex_ble.pb.swift} +405 -465
  118. package/ios/Source/speakerid/SpeakerIdEngine.swift +2 -2
  119. package/ios/Source/status/DeviceStatus.swift +27 -0
  120. package/ios/Source/stt/SherpaOnnxTranscriber.swift +15 -14
  121. package/ios/Source/utils/Enums.swift +0 -1
  122. package/ios/Source/utils/G1Text.swift +0 -1
  123. package/ios/Source/utils/G2NotifyFile.swift +20 -10
  124. package/ios/Source/utils/JSCExperiment.swift +6 -6
  125. package/ios/Source/utils/Models.swift +0 -1
  126. package/ios/Tests/BluetoothSDKTests/BluetoothAvailabilityTests.swift +29 -0
  127. package/ios/Tests/BluetoothSDKTests/DisplayPacerTests.swift +255 -0
  128. package/ios/Tests/BluetoothSDKTests/G2AncsRelayTests.swift +186 -0
  129. package/ios/Tests/BluetoothSDKTests/G2BleLinkTests.swift +52 -0
  130. package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +46 -51
  131. package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +164 -5
  132. package/ios/Tests/BluetoothSDKTests/G2CfwSceneTests.swift +497 -0
  133. package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +108 -44
  134. package/ios/Tests/BluetoothSDKTests/G2CfwTransportTests.swift +259 -0
  135. package/ios/Tests/BluetoothSDKTests/G2DisplayDiagnosticsTests.swift +89 -0
  136. package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +3 -2
  137. package/ios/Tests/BluetoothSDKTests/G2ImageAckTests.swift +104 -0
  138. package/ios/Tests/BluetoothSDKTests/G2KeyboardTests.swift +274 -0
  139. package/ios/Tests/BluetoothSDKTests/G2NotifyFileTests.swift +21 -5
  140. package/ios/Tests/BluetoothSDKTests/G2RingBatteryTests.swift +56 -0
  141. package/ios/Tests/BluetoothSDKTests/G2SensorsTests.swift +56 -0
  142. package/ios/Tests/BluetoothSDKTests/G2TouchInputTests.swift +71 -0
  143. package/ios/Tests/BluetoothSDKTests/G2WritePacingTests.swift +35 -0
  144. package/ios/Tests/BluetoothSDKTests/GlassesNotificationTests.swift +95 -0
  145. package/package.json +1 -1
  146. package/plugin/build/withAndroid.js +2 -2
  147. package/plugin/build/withIos.js +1 -1
  148. package/scripts/gen-nex-proto.sh +12 -0
  149. package/scripts/inject-ios-sdk-version.mjs +1 -1
  150. package/src/BluetoothSdk.types.ts +214 -19
  151. package/src/_internal.ts +1 -1
  152. package/src/_private/BluetoothSdkModule.ts +40 -0
  153. package/src/generated/changelogCatalog.ts +37 -1
  154. package/src/generated/releaseMetadata.ts +4 -4
  155. package/src/index.ts +24 -0
@@ -129,8 +129,8 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
129
129
  canvasElements.firstIndex(where: { $0.key == key })
130
130
  }
131
131
 
132
- private func sendCanvasCommand(_ configure: (inout Glassly_Ble_PhoneToGlasses) -> Void) {
133
- var msg = Glassly_Ble_PhoneToGlasses()
132
+ private func sendCanvasCommand(_ configure: (inout NexPhoneToGlasses) -> Void) {
133
+ var msg = NexPhoneToGlasses()
134
134
  configure(&msg)
135
135
  guard let data = try? msg.serializedData() else { return }
136
136
  queueDataWithOptimalChunking(data, packetType: PACKET_TYPE_PROTOBUF, waitTimeMs: 10)
@@ -735,7 +735,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
735
735
  guard packetData.count > 1 else { return }
736
736
  let payload = packetData.subdata(in: 1 ..< packetData.count)
737
737
  let commandName: String
738
- if let phoneToGlasses = try? Glassly_Ble_PhoneToGlasses(serializedData: payload) {
738
+ if let phoneToGlasses = try? NexPhoneToGlasses(serializedData: payload) {
739
739
  commandName = String(describing: phoneToGlasses.payload)
740
740
  } else {
741
741
  commandName = "UNKNOWN"
@@ -1257,7 +1257,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1257
1257
  // during continuous speech) and each Bridge.log costs the JS thread.
1258
1258
  // Bridge.log("NEX: Displaying text wall: '\(sanitizedText)'")
1259
1259
 
1260
- let displayText = Glassly_Ble_DisplayText.with {
1260
+ let displayText = NexDisplayText.with {
1261
1261
  $0.text = sanitizedText
1262
1262
  $0.size = 48
1263
1263
  $0.x = 20
@@ -1265,7 +1265,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1265
1265
  $0.color = 10000
1266
1266
  }
1267
1267
 
1268
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1268
+ let phoneToGlasses = NexPhoneToGlasses.with {
1269
1269
  $0.displayText = displayText
1270
1270
  }
1271
1271
 
@@ -1354,7 +1354,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1354
1354
 
1355
1355
  Bridge.log("NEX: Displaying scrolling text: '\(text)'")
1356
1356
 
1357
- let displayScrollingText = Glassly_Ble_DisplayScrollingText.with {
1357
+ let displayScrollingText = NexDisplayScrollingText.with {
1358
1358
  $0.text = text
1359
1359
  $0.size = 48
1360
1360
  $0.x = 20
@@ -1368,7 +1368,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1368
1368
  $0.lineSpacing = 2
1369
1369
  }
1370
1370
 
1371
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1371
+ let phoneToGlasses = NexPhoneToGlasses.with {
1372
1372
  $0.displayScrollingText = displayScrollingText
1373
1373
  }
1374
1374
 
@@ -1412,7 +1412,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1412
1412
  let totalChunks = Int(ceil(Double(bmpData.count) / Double(bmpChunkSize)))
1413
1413
 
1414
1414
  // Send display image command first
1415
- let displayImage = Glassly_Ble_DisplayImage.with {
1415
+ let displayImage = NexDisplayImage.with {
1416
1416
  $0.streamID = streamId
1417
1417
  $0.totalChunks = UInt32(totalChunks)
1418
1418
  $0.x = 0
@@ -1422,7 +1422,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1422
1422
  $0.encoding = "raw"
1423
1423
  }
1424
1424
 
1425
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1425
+ let phoneToGlasses = NexPhoneToGlasses.with {
1426
1426
  $0.msgID = "img_start_1"
1427
1427
  $0.displayImage = displayImage
1428
1428
  }
@@ -1615,7 +1615,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1615
1615
  if !canvasElements.isEmpty || currentLayoutId != nil {
1616
1616
  canvasElements.removeAll()
1617
1617
  currentLayoutId = nil
1618
- sendCanvasCommand { $0.canvasClear = Glassly_Ble_CanvasClear() }
1618
+ sendCanvasCommand { $0.canvasClear = NexCanvasClear() }
1619
1619
  }
1620
1620
 
1621
1621
  // Drop any pending/resendable text wall so a stale caption can't
@@ -1626,9 +1626,9 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1626
1626
  textWallResendsRemaining = 0
1627
1627
  textWallLock.unlock()
1628
1628
 
1629
- let clearDisplay = Glassly_Ble_ClearDisplay()
1629
+ let clearDisplay = NexClearDisplay()
1630
1630
 
1631
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1631
+ let phoneToGlasses = NexPhoneToGlasses.with {
1632
1632
  $0.msgID = "clear_disp_001"
1633
1633
  $0.clearDisplay_p = clearDisplay
1634
1634
  }
@@ -1675,11 +1675,11 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1675
1675
  }
1676
1676
  Bridge.log("NEX: Setting brightness to wire value \(validBrightness)")
1677
1677
 
1678
- let brightnessConfig = Glassly_Ble_BrightnessConfig.with {
1678
+ let brightnessConfig = NexBrightnessConfig.with {
1679
1679
  $0.value = UInt32(validBrightness)
1680
1680
  }
1681
1681
 
1682
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1682
+ let phoneToGlasses = NexPhoneToGlasses.with {
1683
1683
  $0.brightness = brightnessConfig
1684
1684
  }
1685
1685
 
@@ -1695,11 +1695,11 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1695
1695
 
1696
1696
  Bridge.log("NEX: Setting auto brightness to \(enabled)")
1697
1697
 
1698
- let autoBrightnessConfig = Glassly_Ble_AutoBrightnessConfig.with {
1698
+ let autoBrightnessConfig = NexAutoBrightnessConfig.with {
1699
1699
  $0.enabled = enabled
1700
1700
  }
1701
1701
 
1702
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1702
+ let phoneToGlasses = NexPhoneToGlasses.with {
1703
1703
  $0.autoBrightness = autoBrightnessConfig
1704
1704
  }
1705
1705
 
@@ -1717,11 +1717,11 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1717
1717
  let validAngle = max(0, min(60, angle))
1718
1718
  Bridge.log("NEX: Setting head-up angle to \(validAngle) degrees")
1719
1719
 
1720
- let headUpAngleConfig = Glassly_Ble_HeadUpAngleConfig.with {
1720
+ let headUpAngleConfig = NexHeadUpAngleConfig.with {
1721
1721
  $0.angle = UInt32(validAngle)
1722
1722
  }
1723
1723
 
1724
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1724
+ let phoneToGlasses = NexPhoneToGlasses.with {
1725
1725
  $0.headUpAngle = headUpAngleConfig
1726
1726
  }
1727
1727
 
@@ -1739,11 +1739,11 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1739
1739
  let validHeight = max(0, min(8, height))
1740
1740
  Bridge.log("NEX: Setting display height to \(validHeight)")
1741
1741
 
1742
- let displayHeightConfig = Glassly_Ble_DisplayHeightConfig.with {
1742
+ let displayHeightConfig = NexDisplayHeightConfig.with {
1743
1743
  $0.height = UInt32(validHeight)
1744
1744
  }
1745
1745
 
1746
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1746
+ let phoneToGlasses = NexPhoneToGlasses.with {
1747
1747
  $0.displayHeight = displayHeightConfig
1748
1748
  }
1749
1749
 
@@ -1760,11 +1760,11 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1760
1760
  let tier = NexDashboardDisplayWire.depthToWireTier(depth)
1761
1761
  Bridge.log("NEX: Setting display distance tier \(tier) in distance_cm field (dashboard depth \(depth))")
1762
1762
 
1763
- let displayDistanceConfig = Glassly_Ble_DisplayDistanceConfig.with {
1763
+ let displayDistanceConfig = NexDisplayDistanceConfig.with {
1764
1764
  $0.distanceCm = tier
1765
1765
  }
1766
1766
 
1767
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1767
+ let phoneToGlasses = NexPhoneToGlasses.with {
1768
1768
  $0.displayDistance = displayDistanceConfig
1769
1769
  }
1770
1770
 
@@ -1782,11 +1782,11 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1782
1782
  return
1783
1783
  }
1784
1784
 
1785
- let vadConfig = Glassly_Ble_VadEnabledConfig.with {
1785
+ let vadConfig = NexVadEnabledConfig.with {
1786
1786
  $0.enabled = enabled
1787
1787
  }
1788
1788
 
1789
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1789
+ let phoneToGlasses = NexPhoneToGlasses.with {
1790
1790
  $0.vadEnabled = vadConfig
1791
1791
  }
1792
1792
 
@@ -1806,11 +1806,11 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1806
1806
 
1807
1807
  Bridge.log("NEX: Setting microphone enabled: \(enabled)")
1808
1808
 
1809
- let micStateConfig = Glassly_Ble_MicStateConfig.with {
1809
+ let micStateConfig = NexMicStateConfig.with {
1810
1810
  $0.enabled = enabled
1811
1811
  }
1812
1812
 
1813
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1813
+ let phoneToGlasses = NexPhoneToGlasses.with {
1814
1814
  $0.micState = micStateConfig
1815
1815
  }
1816
1816
 
@@ -1844,9 +1844,9 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1844
1844
 
1845
1845
  Bridge.log("NEX: Querying battery status")
1846
1846
 
1847
- let batteryStateRequest = Glassly_Ble_BatteryStateRequest()
1847
+ let batteryStateRequest = NexBatteryStateRequest()
1848
1848
 
1849
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1849
+ let phoneToGlasses = NexPhoneToGlasses.with {
1850
1850
  $0.batteryState = batteryStateRequest
1851
1851
  }
1852
1852
 
@@ -1862,9 +1862,9 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1862
1862
 
1863
1863
  Bridge.log("NEX: Querying glasses information")
1864
1864
 
1865
- let glassesInfoRequest = Glassly_Ble_GlassesInfoRequest()
1865
+ let glassesInfoRequest = NexGlassesInfoRequest()
1866
1866
 
1867
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
1867
+ let phoneToGlasses = NexPhoneToGlasses.with {
1868
1868
  $0.glassesInfo = glassesInfoRequest
1869
1869
  }
1870
1870
 
@@ -1978,7 +1978,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
1978
1978
 
1979
1979
  private func processProtobufData(_ protobufData: Data) {
1980
1980
  do {
1981
- let glassesToPhone = try Glassly_Ble_GlassesToPhone(serializedData: protobufData)
1981
+ let glassesToPhone = try NexGlassesToPhone(serializedData: protobufData)
1982
1982
  // No per-message log: String(describing: payload) stringifies the whole
1983
1983
  // protobuf and every Bridge.log costs the JS thread.
1984
1984
  // Bridge.log("NEX: Processing protobuf payload case: \(glassesToPhone.payload)")
@@ -2067,7 +2067,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2067
2067
 
2068
2068
  // MARK: - Protobuf Event Handlers
2069
2069
 
2070
- private func handleBatteryStatusProtobuf(_ batteryStatus: Glassly_Ble_BatteryStatus) {
2070
+ private func handleBatteryStatusProtobuf(_ batteryStatus: NexBatteryStatus) {
2071
2071
  let level = Int(batteryStatus.level)
2072
2072
  let isCharging = batteryStatus.charging
2073
2073
 
@@ -2078,7 +2078,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2078
2078
  DeviceStore.shared.apply("glasses", "charging", isCharging)
2079
2079
  }
2080
2080
 
2081
- private func handleChargingStateProtobuf(_ chargingState: Glassly_Ble_ChargingState) {
2081
+ private func handleChargingStateProtobuf(_ chargingState: NexChargingState) {
2082
2082
  let chargingState = chargingState.state == .charging
2083
2083
 
2084
2084
  Bridge.log("NEX: 🔌 Charging State: \(chargingState ? "CHARGING" : "NOT_CHARGING")")
@@ -2087,7 +2087,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2087
2087
  DeviceStore.shared.apply("glasses", "charging", chargingState)
2088
2088
  }
2089
2089
 
2090
- private func handleDeviceInfoProtobuf(_ deviceInfo: Glassly_Ble_DeviceInfo) {
2090
+ private func handleDeviceInfoProtobuf(_ deviceInfo: NexDeviceInfo) {
2091
2091
  Bridge.log("NEX: 📱 Device Info: \(deviceInfo)")
2092
2092
 
2093
2093
  // Update @Published properties (G1-compatible approach)
@@ -2095,7 +2095,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2095
2095
  DeviceStore.shared.apply("glasses", "deviceHardwareModel", deviceInfo.hwModel)
2096
2096
  }
2097
2097
 
2098
- private func handleHeadPositionProtobuf(_ headPosition: Glassly_Ble_HeadPosition) {
2098
+ private func handleHeadPositionProtobuf(_ headPosition: NexHeadPosition) {
2099
2099
  let angle = Int(headPosition.angle)
2100
2100
 
2101
2101
  Bridge.log("NEX: 📐 Head Position - Angle: \(angle)°")
@@ -2104,7 +2104,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2104
2104
  headUpAngle = angle
2105
2105
  }
2106
2106
 
2107
- private func handleHeadUpAngleResponseProtobuf(_ response: Glassly_Ble_HeadUpAngleResponse) {
2107
+ private func handleHeadUpAngleResponseProtobuf(_ response: NexHeadUpAngleResponse) {
2108
2108
  let success = response.success
2109
2109
 
2110
2110
  Bridge.log("NEX: 📐 Head Up Angle Set Response - Success: \(success)")
@@ -2119,7 +2119,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2119
2119
  emitEvent("HeadUpAngleResponseEvent", body: eventBody)
2120
2120
  }
2121
2121
 
2122
- private func handleVadEventProtobuf(_ vadEvent: Glassly_Ble_VadEvent) {
2122
+ private func handleVadEventProtobuf(_ vadEvent: NexVadEvent) {
2123
2123
  let vadActiveState = vadEvent.state == .active
2124
2124
 
2125
2125
  Bridge.log("NEX: 🎤 VAD Event - Voice Activity: \(vadActiveState)")
@@ -2130,7 +2130,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2130
2130
  }
2131
2131
 
2132
2132
  private func handleImageTransferCompleteProtobuf(
2133
- _ transferComplete: Glassly_Ble_ImageTransferComplete
2133
+ _ transferComplete: NexImageTransferComplete
2134
2134
  ) {
2135
2135
  let status = transferComplete.status
2136
2136
  let missingChunks = transferComplete.missingChunks
@@ -2178,7 +2178,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2178
2178
  }
2179
2179
  }
2180
2180
 
2181
- private func handleImuDataProtobuf(_ imuData: Glassly_Ble_ImuData) {
2181
+ private func handleImuDataProtobuf(_ imuData: NexImuData) {
2182
2182
  Bridge.log("NEX: 📊 IMU Data: \(imuData)")
2183
2183
 
2184
2184
  // Update @Published properties (G1-compatible approach)
@@ -2187,7 +2187,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2187
2187
  magnetometer = [imuData.mag.x, imuData.mag.y, imuData.mag.z]
2188
2188
  }
2189
2189
 
2190
- private func handleButtonEventProtobuf(_ buttonEvent: Glassly_Ble_ButtonEvent) {
2190
+ private func handleButtonEventProtobuf(_ buttonEvent: NexButtonEvent) {
2191
2191
  let buttonNumber = Int(buttonEvent.button.rawValue)
2192
2192
  let buttonState = buttonEvent.state
2193
2193
 
@@ -2198,7 +2198,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2198
2198
  lastButtonState = "\(buttonState.rawValue)"
2199
2199
  }
2200
2200
 
2201
- private func handleHeadGestureProtobuf(_ headGesture: Glassly_Ble_HeadGesture) {
2201
+ private func handleHeadGestureProtobuf(_ headGesture: NexHeadGesture) {
2202
2202
  let gestureType = headGesture.gesture
2203
2203
 
2204
2204
  Bridge.log("NEX: 👤 Head Gesture: \(gestureType)")
@@ -2450,7 +2450,7 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2450
2450
  let eventBody: [String: Any] = [
2451
2451
  "protobuf_schema_version": [
2452
2452
  "schema_version": 1, // Default version
2453
- "build_info": "Schema v1 | glassly_ble.proto",
2453
+ "build_info": "Schema v1 | nex_ble.proto",
2454
2454
  "device_model": "Mentra Display",
2455
2455
  ],
2456
2456
  ]
@@ -2487,8 +2487,8 @@ class MentraNexSGC: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SG
2487
2487
  }
2488
2488
 
2489
2489
  private func sendDisconnectRequest() {
2490
- let phoneToGlasses = Glassly_Ble_PhoneToGlasses.with {
2491
- $0.disconnect = Glassly_Ble_DisconnectRequest()
2490
+ let phoneToGlasses = NexPhoneToGlasses.with {
2491
+ $0.disconnect = NexDisconnectRequest()
2492
2492
  }
2493
2493
  guard let protobufData = try? phoneToGlasses.serializedData() else {
2494
2494
  Bridge.log("NEX: ⚠️ Failed to serialize DisconnectRequest")
@@ -5,7 +5,7 @@ import Foundation
5
5
  /// against the previous frame sent to this device.
6
6
  struct SceneElement: Sendable {
7
7
  let id: String
8
- let type: String // "text" | "rect" | "image"
8
+ let type: String // "text" | "rect" | "image" | "circle" | "arc" | "pie" | "line" | "triangle" | "quad" | "bezier"
9
9
  let x: Int32
10
10
  let y: Int32
11
11
  let w: Int32
@@ -20,11 +20,35 @@ struct SceneElement: Sendable {
20
20
  /// height (grid text); anything else is the default proportional face.
21
21
  let font: String
22
22
  let fontSize: Int32
23
+ /// Shapes: solid fill instead of an outline.
24
+ let fill: Bool
25
+ /// Gray level 0..15, or -1 for the default (brightest).
26
+ let color: Int32
27
+ /// Point shapes: x0, y0, x1, y1, … in canvas pixels.
28
+ let points: [Int32]
29
+ let startDeg: Int32
30
+ let endDeg: Int32
31
+ /// Transition duration in ms (0 = none) and easing name.
32
+ let transitionMs: Int32
33
+ let easing: String
34
+ let path: [UInt8]
35
+ let pathScale: Int32
36
+ let fillRule: Int32
37
+ let rotationQ8: Int32
38
+ let pivotX: Int32?
39
+ let pivotY: Int32?
40
+ /// Stereo disparity in canvas px: drawn at x+depth on the left lens and
41
+ /// x-depth on the right, so positive values float in front of the frame.
42
+ let depth: Int32
23
43
 
24
44
  init(
25
45
  id: String, type: String, x: Int32, y: Int32, w: Int32, h: Int32, text: String?, data: String?,
26
46
  border: Int32, radius: Int32, change: String, contentHash: String,
27
- font: String = "default", fontSize: Int32 = 0
47
+ font: String = "default", fontSize: Int32 = 0,
48
+ fill: Bool = false, color: Int32 = -1, points: [Int32] = [], startDeg: Int32 = 0, endDeg: Int32 = 0,
49
+ transitionMs: Int32 = 0, easing: String = "ease",
50
+ path: [UInt8] = [], pathScale: Int32 = 256, fillRule: Int32 = 0,
51
+ rotationQ8: Int32 = 0, pivotX: Int32? = nil, pivotY: Int32? = nil, depth: Int32 = 0
28
52
  ) {
29
53
  self.id = id
30
54
  self.type = type
@@ -40,15 +64,45 @@ struct SceneElement: Sendable {
40
64
  self.contentHash = contentHash
41
65
  self.font = font
42
66
  self.fontSize = fontSize
67
+ self.fill = fill
68
+ self.color = color
69
+ self.points = points
70
+ self.startDeg = startDeg
71
+ self.endDeg = endDeg
72
+ self.transitionMs = transitionMs
73
+ self.easing = easing
74
+ self.path = path
75
+ self.pathScale = pathScale
76
+ self.fillRule = fillRule
77
+ self.rotationQ8 = rotationQ8
78
+ self.pivotX = pivotX
79
+ self.pivotY = pivotY
80
+ self.depth = depth
43
81
  }
44
82
 
45
83
  func with(change: String) -> SceneElement {
46
84
  SceneElement(
47
85
  id: id, type: type, x: x, y: y, w: w, h: h, text: text, data: data,
48
86
  border: border, radius: radius, change: change, contentHash: contentHash,
49
- font: font, fontSize: fontSize
87
+ font: font, fontSize: fontSize, fill: fill, color: color, points: points,
88
+ startDeg: startDeg, endDeg: endDeg, transitionMs: transitionMs, easing: easing,
89
+ path: path, pathScale: pathScale, fillRule: fillRule, rotationQ8: rotationQ8, pivotX: pivotX, pivotY: pivotY,
90
+ depth: depth
50
91
  )
51
92
  }
93
+
94
+ /// The same element placed at the canvas origin (for standalone rasterization).
95
+ func atOrigin() -> SceneElement {
96
+ SceneElement(
97
+ id: id, type: type, x: 0, y: 0, w: w, h: h, text: text, data: data,
98
+ border: border, radius: radius, change: change, contentHash: contentHash,
99
+ font: font, fontSize: fontSize, fill: fill, color: color, points: points,
100
+ startDeg: startDeg, endDeg: endDeg, transitionMs: transitionMs, easing: easing,
101
+ path: path, pathScale: pathScale, fillRule: fillRule, rotationQ8: rotationQ8, pivotX: pivotX, pivotY: pivotY,
102
+ depth: depth
103
+ )
104
+ }
105
+
52
106
  }
53
107
 
54
108
  /// A whole scene from the host pipeline — the full frame plus per-element
@@ -67,11 +121,15 @@ struct SceneFrame: Sendable {
67
121
  let removed: [String]
68
122
  let canvasWidth: Int32
69
123
  let canvasHeight: Int32
124
+ /// Host frame number, reported back in `scene_frame_presented` once the
125
+ /// device has the frame. 0 = native-originated (legacy sugar), never reported.
126
+ let seq: Int
70
127
 
71
128
  init(
72
129
  appId: String, epoch: Int, replay: Bool, elements: [SceneElement], removed: [String],
73
130
  canvasWidth: Int32 = SceneFrame.defaultCanvasWidth,
74
- canvasHeight: Int32 = SceneFrame.defaultCanvasHeight
131
+ canvasHeight: Int32 = SceneFrame.defaultCanvasHeight,
132
+ seq: Int = 0
75
133
  ) {
76
134
  self.appId = appId
77
135
  self.epoch = epoch
@@ -80,15 +138,35 @@ struct SceneFrame: Sendable {
80
138
  self.removed = removed
81
139
  self.canvasWidth = canvasWidth > 0 ? canvasWidth : SceneFrame.defaultCanvasWidth
82
140
  self.canvasHeight = canvasHeight > 0 ? canvasHeight : SceneFrame.defaultCanvasHeight
141
+ self.seq = seq
83
142
  }
84
143
 
85
144
  func asReplay() -> SceneFrame {
86
145
  SceneFrame(
87
146
  appId: appId, epoch: epoch, replay: true,
88
147
  elements: elements.map { $0.with(change: "created") }, removed: removed,
89
- canvasWidth: canvasWidth, canvasHeight: canvasHeight
148
+ canvasWidth: canvasWidth, canvasHeight: canvasHeight, seq: seq
90
149
  )
91
150
  }
151
+
152
+ /// Tell the host this frame's content is on the glasses (or why it isn't).
153
+ func reportPresented(_ presented: Bool, reason: String? = nil) {
154
+ presentTag?.report(presented, reason: reason)
155
+ }
156
+
157
+ /// What a queued transfer must report once it lands; nil for native-originated frames.
158
+ var presentTag: PresentTag? {
159
+ seq > 0 ? PresentTag(seq: seq, appId: appId) : nil
160
+ }
161
+
162
+ struct PresentTag: Sendable {
163
+ let seq: Int
164
+ let appId: String
165
+
166
+ func report(_ presented: Bool, reason: String? = nil) {
167
+ Bridge.sendSceneFramePresented(seq: seq, appId: appId, presented: presented, reason: reason)
168
+ }
169
+ }
92
170
  }
93
171
 
94
172
  @MainActor
@@ -187,6 +265,9 @@ protocol SGCManager {
187
265
  /// True while the glasses' own modal nav UI owns the display — DeviceManager
188
266
  /// suppresses normal display traffic until exit.
189
267
  var navigationModeActive: Bool { get }
268
+ /// True when the driver renders scenes itself with link-driven last-wins
269
+ /// coalescing; DeviceManager then skips the display pacer.
270
+ var supportsCfwSceneRendering: Bool { get }
190
271
  func enterNavigationMode()
191
272
  /// `arrived` shows the device's "navigation complete" screen before exiting.
192
273
  func exitNavigationMode(arrived: Bool)
@@ -208,6 +289,9 @@ protocol SGCManager {
208
289
  /// Live cfw_scene_renderer switch (only the G2 CFW has one): swap the
209
290
  /// on-glass surface between the CFW carrier and a blank stock page.
210
291
  func cfwSceneRendererToggled(_ enabled: Bool) async
292
+ /// Live cfw_fast_ble switch (G2 Glassly CFW revision 26+): ask the lenses
293
+ /// for the 7.5 ms fast connection profile, or return them to stock.
294
+ func cfwFastBleToggled(_ enabled: Bool) async
211
295
 
212
296
  // MARK: - Dashboard Menu
213
297
 
@@ -221,12 +305,13 @@ protocol SGCManager {
221
305
 
222
306
  func sendCalendarEvents(_ events: [[String: Any]])
223
307
 
224
- // MARK: - Phone Notifications (on-glass ANCS display options + app filter)
308
+ // MARK: - Phone Notifications
225
309
 
226
- /// Push NotificationControl from the DeviceStore `notification_ctrl` dict.
227
- func sendNotificationCtrl()
228
- /// Push the ANCS app allowlist from the DeviceStore `notification_filter` dict.
229
- func sendNotificationFilter()
310
+ /// Apply glasses-neutral notification behaviour to the connected firmware.
311
+ func setGlassesNotificationConfig(_ config: GlassesNotificationConfig) throws
312
+ func glassesNotificationStatus() -> GlassesNotificationStatus
313
+ /// Hand one notification to glasses that present content themselves.
314
+ func uploadPhoneNotification(_ upload: PhoneNotificationUpload) throws
230
315
 
231
316
  // MARK: - Dashboard Display Settings
232
317
 
@@ -262,6 +347,8 @@ protocol SGCManager {
262
347
  func ping()
263
348
  func dbg1()
264
349
  func dbg2()
350
+ func dbg3()
351
+ func dbg4()
265
352
 
266
353
  // MARK: - Network Management
267
354
 
@@ -310,6 +397,10 @@ extension SGCManager {
310
397
  /// Default: no-op. Only G2 distinguishes stock from Glassly CFW firmware.
311
398
  func awaitFirmwareClassification() async {}
312
399
 
400
+ /// Default: no-op debug hooks; only G2 wires them.
401
+ func dbg3() {}
402
+ func dbg4() {}
403
+
313
404
  /// Default: no-op. Only G2 renders positioned text containers; other glasses ignore it.
314
405
  func sendPositionedText(
315
406
  _: String, x _: Int32, y _: Int32, width _: Int32, height _: Int32,
@@ -404,6 +495,8 @@ extension SGCManager {
404
495
  for id in frame.removed where !paintedIds.contains(id) {
405
496
  await removeLayoutElement(id, layoutId: frame.appId)
406
497
  }
498
+ // No device ack on this path: the awaited verbs above cover the sends.
499
+ frame.reportPresented(true)
407
500
  }
408
501
 
409
502
  // MARK: - Video recording (default: ignore custom settings, use saved defaults)
@@ -437,6 +530,12 @@ extension SGCManager {
437
530
 
438
531
  func cfwSceneRendererToggled(_: Bool) async {}
439
532
 
533
+ func cfwFastBleToggled(_: Bool) async {}
534
+
535
+ var supportsCfwSceneRendering: Bool {
536
+ false
537
+ }
538
+
440
539
  // MARK: - Native navigation defaults (only G2 has a glasses-side nav UI)
441
540
 
442
541
  var navigationModeActive: Bool {
@@ -476,11 +575,19 @@ extension SGCManager {
476
575
 
477
576
  func sendCalendarEvents(_: [[String: Any]]) {}
478
577
 
479
- // MARK: - Phone Notifications (default no-op — only G2 filters ANCS on-glass)
578
+ // MARK: - Phone Notifications (default unsupported — only G2 has a notification surface)
480
579
 
481
- func sendNotificationCtrl() {}
580
+ func setGlassesNotificationConfig(_: GlassesNotificationConfig) throws {
581
+ throw GlassesNotificationError.unsupported
582
+ }
482
583
 
483
- func sendNotificationFilter() {}
584
+ func glassesNotificationStatus() -> GlassesNotificationStatus {
585
+ .unsupported
586
+ }
587
+
588
+ func uploadPhoneNotification(_: PhoneNotificationUpload) throws {
589
+ throw GlassesNotificationError.unsupported
590
+ }
484
591
 
485
592
  // MARK: - Dashboard Display Settings (default no-op — only G2 supports this)
486
593