@glassly/bluetooth-sdk 0.1.0-dev.26

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 (548) hide show
  1. package/README.md +563 -0
  2. package/android/build.gradle +450 -0
  3. package/android/gradle.properties.example +9 -0
  4. package/android/lc3Lib/CMakeLists.txt +6 -0
  5. package/android/lc3Lib/build.gradle +212 -0
  6. package/android/lc3Lib/proguard-rules.pro +33 -0
  7. package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
  8. package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
  9. package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
  10. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
  11. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
  12. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
  13. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
  14. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
  15. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
  16. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
  17. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
  18. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
  19. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
  20. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
  21. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
  22. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
  23. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
  24. package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
  25. package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
  26. package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
  27. package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
  28. package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
  29. package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
  30. package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
  31. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
  32. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
  33. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
  34. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
  35. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
  36. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
  37. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
  38. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
  39. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
  40. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
  41. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
  42. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
  43. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
  44. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
  45. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
  46. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
  47. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
  48. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
  49. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
  50. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
  51. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
  52. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
  53. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
  54. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
  55. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
  56. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
  57. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
  58. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
  59. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
  60. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
  61. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
  62. package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
  63. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
  64. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
  65. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
  66. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
  67. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
  68. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
  69. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
  70. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
  71. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
  72. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
  73. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
  74. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
  75. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
  76. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
  77. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
  78. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
  79. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
  80. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
  81. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
  82. package/android/lc3Lib/src/main/java/com/glassly/lc3Lib/Lc3Cpp.java +38 -0
  83. package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
  84. package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
  85. package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
  86. package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
  87. package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
  88. package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
  89. package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
  90. package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
  91. package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
  92. package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
  93. package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
  94. package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
  95. package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
  96. package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
  97. package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
  98. package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
  99. package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
  100. package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
  101. package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
  102. package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
  103. package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
  104. package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
  105. package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
  106. package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
  107. package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
  108. package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
  109. package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
  110. package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
  111. package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
  112. package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
  113. package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
  114. package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
  115. package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
  116. package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
  117. package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
  118. package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
  119. package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
  120. package/android/settings.gradle +4 -0
  121. package/android/silero/LICENSE.md +17 -0
  122. package/android/silero/build.gradle +50 -0
  123. package/android/silero/proguard-rules.pro +24 -0
  124. package/android/silero/src/androidTest/assets/hello.wav +0 -0
  125. package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
  126. package/android/silero/src/main/assets/LICENSE +21 -0
  127. package/android/silero/src/main/assets/silero_vad.onnx +0 -0
  128. package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
  129. package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
  130. package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
  131. package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
  132. package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
  133. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
  134. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
  135. package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
  136. package/android/src/main/AndroidManifest.xml +42 -0
  137. package/android/src/main/assets/G2Firmware/README.md +11 -0
  138. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +2325 -0
  139. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkAnalytics.kt +216 -0
  140. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDebug.kt +14 -0
  141. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDefaults.kt +7 -0
  142. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +1088 -0
  143. package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +953 -0
  144. package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +2528 -0
  145. package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +360 -0
  146. package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +8 -0
  147. package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
  148. package/android/src/main/java/com/glassly/bluetoothsdk/LocalNetworkModule.kt +414 -0
  149. package/android/src/main/java/com/glassly/bluetoothsdk/ObservableStore.kt +88 -0
  150. package/android/src/main/java/com/glassly/bluetoothsdk/OtaManifest.kt +191 -0
  151. package/android/src/main/java/com/glassly/bluetoothsdk/ReleaseChangelog.kt +61 -0
  152. package/android/src/main/java/com/glassly/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
  153. package/android/src/main/java/com/glassly/bluetoothsdk/audio/AudioModels.kt +144 -0
  154. package/android/src/main/java/com/glassly/bluetoothsdk/camera/CameraModels.kt +486 -0
  155. package/android/src/main/java/com/glassly/bluetoothsdk/connection/ConnectionModels.kt +78 -0
  156. package/android/src/main/java/com/glassly/bluetoothsdk/controllers/ControllerManager.kt +158 -0
  157. package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +937 -0
  158. package/android/src/main/java/com/glassly/bluetoothsdk/debug/BleTraceLogger.kt +165 -0
  159. package/android/src/main/java/com/glassly/bluetoothsdk/events/BluetoothEvents.kt +178 -0
  160. package/android/src/main/java/com/glassly/bluetoothsdk/internal/MapParsing.kt +140 -0
  161. package/android/src/main/java/com/glassly/bluetoothsdk/net/LocalIpv4.kt +57 -0
  162. package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
  163. package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/OtaServerModule.kt +120 -0
  164. package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
  165. package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +622 -0
  166. package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/PhotoReceiverModule.kt +156 -0
  167. package/android/src/main/java/com/glassly/bluetoothsdk/requests/DisplayRequests.kt +56 -0
  168. package/android/src/main/java/com/glassly/bluetoothsdk/services/Foreground.kt +272 -0
  169. package/android/src/main/java/com/glassly/bluetoothsdk/services/PhoneMic.kt +1195 -0
  170. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Ar99.java +2653 -0
  171. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
  172. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
  173. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1.kt +3878 -0
  174. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
  175. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +6428 -0
  176. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +49 -0
  177. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +1241 -0
  178. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +270 -0
  179. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +291 -0
  180. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +411 -0
  181. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/GlasslyBle.java +43189 -0
  182. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Mach1.kt +1280 -0
  183. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +10817 -0
  184. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
  185. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
  186. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +2011 -0
  187. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Nimo.kt +2532 -0
  188. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +470 -0
  189. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Simulated.kt +228 -0
  190. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
  191. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
  192. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
  193. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
  194. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
  195. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
  196. package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +525 -0
  197. package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +669 -0
  198. package/android/src/main/java/com/glassly/bluetoothsdk/status/RuntimeState.kt +201 -0
  199. package/android/src/main/java/com/glassly/bluetoothsdk/status/WifiHotspotStatus.kt +189 -0
  200. package/android/src/main/java/com/glassly/bluetoothsdk/streaming/StreamModels.kt +518 -0
  201. package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +391 -0
  202. package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +518 -0
  203. package/android/src/main/java/com/glassly/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
  204. package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +248 -0
  205. package/android/src/main/java/com/glassly/bluetoothsdk/types/DeviceModels.kt +96 -0
  206. package/android/src/main/java/com/glassly/bluetoothsdk/utils/AES.java +106 -0
  207. package/android/src/main/java/com/glassly/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
  208. package/android/src/main/java/com/glassly/bluetoothsdk/utils/AvifExifStripper.java +573 -0
  209. package/android/src/main/java/com/glassly/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
  210. package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleJsonCompact.java +496 -0
  211. package/android/src/main/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadService.java +677 -0
  212. package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleWireProtocol.java +108 -0
  213. package/android/src/main/java/com/glassly/bluetoothsdk/utils/Constants.kt +46 -0
  214. package/android/src/main/java/com/glassly/bluetoothsdk/utils/G1Text.kt +436 -0
  215. package/android/src/main/java/com/glassly/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
  216. package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
  217. package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleUploadService.java +77 -0
  218. package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900LengthCodec.java +115 -0
  219. package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtils.java +827 -0
  220. package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunkReassembler.java +228 -0
  221. package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunker.java +325 -0
  222. package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +698 -0
  223. package/android/src/main/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitor.kt +348 -0
  224. package/android/src/main/java/com/glassly/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
  225. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
  226. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
  227. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
  228. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
  229. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
  230. package/android/src/main/res/values/strings.xml +4 -0
  231. package/android/src/test/java/com/glassly/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
  232. package/android/src/test/java/com/glassly/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
  233. package/android/src/test/java/com/glassly/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
  234. package/android/src/test/java/com/glassly/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
  235. package/android/src/test/java/com/glassly/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
  236. package/android/src/test/java/com/glassly/bluetoothsdk/StreamStatusEventTest.kt +29 -0
  237. package/android/src/test/java/com/glassly/bluetoothsdk/camera/PhotoRequestTest.kt +141 -0
  238. package/android/src/test/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
  239. package/android/src/test/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
  240. package/android/src/test/java/com/glassly/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
  241. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
  242. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
  243. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
  244. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/EvenRleTest.kt +63 -0
  245. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
  246. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +89 -0
  247. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +344 -0
  248. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +200 -0
  249. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocolTest.kt +131 -0
  250. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
  251. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
  252. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
  253. package/android/src/test/java/com/glassly/bluetoothsdk/utils/AvifExifStripperTest.java +46 -0
  254. package/android/src/test/java/com/glassly/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
  255. package/android/src/test/java/com/glassly/bluetoothsdk/utils/BleJsonCompactTest.java +319 -0
  256. package/android/src/test/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +107 -0
  257. package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
  258. package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
  259. package/android/src/test/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
  260. package/android/src/test/resources/avif_with_exif.avif +0 -0
  261. package/app.plugin.js +3 -0
  262. package/build/BluetoothSdk.types.d.ts +1354 -0
  263. package/build/BluetoothSdk.types.d.ts.map +1 -0
  264. package/build/BluetoothSdk.types.js +41 -0
  265. package/build/BluetoothSdk.types.js.map +1 -0
  266. package/build/_internal.d.ts +17 -0
  267. package/build/_internal.d.ts.map +1 -0
  268. package/build/_internal.js +16 -0
  269. package/build/_internal.js.map +1 -0
  270. package/build/_private/BluetoothSdkModule.d.ts +172 -0
  271. package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
  272. package/build/_private/BluetoothSdkModule.js +300 -0
  273. package/build/_private/BluetoothSdkModule.js.map +1 -0
  274. package/build/_private/LocalNetworkModule.d.ts +38 -0
  275. package/build/_private/LocalNetworkModule.d.ts.map +1 -0
  276. package/build/_private/LocalNetworkModule.js +3 -0
  277. package/build/_private/LocalNetworkModule.js.map +1 -0
  278. package/build/_private/cameraRequestPayload.d.ts +4 -0
  279. package/build/_private/cameraRequestPayload.d.ts.map +1 -0
  280. package/build/_private/cameraRequestPayload.js +32 -0
  281. package/build/_private/cameraRequestPayload.js.map +1 -0
  282. package/build/_private/photoRequestPayload.d.ts +6 -0
  283. package/build/_private/photoRequestPayload.d.ts.map +1 -0
  284. package/build/_private/photoRequestPayload.js +86 -0
  285. package/build/_private/photoRequestPayload.js.map +1 -0
  286. package/build/_private/sdkOtaManifest.d.ts +11 -0
  287. package/build/_private/sdkOtaManifest.d.ts.map +1 -0
  288. package/build/_private/sdkOtaManifest.js +15 -0
  289. package/build/_private/sdkOtaManifest.js.map +1 -0
  290. package/build/changelogs.d.ts +7 -0
  291. package/build/changelogs.d.ts.map +1 -0
  292. package/build/changelogs.js +46 -0
  293. package/build/changelogs.js.map +1 -0
  294. package/build/debug.d.ts +3 -0
  295. package/build/debug.d.ts.map +1 -0
  296. package/build/debug.js +8 -0
  297. package/build/debug.js.map +1 -0
  298. package/build/generated/changelogCatalog.d.ts +9 -0
  299. package/build/generated/changelogCatalog.d.ts.map +1 -0
  300. package/build/generated/changelogCatalog.js +12 -0
  301. package/build/generated/changelogCatalog.js.map +1 -0
  302. package/build/generated/releaseMetadata.d.ts +12 -0
  303. package/build/generated/releaseMetadata.d.ts.map +1 -0
  304. package/build/generated/releaseMetadata.js +10 -0
  305. package/build/generated/releaseMetadata.js.map +1 -0
  306. package/build/index.d.ts +6 -0
  307. package/build/index.d.ts.map +1 -0
  308. package/build/index.js +171 -0
  309. package/build/index.js.map +1 -0
  310. package/build/ota-server/OtaServer.types.d.ts +14 -0
  311. package/build/ota-server/OtaServer.types.d.ts.map +1 -0
  312. package/build/ota-server/OtaServer.types.js +2 -0
  313. package/build/ota-server/OtaServer.types.js.map +1 -0
  314. package/build/ota-server/OtaServerModule.d.ts +21 -0
  315. package/build/ota-server/OtaServerModule.d.ts.map +1 -0
  316. package/build/ota-server/OtaServerModule.js +3 -0
  317. package/build/ota-server/OtaServerModule.js.map +1 -0
  318. package/build/ota-server/OtaServerModule.web.d.ts +10 -0
  319. package/build/ota-server/OtaServerModule.web.d.ts.map +1 -0
  320. package/build/ota-server/OtaServerModule.web.js +12 -0
  321. package/build/ota-server/OtaServerModule.web.js.map +1 -0
  322. package/build/ota-server/index.d.ts +3 -0
  323. package/build/ota-server/index.d.ts.map +1 -0
  324. package/build/ota-server/index.js +3 -0
  325. package/build/ota-server/index.js.map +1 -0
  326. package/build/ota-transport/index.d.ts +67 -0
  327. package/build/ota-transport/index.d.ts.map +1 -0
  328. package/build/ota-transport/index.js +58 -0
  329. package/build/ota-transport/index.js.map +1 -0
  330. package/build/photo-receiver/PhotoReceiver.types.d.ts +18 -0
  331. package/build/photo-receiver/PhotoReceiver.types.d.ts.map +1 -0
  332. package/build/photo-receiver/PhotoReceiver.types.js +2 -0
  333. package/build/photo-receiver/PhotoReceiver.types.js.map +1 -0
  334. package/build/photo-receiver/PhotoReceiverModule.d.ts +10 -0
  335. package/build/photo-receiver/PhotoReceiverModule.d.ts.map +1 -0
  336. package/build/photo-receiver/PhotoReceiverModule.js +3 -0
  337. package/build/photo-receiver/PhotoReceiverModule.js.map +1 -0
  338. package/build/photo-receiver/PhotoReceiverModule.web.d.ts +10 -0
  339. package/build/photo-receiver/PhotoReceiverModule.web.d.ts.map +1 -0
  340. package/build/photo-receiver/PhotoReceiverModule.web.js +12 -0
  341. package/build/photo-receiver/PhotoReceiverModule.web.js.map +1 -0
  342. package/build/photo-receiver/index.d.ts +3 -0
  343. package/build/photo-receiver/index.d.ts.map +1 -0
  344. package/build/photo-receiver/index.js +3 -0
  345. package/build/photo-receiver/index.js.map +1 -0
  346. package/build/react/index.d.ts +5 -0
  347. package/build/react/index.d.ts.map +1 -0
  348. package/build/react/index.js +4 -0
  349. package/build/react/index.js.map +1 -0
  350. package/build/react/useBluetooth.d.ts +100 -0
  351. package/build/react/useBluetooth.d.ts.map +1 -0
  352. package/build/react/useBluetooth.js +167 -0
  353. package/build/react/useBluetooth.js.map +1 -0
  354. package/build/react/useBluetoothEvent.d.ts +6 -0
  355. package/build/react/useBluetoothEvent.d.ts.map +1 -0
  356. package/build/react/useBluetoothEvent.js +21 -0
  357. package/build/react/useBluetoothEvent.js.map +1 -0
  358. package/build/react/useBluetoothScan.d.ts +22 -0
  359. package/build/react/useBluetoothScan.d.ts.map +1 -0
  360. package/build/react/useBluetoothScan.js +135 -0
  361. package/build/react/useBluetoothScan.js.map +1 -0
  362. package/build/react/useBluetoothStatus.d.ts +16 -0
  363. package/build/react/useBluetoothStatus.d.ts.map +1 -0
  364. package/build/react/useBluetoothStatus.js +137 -0
  365. package/build/react/useBluetoothStatus.js.map +1 -0
  366. package/build/react/useGlassesConnection.d.ts +29 -0
  367. package/build/react/useGlassesConnection.d.ts.map +1 -0
  368. package/build/react/useGlassesConnection.js +153 -0
  369. package/build/react/useGlassesConnection.js.map +1 -0
  370. package/build/types/index.d.ts +3 -0
  371. package/build/types/index.d.ts.map +1 -0
  372. package/build/types/index.js +2 -0
  373. package/build/types/index.js.map +1 -0
  374. package/expo-module.config.json +14 -0
  375. package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
  376. package/ios/BluetoothSdkModule.swift +1034 -0
  377. package/ios/GlasslyBluetoothSDK.podspec +106 -0
  378. package/ios/LocalIPv4.swift +150 -0
  379. package/ios/LocalOtaServer.swift +309 -0
  380. package/ios/LocalPhotoUploadServer.swift +754 -0
  381. package/ios/OtaServerModule.swift +147 -0
  382. package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
  383. package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
  384. package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
  385. package/ios/Packages/CoreObjC/attdet.c +92 -0
  386. package/ios/Packages/CoreObjC/attdet.h +44 -0
  387. package/ios/Packages/CoreObjC/bits.c +375 -0
  388. package/ios/Packages/CoreObjC/bits.h +315 -0
  389. package/ios/Packages/CoreObjC/bwdet.c +129 -0
  390. package/ios/Packages/CoreObjC/bwdet.h +69 -0
  391. package/ios/Packages/CoreObjC/common.h +151 -0
  392. package/ios/Packages/CoreObjC/energy.c +70 -0
  393. package/ios/Packages/CoreObjC/energy.h +43 -0
  394. package/ios/Packages/CoreObjC/fastmath.h +158 -0
  395. package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
  396. package/ios/Packages/CoreObjC/lc3.c +704 -0
  397. package/ios/Packages/CoreObjC/lc3.h +313 -0
  398. package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
  399. package/ios/Packages/CoreObjC/lc3_private.h +163 -0
  400. package/ios/Packages/CoreObjC/ltpf.c +905 -0
  401. package/ios/Packages/CoreObjC/ltpf.h +111 -0
  402. package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
  403. package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
  404. package/ios/Packages/CoreObjC/makefile.mk +35 -0
  405. package/ios/Packages/CoreObjC/mdct.c +469 -0
  406. package/ios/Packages/CoreObjC/mdct.h +57 -0
  407. package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
  408. package/ios/Packages/CoreObjC/meson.build +61 -0
  409. package/ios/Packages/CoreObjC/plc.c +61 -0
  410. package/ios/Packages/CoreObjC/plc.h +57 -0
  411. package/ios/Packages/CoreObjC/rnnoise.h +114 -0
  412. package/ios/Packages/CoreObjC/sns.c +880 -0
  413. package/ios/Packages/CoreObjC/sns.h +103 -0
  414. package/ios/Packages/CoreObjC/spec.c +907 -0
  415. package/ios/Packages/CoreObjC/spec.h +119 -0
  416. package/ios/Packages/CoreObjC/tables.c +3457 -0
  417. package/ios/Packages/CoreObjC/tables.h +94 -0
  418. package/ios/Packages/CoreObjC/tns.c +457 -0
  419. package/ios/Packages/CoreObjC/tns.h +99 -0
  420. package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
  421. package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
  422. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +2383 -0
  423. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +48 -0
  424. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
  425. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
  426. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
  427. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
  428. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
  429. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
  430. package/ios/Packages/libbz2/module.modulemap +5 -0
  431. package/ios/Packages/libbz2/shim.h +1 -0
  432. package/ios/PhotoReceiverModule.swift +129 -0
  433. package/ios/Source/BluetoothSDK.swift +2303 -0
  434. package/ios/Source/BluetoothSDKDebug.swift +12 -0
  435. package/ios/Source/BluetoothSdkDefaults.swift +29 -0
  436. package/ios/Source/Bridge.swift +708 -0
  437. package/ios/Source/Bridging-Header.h +16 -0
  438. package/ios/Source/DeviceManager.swift +2109 -0
  439. package/ios/Source/DeviceStore.swift +336 -0
  440. package/ios/Source/GeneratedChangelogCatalog.swift +7 -0
  441. package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
  442. package/ios/Source/ObservableStore.swift +118 -0
  443. package/ios/Source/OtaManifest.swift +211 -0
  444. package/ios/Source/PrivacyInfo.xcprivacy +23 -0
  445. package/ios/Source/ReleaseChangelog.swift +65 -0
  446. package/ios/Source/Resources/G2Firmware/README.md +10 -0
  447. package/ios/Source/audio/AudioModels.swift +178 -0
  448. package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
  449. package/ios/Source/camera/CameraModels.swift +866 -0
  450. package/ios/Source/connection/ScanSession.swift +27 -0
  451. package/ios/Source/controllers/ControllerManager.swift +184 -0
  452. package/ios/Source/controllers/R1.swift +829 -0
  453. package/ios/Source/errors/BluetoothSdkError.swift +19 -0
  454. package/ios/Source/events/BluetoothEvents.swift +342 -0
  455. package/ios/Source/internal/BleTraceLogger.swift +192 -0
  456. package/ios/Source/internal/BluetoothAvailability.swift +78 -0
  457. package/ios/Source/internal/BluetoothSdkAnalytics.swift +199 -0
  458. package/ios/Source/internal/ValueParsing.swift +99 -0
  459. package/ios/Source/requests/DisplayRequests.swift +82 -0
  460. package/ios/Source/services/PhoneMic.swift +662 -0
  461. package/ios/Source/sgcs/Ar99.swift +2451 -0
  462. package/ios/Source/sgcs/Frame.swift +649 -0
  463. package/ios/Source/sgcs/G1.swift +2554 -0
  464. package/ios/Source/sgcs/G2.swift +6887 -0
  465. package/ios/Source/sgcs/G2CfwClassification.swift +43 -0
  466. package/ios/Source/sgcs/G2CfwRenderer.swift +1279 -0
  467. package/ios/Source/sgcs/G2CfwTextureCache.swift +233 -0
  468. package/ios/Source/sgcs/G2FlashProtocol.swift +312 -0
  469. package/ios/Source/sgcs/G2Flasher.swift +773 -0
  470. package/ios/Source/sgcs/Mach1.swift +541 -0
  471. package/ios/Source/sgcs/MentraLive.swift +7011 -0
  472. package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
  473. package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
  474. package/ios/Source/sgcs/MentraNex.swift +3221 -0
  475. package/ios/Source/sgcs/Nimo.swift +1886 -0
  476. package/ios/Source/sgcs/SGCManager.swift +608 -0
  477. package/ios/Source/sgcs/Simulated.swift +274 -0
  478. package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
  479. package/ios/Source/sgcs/glassly_ble.pb.swift +4354 -0
  480. package/ios/Source/speakerid/SpeakerIdEngine.swift +573 -0
  481. package/ios/Source/status/DeviceStatus.swift +981 -0
  482. package/ios/Source/status/RuntimeState.swift +359 -0
  483. package/ios/Source/status/WifiHotspotStatus.swift +345 -0
  484. package/ios/Source/streaming/StreamModels.swift +693 -0
  485. package/ios/Source/stt/STTTools.swift +191 -0
  486. package/ios/Source/stt/SherpaOnnxTranscriber.swift +466 -0
  487. package/ios/Source/tts/TTSTools.swift +171 -0
  488. package/ios/Source/types/DeviceModels.swift +177 -0
  489. package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
  490. package/ios/Source/utils/BinaryCodec.swift +61 -0
  491. package/ios/Source/utils/BleJsonCompact.swift +405 -0
  492. package/ios/Source/utils/BleWireProtocol.swift +92 -0
  493. package/ios/Source/utils/Constants.swift +78 -0
  494. package/ios/Source/utils/Enums.swift +95 -0
  495. package/ios/Source/utils/G1Text.swift +2096 -0
  496. package/ios/Source/utils/JSCExperiment.swift +241 -0
  497. package/ios/Source/utils/MemoryMonitor.swift +44 -0
  498. package/ios/Source/utils/MessageChunkReassembler.swift +190 -0
  499. package/ios/Source/utils/MessageChunker.swift +294 -0
  500. package/ios/Source/utils/Models.swift +135 -0
  501. package/ios/Source/utils/PhoneAudioMonitor.swift +230 -0
  502. package/ios/Source/utils/TarBz2Extractor.swift +257 -0
  503. package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +78 -0
  504. package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +372 -0
  505. package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +162 -0
  506. package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +182 -0
  507. package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
  508. package/ios/Tests/ReleaseChangelogTests.swift +13 -0
  509. package/package.json +129 -0
  510. package/plugin/build/index.d.ts +10 -0
  511. package/plugin/build/index.js +12 -0
  512. package/plugin/build/withAndroid.d.ts +3 -0
  513. package/plugin/build/withAndroid.js +190 -0
  514. package/plugin/build/withIos.d.ts +5 -0
  515. package/plugin/build/withIos.js +108 -0
  516. package/scripts/inject-ios-sdk-version.mjs +65 -0
  517. package/scripts/public-ota-api.test.mjs +37 -0
  518. package/scripts/verify-release-package.mjs +45 -0
  519. package/scripts/write-release-metadata.mjs +152 -0
  520. package/scripts/write-release-metadata.test.mjs +37 -0
  521. package/src/BluetoothSdk.types.ts +1623 -0
  522. package/src/_internal.ts +16 -0
  523. package/src/_private/BluetoothSdkModule.ts +684 -0
  524. package/src/_private/LocalNetworkModule.ts +50 -0
  525. package/src/_private/cameraRequestPayload.ts +34 -0
  526. package/src/_private/photoRequestPayload.ts +90 -0
  527. package/src/_private/sdkOtaManifest.ts +18 -0
  528. package/src/changelogs.ts +46 -0
  529. package/src/debug.ts +9 -0
  530. package/src/generated/changelogCatalog.ts +13 -0
  531. package/src/generated/releaseMetadata.ts +20 -0
  532. package/src/index.ts +321 -0
  533. package/src/ota-server/OtaServer.types.ts +10 -0
  534. package/src/ota-server/OtaServerModule.ts +23 -0
  535. package/src/ota-server/OtaServerModule.web.ts +17 -0
  536. package/src/ota-server/index.ts +2 -0
  537. package/src/ota-transport/index.ts +116 -0
  538. package/src/photo-receiver/PhotoReceiver.types.ts +20 -0
  539. package/src/photo-receiver/PhotoReceiverModule.ts +11 -0
  540. package/src/photo-receiver/PhotoReceiverModule.web.ts +17 -0
  541. package/src/photo-receiver/index.ts +2 -0
  542. package/src/react/index.ts +21 -0
  543. package/src/react/useBluetooth.ts +309 -0
  544. package/src/react/useBluetoothEvent.ts +35 -0
  545. package/src/react/useBluetoothScan.ts +173 -0
  546. package/src/react/useBluetoothStatus.ts +180 -0
  547. package/src/react/useGlassesConnection.ts +202 -0
  548. package/src/types/index.ts +12 -0
@@ -0,0 +1,773 @@
1
+ @preconcurrency import CoreBluetooth
2
+ import CryptoKit
3
+ import Foundation
4
+
5
+ struct G2FirmwareArtifact {
6
+ let id: String
7
+ let displayName: String
8
+ let version: String
9
+ let fileName: String
10
+ let sha256: String
11
+ let size: Int
12
+
13
+ var dictionary: [String: Any] {
14
+ [
15
+ "id": id,
16
+ "displayName": displayName,
17
+ "version": version,
18
+ "fileName": fileName,
19
+ "sha256": sha256,
20
+ "size": size,
21
+ ]
22
+ }
23
+ }
24
+
25
+ enum G2FirmwareCatalog {
26
+ static let artifacts = [
27
+ G2FirmwareArtifact(
28
+ id: "stock",
29
+ displayName: "Stock firmware",
30
+ version: "2.2.6.10",
31
+ fileName: "g2-stock-2.2.6.10.evenota.bin",
32
+ sha256: "f4dfb0b49ad3de3c2daf17f8a27a157c3dc98411d6a0d3ab2cfd0918f41b9afa",
33
+ size: 4_301_227
34
+ ),
35
+ G2FirmwareArtifact(
36
+ id: "glassly-cfw",
37
+ displayName: "Glassly CFW",
38
+ version: "2.2.6.10 CFW",
39
+ fileName: "g2-glassly-cfw-2.2.6.10.evenota.bin",
40
+ sha256: "82726bbe4d1d82f06a3d345ba3e5a4a39161077e5d26dcd7af24c4677df87fd1",
41
+ size: 4_336_071
42
+ ),
43
+ ]
44
+
45
+ static func artifact(id: String) throws -> G2FirmwareArtifact {
46
+ guard let artifact = artifacts.first(where: { $0.id == id }) else {
47
+ throw G2FirmwareFlashError.invalidArtifact("Unknown G2 firmware target: \(id)")
48
+ }
49
+ return artifact
50
+ }
51
+
52
+ static func load(_ artifact: G2FirmwareArtifact) throws -> Data {
53
+ guard let url = resourceURL(fileName: artifact.fileName) else {
54
+ throw G2FirmwareFlashError.invalidArtifact(
55
+ "Bundled firmware \(artifact.fileName) is missing from the app."
56
+ )
57
+ }
58
+ let data = try Data(contentsOf: url, options: .mappedIfSafe)
59
+ guard data.count == artifact.size else {
60
+ throw G2FirmwareFlashError.invalidArtifact(
61
+ "\(artifact.displayName) size mismatch: expected \(artifact.size), got \(data.count)"
62
+ )
63
+ }
64
+ let digest = SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined()
65
+ guard digest == artifact.sha256 else {
66
+ throw G2FirmwareFlashError.invalidArtifact(
67
+ "\(artifact.displayName) SHA-256 mismatch; refusing to flash"
68
+ )
69
+ }
70
+ return data
71
+ }
72
+
73
+ private static func resourceURL(fileName: String) -> URL? {
74
+ let name = fileName as NSString
75
+ let base = name.deletingPathExtension
76
+ let ext = name.pathExtension
77
+ #if SWIFT_PACKAGE
78
+ return Bundle.module.url(forResource: base, withExtension: ext, subdirectory: "G2Firmware")
79
+ ?? Bundle.module.url(forResource: base, withExtension: ext)
80
+ #else
81
+ let candidates = Bundle.allBundles + Bundle.allFrameworks + [Bundle.main]
82
+ for bundle in candidates {
83
+ if let url = bundle.url(forResource: base, withExtension: ext, subdirectory: "G2Firmware") {
84
+ return url
85
+ }
86
+ if let resourceBundleURL = bundle.url(forResource: "BluetoothSDKG2Firmware", withExtension: "bundle"),
87
+ let resourceBundle = Bundle(url: resourceBundleURL),
88
+ let url = resourceBundle.url(forResource: base, withExtension: ext)
89
+ {
90
+ return url
91
+ }
92
+ }
93
+ return nil
94
+ #endif
95
+ }
96
+ }
97
+
98
+ enum G2FirmwareFlashError: Error, LocalizedError {
99
+ case busy
100
+ case invalidArtifact(String)
101
+ case notConnected
102
+ case channelsMissing(String)
103
+ case transport(String)
104
+
105
+ var errorDescription: String? {
106
+ switch self {
107
+ case .busy:
108
+ "A G2 firmware flash is already in progress."
109
+ case let .invalidArtifact(message), let .channelsMissing(message), let .transport(message):
110
+ message
111
+ case .notConnected:
112
+ "Both G2 lenses must be connected before flashing."
113
+ }
114
+ }
115
+ }
116
+
117
+ /// Shared stock/CFW OTA state machine. It owns no rendering policy and can be
118
+ /// used by the unified `G2` controller with either firmware artifact.
119
+ final class G2Flasher: @unchecked Sendable {
120
+ private static let dataWriteUUID = CBUUID(string: "00002760-08C2-11E1-9073-0E8AC72E0001")
121
+ static let dataNotifyUUID = CBUUID(string: "00002760-08C2-11E1-9073-0E8AC72E0002")
122
+ private static let controlWriteUUID = CBUUID(string: "00002760-08C2-11E1-9073-0E8AC72E5401")
123
+ static let controlNotifyUUID = CBUUID(string: "00002760-08C2-11E1-9073-0E8AC72E5402")
124
+ private static let blockNakRetries = 3
125
+ private static let componentRetries = 3
126
+ private static let reconnectAttempts = 3
127
+ private static let reconnectDelay: TimeInterval = 10
128
+
129
+ private struct ReceivedFrame {
130
+ let peripheralID: UUID
131
+ let characteristicUUID: CBUUID
132
+ let bytes: [UInt8]
133
+ }
134
+
135
+ private struct LensSession {
136
+ let peripheral: CBPeripheral
137
+ let dataWrite: CBCharacteristic
138
+ let dataNotify: CBCharacteristic
139
+ let controlWrite: CBCharacteristic
140
+ let controlNotify: CBCharacteristic
141
+ }
142
+
143
+ private enum SessionError: LocalizedError {
144
+ case acknowledgementTimeout(String)
145
+
146
+ var errorDescription: String? {
147
+ switch self {
148
+ case let .acknowledgementTimeout(message): message
149
+ }
150
+ }
151
+ }
152
+
153
+ private let queue = DispatchQueue(label: "com.glassly.g2.flash", qos: .userInitiated)
154
+ private let stateLock = NSLock()
155
+ private let receiveSemaphore = DispatchSemaphore(value: 0)
156
+ private var receiveFrames: [ReceivedFrame] = []
157
+ private var active = false
158
+ private var sequence: UInt8 = 0
159
+
160
+ var isActive: Bool {
161
+ stateLock.lock()
162
+ defer { stateLock.unlock() }
163
+ return active
164
+ }
165
+
166
+ func receive(_ data: Data, from characteristicUUID: CBUUID, peripheralID: UUID) {
167
+ stateLock.lock()
168
+ guard active else {
169
+ stateLock.unlock()
170
+ return
171
+ }
172
+ receiveFrames.append(
173
+ ReceivedFrame(
174
+ peripheralID: peripheralID,
175
+ characteristicUUID: characteristicUUID,
176
+ bytes: [UInt8](data)
177
+ )
178
+ )
179
+ stateLock.unlock()
180
+ receiveSemaphore.signal()
181
+ }
182
+
183
+ func flash(
184
+ artifact: G2FirmwareArtifact,
185
+ left: CBPeripheral,
186
+ right: CBPeripheral,
187
+ central: CBCentralManager
188
+ ) async throws -> [String: Any] {
189
+ try begin()
190
+ return try await withCheckedThrowingContinuation { continuation in
191
+ queue.async { [weak self] in
192
+ guard let self else {
193
+ continuation.resume(throwing: G2FirmwareFlashError.transport("G2 flasher was released"))
194
+ return
195
+ }
196
+ defer { self.finish() }
197
+ do {
198
+ let result = try self.run(
199
+ artifact: artifact,
200
+ left: left,
201
+ right: right,
202
+ central: central
203
+ )
204
+ continuation.resume(returning: result)
205
+ } catch {
206
+ self.progress(error.localizedDescription, 0, done: true, ok: false)
207
+ continuation.resume(throwing: error)
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ private func begin() throws {
214
+ stateLock.lock()
215
+ defer { stateLock.unlock() }
216
+ guard !active else { throw G2FirmwareFlashError.busy }
217
+ active = true
218
+ sequence = 0
219
+ receiveFrames.removeAll()
220
+ while receiveSemaphore.wait(timeout: .now()) == .success {}
221
+ }
222
+
223
+ private func finish() {
224
+ stateLock.lock()
225
+ active = false
226
+ receiveFrames.removeAll()
227
+ stateLock.unlock()
228
+ }
229
+
230
+ private func run(
231
+ artifact: G2FirmwareArtifact,
232
+ left: CBPeripheral,
233
+ right: CBPeripheral,
234
+ central: CBCentralManager
235
+ ) throws -> [String: Any] {
236
+ progress("Validating bundled \(artifact.displayName)…", 0.02)
237
+ let imageData = try G2FirmwareCatalog.load(artifact)
238
+ let image = [UInt8](imageData)
239
+ let validated: G2FlashProtocol.ValidatedImage
240
+ do {
241
+ validated = try Self.preflight(image)
242
+ } catch {
243
+ throw G2FirmwareFlashError.invalidArtifact("EVENOTA validation failed: \(error)")
244
+ }
245
+ let segments = validated.segments
246
+ let guardResult = validated.guardResult
247
+
248
+ // Re-run the implementation self-test against the bundled byte-identical
249
+ // stock reference before every flash, including CFW flashes.
250
+ let stock = try G2FirmwareCatalog.load(G2FirmwareCatalog.artifacts[0])
251
+ if let failure = G2FlashProtocol.selfTestGuard(
252
+ [UInt8](stock), expect: G2FlashProtocol.goldenStock
253
+ ) {
254
+ throw G2FirmwareFlashError.invalidArtifact("Brick-guard self-test failed: \(failure)")
255
+ }
256
+ progress(
257
+ String(format: "Image verified; program end 0x%08x", guardResult.progEnd),
258
+ 0.10
259
+ )
260
+
261
+ let lenses = [("Left", left), ("Right", right)]
262
+ for (label, peripheral) in lenses {
263
+ try requireChannels(peripheral, label: label)
264
+ }
265
+
266
+ for (index, entry) in lenses.enumerated() {
267
+ progress("Flashing \(entry.0) lens…", 0.12 + Double(index) * 0.43)
268
+ resetSequence()
269
+ try flashLens(
270
+ entry.1,
271
+ image: image,
272
+ segments: segments,
273
+ side: entry.0,
274
+ lensIndex: index,
275
+ central: central
276
+ )
277
+ }
278
+ progress("Flash complete.", 1, done: true, ok: true)
279
+ return [
280
+ "ok": true,
281
+ "target": artifact.id,
282
+ "version": artifact.version,
283
+ "sha256": artifact.sha256,
284
+ ]
285
+ }
286
+
287
+ /// Pure preflight seam used by the production flasher and native tests.
288
+ /// Keeping it here makes it impossible for a future image source to enter
289
+ /// the BLE state machine without component CRC and MRAM validation.
290
+ static func preflight(_ image: [UInt8]) throws -> G2FlashProtocol.ValidatedImage {
291
+ try G2FlashProtocol.validateFirmware(image)
292
+ }
293
+
294
+ private func requireChannels(_ peripheral: CBPeripheral, label: String) throws {
295
+ guard findCharacteristic(peripheral, Self.dataWriteUUID) != nil,
296
+ findCharacteristic(peripheral, Self.dataNotifyUUID) != nil,
297
+ findCharacteristic(peripheral, Self.controlWriteUUID) != nil,
298
+ findCharacteristic(peripheral, Self.controlNotifyUUID) != nil
299
+ else {
300
+ throw G2FirmwareFlashError.channelsMissing(
301
+ "\(label) lens OTA/authentication characteristics are unavailable. Reconnect both lenses and try again."
302
+ )
303
+ }
304
+ }
305
+
306
+ private func flashLens(
307
+ _ peripheral: CBPeripheral,
308
+ image: [UInt8],
309
+ segments: [G2FlashProtocol.Segment],
310
+ side: String,
311
+ lensIndex: Int,
312
+ central: CBCentralManager
313
+ ) throws {
314
+ var session = try prepareSession(peripheral, side: side)
315
+ resetSequence()
316
+ try beginOtaSession(session, side: side)
317
+
318
+ for (index, segment) in segments.enumerated() {
319
+ try flashComponent(
320
+ segment,
321
+ index: index,
322
+ total: segments.count,
323
+ image: image,
324
+ session: &session,
325
+ side: side,
326
+ lensIndex: lensIndex,
327
+ central: central
328
+ )
329
+ }
330
+ }
331
+
332
+ private func prepareSession(_ peripheral: CBPeripheral, side: String) throws -> LensSession {
333
+ guard peripheral.state == .connected else {
334
+ throw G2FirmwareFlashError.transport("\(side) lens is not connected")
335
+ }
336
+ let session = try lensSession(peripheral, side: side)
337
+ onMain {
338
+ peripheral.setNotifyValue(true, for: session.dataNotify)
339
+ peripheral.setNotifyValue(true, for: session.controlNotify)
340
+ }
341
+ Thread.sleep(forTimeInterval: 2.5)
342
+ guard session.dataNotify.isNotifying, session.controlNotify.isNotifying else {
343
+ throw G2FirmwareFlashError.transport(
344
+ "\(side) lens did not confirm OTA and authentication notifications"
345
+ )
346
+ }
347
+ drainReceiveQueue()
348
+ try authenticate(session, side: side)
349
+ return session
350
+ }
351
+
352
+ private func lensSession(_ peripheral: CBPeripheral, side: String) throws -> LensSession {
353
+ guard let dataWrite = findCharacteristic(peripheral, Self.dataWriteUUID),
354
+ let dataNotify = findCharacteristic(peripheral, Self.dataNotifyUUID),
355
+ let controlWrite = findCharacteristic(peripheral, Self.controlWriteUUID),
356
+ let controlNotify = findCharacteristic(peripheral, Self.controlNotifyUUID)
357
+ else {
358
+ throw G2FirmwareFlashError.channelsMissing("\(side) lens OTA/authentication channels disappeared")
359
+ }
360
+ return LensSession(
361
+ peripheral: peripheral,
362
+ dataWrite: dataWrite,
363
+ dataNotify: dataNotify,
364
+ controlWrite: controlWrite,
365
+ controlNotify: controlNotify
366
+ )
367
+ }
368
+
369
+ private func authenticate(_ session: LensSession, side: String) throws {
370
+ let magic = nextSequence()
371
+ drainReceiveQueue()
372
+ try writeFrames(
373
+ G2FlashProtocol.authenticationFrames(magic: magic),
374
+ characteristic: session.controlWrite,
375
+ peripheral: session.peripheral
376
+ )
377
+ try waitForAuthentication(
378
+ peripheralID: session.peripheral.identifier,
379
+ magic: magic,
380
+ timeoutMs: 4000,
381
+ side: side
382
+ )
383
+ }
384
+
385
+ private func beginOtaSession(_ session: LensSession, side: String) throws {
386
+ let beginStatus = try sendAndWait(
387
+ G2FlashProtocol.ctrlFrames(0x00, seq: nextSequence()),
388
+ opcode: 0x00,
389
+ characteristic: session.dataWrite,
390
+ peripheral: session.peripheral
391
+ )
392
+ guard beginStatus == 0 || beginStatus == 8 || beginStatus == 9 else {
393
+ throw G2FirmwareFlashError.transport("\(side) lens rejected OTA begin (status \(beginStatus))")
394
+ }
395
+ }
396
+
397
+ private func flashComponent(
398
+ _ segment: G2FlashProtocol.Segment,
399
+ index: Int,
400
+ total: Int,
401
+ image: [UInt8],
402
+ session: inout LensSession,
403
+ side: String,
404
+ lensIndex: Int,
405
+ central: CBCentralManager
406
+ ) throws {
407
+ let offset = Int(segment.off) + 128
408
+ let payloadSize = Int(segment.ps)
409
+ guard offset >= 128, payloadSize > 0, offset + payloadSize <= image.count else {
410
+ throw G2FirmwareFlashError.invalidArtifact("\(segment.fn) payload extends past EOF")
411
+ }
412
+ let payload = Array(image[offset ..< offset + payloadSize])
413
+ let blockCount = (payload.count + 4095) / 4096
414
+
415
+ var lastFailure = "unknown error"
416
+ for attempt in 1 ... Self.componentRetries {
417
+ if attempt > 1 {
418
+ progress("\(side): retrying \(segment.fn) (\(attempt)/\(Self.componentRetries))", 0)
419
+ drainReceiveQueue()
420
+ }
421
+ do {
422
+ let fileStatus = try sendAndWait(
423
+ G2FlashProtocol.ctrlFrames(0x01, segment.sub, seq: nextSequence()),
424
+ opcode: 0x01,
425
+ characteristic: session.dataWrite,
426
+ peripheral: session.peripheral
427
+ )
428
+ guard fileStatus == 0 else {
429
+ throw G2FirmwareFlashError.transport("FILE_CHECK status \(fileStatus)")
430
+ }
431
+
432
+ for blockIndex in 0 ..< blockCount {
433
+ let start = blockIndex * 4096
434
+ let block = Array(payload[start ..< min(start + 4096, payload.count)])
435
+ var acknowledged = false
436
+ for retry in 0 ..< Self.blockNakRetries {
437
+ let sequence = nextSequence()
438
+ drainReceiveQueue()
439
+ try writeFrames(
440
+ G2FlashProtocol.ctrlFrames(0x02, seq: sequence),
441
+ characteristic: session.dataWrite,
442
+ peripheral: session.peripheral
443
+ )
444
+ try writeFrames(
445
+ G2FlashProtocol.dataFrames(block, seq: sequence),
446
+ characteristic: session.dataWrite,
447
+ peripheral: session.peripheral
448
+ )
449
+ let status: UInt8
450
+ do {
451
+ status = try waitForAck(
452
+ opcode: 0x02,
453
+ peripheralID: session.peripheral.identifier,
454
+ timeoutMs: 5000
455
+ )
456
+ } catch {
457
+ // A timeout is ambiguous: the block may have been committed and
458
+ // only its ACK lost. Never resend it in place because OTA has no
459
+ // block index/deduplication; restart from FILE_CHECK instead.
460
+ throw SessionError.acknowledgementTimeout(
461
+ "\(segment.fn) block \(blockIndex + 1)/\(blockCount): \(error.localizedDescription)"
462
+ )
463
+ }
464
+ if status == 0 {
465
+ acknowledged = true
466
+ break
467
+ }
468
+ progress(
469
+ "\(side): \(segment.fn) block \(blockIndex + 1)/\(blockCount) NAK \(status), retry \(retry + 1)/\(Self.blockNakRetries)",
470
+ 0
471
+ )
472
+ }
473
+ guard acknowledged else {
474
+ throw G2FirmwareFlashError.transport(
475
+ "\(segment.fn) block \(blockIndex + 1)/\(blockCount) was not acknowledged"
476
+ )
477
+ }
478
+ let componentFraction =
479
+ (Double(index) + Double(blockIndex + 1) / Double(blockCount)) / Double(total)
480
+ let fraction = 0.12 + Double(lensIndex) * 0.43 + componentFraction * 0.43
481
+ progress("\(side): \(segment.fn) \(blockIndex + 1)/\(blockCount)", fraction)
482
+ }
483
+
484
+ let endStatus = try sendAndWait(
485
+ G2FlashProtocol.ctrlFrames(0x03, seq: nextSequence()),
486
+ opcode: 0x03,
487
+ characteristic: session.dataWrite,
488
+ peripheral: session.peripheral,
489
+ timeoutMs: 15000
490
+ )
491
+ guard endStatus == 0 || endStatus == 8 || endStatus == 9 else {
492
+ throw G2FirmwareFlashError.transport("END status \(endStatus)")
493
+ }
494
+ return
495
+ } catch {
496
+ lastFailure = error.localizedDescription
497
+ guard attempt < Self.componentRetries else { break }
498
+ if error is SessionError || session.peripheral.state != .connected {
499
+ session = try recoverSession(
500
+ session.peripheral,
501
+ side: side,
502
+ central: central
503
+ )
504
+ } else {
505
+ drainReceiveQueue()
506
+ Thread.sleep(forTimeInterval: 1.5)
507
+ }
508
+ }
509
+ }
510
+ throw G2FirmwareFlashError.transport(
511
+ "\(side): \(segment.fn) failed after \(Self.componentRetries) attempts: \(lastFailure)"
512
+ )
513
+ }
514
+
515
+ private func sendAndWait(
516
+ _ frames: [[UInt8]],
517
+ opcode: UInt8,
518
+ characteristic: CBCharacteristic,
519
+ peripheral: CBPeripheral,
520
+ timeoutMs: Int = 5000
521
+ ) throws -> UInt8 {
522
+ guard !frames.isEmpty else {
523
+ throw G2FirmwareFlashError.transport("Cannot send an empty OTA frame set")
524
+ }
525
+ drainReceiveQueue()
526
+ try writeFrames(frames, characteristic: characteristic, peripheral: peripheral)
527
+ return try waitForAck(
528
+ opcode: opcode,
529
+ peripheralID: peripheral.identifier,
530
+ timeoutMs: timeoutMs
531
+ )
532
+ }
533
+
534
+ private func waitForAck(opcode: UInt8, peripheralID: UUID, timeoutMs: Int) throws -> UInt8 {
535
+ let deadline = Date().addingTimeInterval(Double(timeoutMs) / 1000)
536
+ var observed: [String] = []
537
+ while Date() < deadline {
538
+ stateLock.lock()
539
+ let frames = receiveFrames
540
+ receiveFrames.removeAll()
541
+ stateLock.unlock()
542
+ let matching = frames.filter {
543
+ $0.peripheralID == peripheralID && $0.characteristicUUID == Self.dataNotifyUUID
544
+ }
545
+ if let status = G2FlashProtocol.otaAckStatus(
546
+ in: matching.map(\.bytes),
547
+ opcode: opcode
548
+ ) {
549
+ return status
550
+ }
551
+ for received in matching.prefix(4) {
552
+ let frame = received.bytes
553
+ if let decoded = G2FlashProtocol.parseRx(frame) {
554
+ observed.append(
555
+ String(
556
+ format: "seq=%u sid=0x%02x pb=%@",
557
+ decoded.sequence, decoded.sid,
558
+ decoded.pb.map { String(format: "%02x", $0) }.joined()
559
+ )
560
+ )
561
+ } else {
562
+ observed.append(frame.prefix(16).map { String(format: "%02x", $0) }.joined())
563
+ }
564
+ }
565
+ _ = receiveSemaphore.wait(timeout: .now() + 0.2)
566
+ }
567
+ let detail = observed.isEmpty ? "no OTA notifications received" : observed.suffix(4).joined(separator: "; ")
568
+ throw SessionError.acknowledgementTimeout(
569
+ String(format: "Timed out waiting for OTA acknowledgement 0x%02x (%@)", opcode, detail)
570
+ )
571
+ }
572
+
573
+ private func waitForAuthentication(
574
+ peripheralID: UUID,
575
+ magic: UInt8,
576
+ timeoutMs: Int,
577
+ side: String
578
+ ) throws {
579
+ let deadline = Date().addingTimeInterval(Double(timeoutMs) / 1000)
580
+ var observed: [String] = []
581
+ while Date() < deadline {
582
+ stateLock.lock()
583
+ let frames = receiveFrames
584
+ receiveFrames.removeAll()
585
+ stateLock.unlock()
586
+ let matching = frames.filter {
587
+ $0.peripheralID == peripheralID && $0.characteristicUUID == Self.controlNotifyUUID
588
+ }
589
+ if matching.contains(where: {
590
+ G2FlashProtocol.isAuthenticationSuccess($0.bytes, magic: magic)
591
+ }) {
592
+ return
593
+ }
594
+ observed.append(contentsOf: matching.prefix(4).map {
595
+ $0.bytes.prefix(16).map { String(format: "%02x", $0) }.joined()
596
+ })
597
+ _ = receiveSemaphore.wait(timeout: .now() + 0.2)
598
+ }
599
+ let detail = observed.isEmpty ? "no authentication notifications received" : observed.suffix(4).joined(separator: "; ")
600
+ throw G2FirmwareFlashError.transport(
601
+ "\(side) lens did not authenticate before OTA (\(detail))"
602
+ )
603
+ }
604
+
605
+ private func recoverSession(
606
+ _ peripheral: CBPeripheral,
607
+ side: String,
608
+ central: CBCentralManager
609
+ ) throws -> LensSession {
610
+ var lastFailure = "unknown error"
611
+ for attempt in 1 ... Self.reconnectAttempts {
612
+ progress(
613
+ "\(side): reconnecting OTA session (\(attempt)/\(Self.reconnectAttempts))",
614
+ 0
615
+ )
616
+ do {
617
+ onMain {
618
+ if peripheral.state != .disconnected {
619
+ central.cancelPeripheralConnection(peripheral)
620
+ }
621
+ }
622
+ try waitForPeripheral(
623
+ peripheral,
624
+ state: .disconnected,
625
+ timeout: 8,
626
+ context: "disconnect before OTA recovery"
627
+ )
628
+ Thread.sleep(forTimeInterval: Self.reconnectDelay)
629
+ drainReceiveQueue()
630
+
631
+ onMain {
632
+ central.connect(
633
+ peripheral,
634
+ options: [
635
+ CBConnectPeripheralOptionNotifyOnConnectionKey: true,
636
+ CBConnectPeripheralOptionNotifyOnDisconnectionKey: true,
637
+ ]
638
+ )
639
+ }
640
+ try waitForPeripheral(
641
+ peripheral,
642
+ state: .connected,
643
+ timeout: 25,
644
+ context: "OTA recovery reconnect"
645
+ )
646
+ onMain { peripheral.discoverServices(nil) }
647
+ _ = try waitForChannels(peripheral, side: side, timeout: 20)
648
+
649
+ let recovered = try prepareSession(peripheral, side: side)
650
+ resetSequence()
651
+ try beginOtaSession(recovered, side: side)
652
+ progress("\(side): OTA session restored", 0)
653
+ return recovered
654
+ } catch {
655
+ lastFailure = error.localizedDescription
656
+ progress("\(side): OTA reconnect attempt failed: \(lastFailure)", 0)
657
+ }
658
+ }
659
+ throw G2FirmwareFlashError.transport(
660
+ "\(side): could not restore OTA session after \(Self.reconnectAttempts) attempts: \(lastFailure)"
661
+ )
662
+ }
663
+
664
+ private func waitForChannels(
665
+ _ peripheral: CBPeripheral,
666
+ side: String,
667
+ timeout: TimeInterval
668
+ ) throws -> LensSession {
669
+ let deadline = Date().addingTimeInterval(timeout)
670
+ while Date() < deadline {
671
+ if let session = try? lensSession(peripheral, side: side) {
672
+ return session
673
+ }
674
+ Thread.sleep(forTimeInterval: 0.1)
675
+ }
676
+ throw G2FirmwareFlashError.channelsMissing(
677
+ "\(side) lens OTA/authentication channels did not return after reconnect"
678
+ )
679
+ }
680
+
681
+ private func waitForPeripheral(
682
+ _ peripheral: CBPeripheral,
683
+ state expected: CBPeripheralState,
684
+ timeout: TimeInterval,
685
+ context: String
686
+ ) throws {
687
+ let deadline = Date().addingTimeInterval(timeout)
688
+ while Date() < deadline {
689
+ if peripheral.state == expected {
690
+ return
691
+ }
692
+ Thread.sleep(forTimeInterval: 0.1)
693
+ }
694
+ throw G2FirmwareFlashError.transport(
695
+ "Timed out waiting for \(context) (state \(peripheral.state.rawValue))"
696
+ )
697
+ }
698
+
699
+ private func writeFrames(
700
+ _ frames: [[UInt8]],
701
+ characteristic: CBCharacteristic,
702
+ peripheral: CBPeripheral
703
+ ) throws {
704
+ for frame in frames {
705
+ let deadline = Date().addingTimeInterval(5)
706
+ while !peripheral.canSendWriteWithoutResponse && Date() < deadline {
707
+ usleep(1000)
708
+ }
709
+ guard peripheral.canSendWriteWithoutResponse else {
710
+ throw G2FirmwareFlashError.transport("BLE write backpressure did not clear")
711
+ }
712
+ onMain {
713
+ peripheral.writeValue(Data(frame), for: characteristic, type: .withoutResponse)
714
+ }
715
+ }
716
+ }
717
+
718
+ private func findCharacteristic(_ peripheral: CBPeripheral, _ uuid: CBUUID) -> CBCharacteristic? {
719
+ for service in peripheral.services ?? [] {
720
+ if let match = service.characteristics?.first(where: { $0.uuid == uuid }) {
721
+ return match
722
+ }
723
+ }
724
+ return nil
725
+ }
726
+
727
+ private func nextSequence() -> UInt8 {
728
+ stateLock.lock()
729
+ sequence &+= 1
730
+ let next = sequence
731
+ stateLock.unlock()
732
+ return next
733
+ }
734
+
735
+ private func resetSequence() {
736
+ stateLock.lock()
737
+ sequence = 0
738
+ stateLock.unlock()
739
+ }
740
+
741
+ private func drainReceiveQueue() {
742
+ stateLock.lock()
743
+ receiveFrames.removeAll()
744
+ stateLock.unlock()
745
+ while receiveSemaphore.wait(timeout: .now()) == .success {}
746
+ }
747
+
748
+ private func onMain(_ action: @escaping () -> Void) {
749
+ if Thread.isMainThread {
750
+ action()
751
+ } else {
752
+ DispatchQueue.main.sync(execute: action)
753
+ }
754
+ }
755
+
756
+ private func progress(
757
+ _ message: String,
758
+ _ progress: Double,
759
+ done: Bool = false,
760
+ ok: Bool = true
761
+ ) {
762
+ Bridge.log("G2 flash: \(message)")
763
+ Bridge.sendTypedMessage(
764
+ "g2_firmware_flash_progress",
765
+ body: [
766
+ "message": message,
767
+ "progress": max(0, min(1, progress)),
768
+ "done": done,
769
+ "ok": ok,
770
+ ]
771
+ )
772
+ }
773
+ }