@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.
- package/README.md +563 -0
- package/android/build.gradle +450 -0
- package/android/gradle.properties.example +9 -0
- package/android/lc3Lib/CMakeLists.txt +6 -0
- package/android/lc3Lib/build.gradle +212 -0
- package/android/lc3Lib/proguard-rules.pro +33 -0
- package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
- package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
- package/android/lc3Lib/src/main/java/com/glassly/lc3Lib/Lc3Cpp.java +38 -0
- package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
- package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
- package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
- package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
- package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
- package/android/settings.gradle +4 -0
- package/android/silero/LICENSE.md +17 -0
- package/android/silero/build.gradle +50 -0
- package/android/silero/proguard-rules.pro +24 -0
- package/android/silero/src/androidTest/assets/hello.wav +0 -0
- package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
- package/android/silero/src/main/assets/LICENSE +21 -0
- package/android/silero/src/main/assets/silero_vad.onnx +0 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
- package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
- package/android/src/main/AndroidManifest.xml +42 -0
- package/android/src/main/assets/G2Firmware/README.md +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +2325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkAnalytics.kt +216 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDebug.kt +14 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDefaults.kt +7 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +1088 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +953 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +2528 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +360 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +8 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/LocalNetworkModule.kt +414 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ObservableStore.kt +88 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/OtaManifest.kt +191 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ReleaseChangelog.kt +61 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/audio/AudioModels.kt +144 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/camera/CameraModels.kt +486 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/ControllerManager.kt +158 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +937 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/debug/BleTraceLogger.kt +165 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/events/BluetoothEvents.kt +178 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/internal/MapParsing.kt +140 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/net/LocalIpv4.kt +57 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/OtaServerModule.kt +120 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +622 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/PhotoReceiverModule.kt +156 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/requests/DisplayRequests.kt +56 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/Foreground.kt +272 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/PhoneMic.kt +1195 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Ar99.java +2653 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1.kt +3878 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +6428 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +1241 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +270 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +291 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +411 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/GlasslyBle.java +43189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Mach1.kt +1280 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +10817 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +2011 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Nimo.kt +2532 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +470 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Simulated.kt +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +525 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +669 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/RuntimeState.kt +201 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/WifiHotspotStatus.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/streaming/StreamModels.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +391 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +248 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/types/DeviceModels.kt +96 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AES.java +106 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AvifExifStripper.java +573 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleJsonCompact.java +496 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadService.java +677 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/Constants.kt +46 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/G1Text.kt +436 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleUploadService.java +77 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtils.java +827 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunkReassembler.java +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunker.java +325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +698 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitor.kt +348 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/StreamStatusEventTest.kt +29 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/camera/PhotoRequestTest.kt +141 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/EvenRleTest.kt +63 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +89 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +344 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +200 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocolTest.kt +131 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/AvifExifStripperTest.java +46 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BleJsonCompactTest.java +319 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +107 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
- package/android/src/test/resources/avif_with_exif.avif +0 -0
- package/app.plugin.js +3 -0
- package/build/BluetoothSdk.types.d.ts +1354 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -0
- package/build/BluetoothSdk.types.js +41 -0
- package/build/BluetoothSdk.types.js.map +1 -0
- package/build/_internal.d.ts +17 -0
- package/build/_internal.d.ts.map +1 -0
- package/build/_internal.js +16 -0
- package/build/_internal.js.map +1 -0
- package/build/_private/BluetoothSdkModule.d.ts +172 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
- package/build/_private/BluetoothSdkModule.js +300 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -0
- package/build/_private/LocalNetworkModule.d.ts +38 -0
- package/build/_private/LocalNetworkModule.d.ts.map +1 -0
- package/build/_private/LocalNetworkModule.js +3 -0
- package/build/_private/LocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts +4 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -0
- package/build/_private/cameraRequestPayload.js +32 -0
- package/build/_private/cameraRequestPayload.js.map +1 -0
- package/build/_private/photoRequestPayload.d.ts +6 -0
- package/build/_private/photoRequestPayload.d.ts.map +1 -0
- package/build/_private/photoRequestPayload.js +86 -0
- package/build/_private/photoRequestPayload.js.map +1 -0
- package/build/_private/sdkOtaManifest.d.ts +11 -0
- package/build/_private/sdkOtaManifest.d.ts.map +1 -0
- package/build/_private/sdkOtaManifest.js +15 -0
- package/build/_private/sdkOtaManifest.js.map +1 -0
- package/build/changelogs.d.ts +7 -0
- package/build/changelogs.d.ts.map +1 -0
- package/build/changelogs.js +46 -0
- package/build/changelogs.js.map +1 -0
- package/build/debug.d.ts +3 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +8 -0
- package/build/debug.js.map +1 -0
- package/build/generated/changelogCatalog.d.ts +9 -0
- package/build/generated/changelogCatalog.d.ts.map +1 -0
- package/build/generated/changelogCatalog.js +12 -0
- package/build/generated/changelogCatalog.js.map +1 -0
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +171 -0
- package/build/index.js.map +1 -0
- package/build/ota-server/OtaServer.types.d.ts +14 -0
- package/build/ota-server/OtaServer.types.d.ts.map +1 -0
- package/build/ota-server/OtaServer.types.js +2 -0
- package/build/ota-server/OtaServer.types.js.map +1 -0
- package/build/ota-server/OtaServerModule.d.ts +21 -0
- package/build/ota-server/OtaServerModule.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.js +3 -0
- package/build/ota-server/OtaServerModule.js.map +1 -0
- package/build/ota-server/OtaServerModule.web.d.ts +10 -0
- package/build/ota-server/OtaServerModule.web.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.web.js +12 -0
- package/build/ota-server/OtaServerModule.web.js.map +1 -0
- package/build/ota-server/index.d.ts +3 -0
- package/build/ota-server/index.d.ts.map +1 -0
- package/build/ota-server/index.js +3 -0
- package/build/ota-server/index.js.map +1 -0
- package/build/ota-transport/index.d.ts +67 -0
- package/build/ota-transport/index.d.ts.map +1 -0
- package/build/ota-transport/index.js +58 -0
- package/build/ota-transport/index.js.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts +18 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.js +2 -0
- package/build/photo-receiver/PhotoReceiver.types.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.js +3 -0
- package/build/photo-receiver/PhotoReceiverModule.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js +12 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js.map +1 -0
- package/build/photo-receiver/index.d.ts +3 -0
- package/build/photo-receiver/index.d.ts.map +1 -0
- package/build/photo-receiver/index.js +3 -0
- package/build/photo-receiver/index.js.map +1 -0
- package/build/react/index.d.ts +5 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +4 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useBluetooth.d.ts +100 -0
- package/build/react/useBluetooth.d.ts.map +1 -0
- package/build/react/useBluetooth.js +167 -0
- package/build/react/useBluetooth.js.map +1 -0
- package/build/react/useBluetoothEvent.d.ts +6 -0
- package/build/react/useBluetoothEvent.d.ts.map +1 -0
- package/build/react/useBluetoothEvent.js +21 -0
- package/build/react/useBluetoothEvent.js.map +1 -0
- package/build/react/useBluetoothScan.d.ts +22 -0
- package/build/react/useBluetoothScan.d.ts.map +1 -0
- package/build/react/useBluetoothScan.js +135 -0
- package/build/react/useBluetoothScan.js.map +1 -0
- package/build/react/useBluetoothStatus.d.ts +16 -0
- package/build/react/useBluetoothStatus.d.ts.map +1 -0
- package/build/react/useBluetoothStatus.js +137 -0
- package/build/react/useBluetoothStatus.js.map +1 -0
- package/build/react/useGlassesConnection.d.ts +29 -0
- package/build/react/useGlassesConnection.d.ts.map +1 -0
- package/build/react/useGlassesConnection.js +153 -0
- package/build/react/useGlassesConnection.js.map +1 -0
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/expo-module.config.json +14 -0
- package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
- package/ios/BluetoothSdkModule.swift +1034 -0
- package/ios/GlasslyBluetoothSDK.podspec +106 -0
- package/ios/LocalIPv4.swift +150 -0
- package/ios/LocalOtaServer.swift +309 -0
- package/ios/LocalPhotoUploadServer.swift +754 -0
- package/ios/OtaServerModule.swift +147 -0
- package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
- package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
- package/ios/Packages/CoreObjC/attdet.c +92 -0
- package/ios/Packages/CoreObjC/attdet.h +44 -0
- package/ios/Packages/CoreObjC/bits.c +375 -0
- package/ios/Packages/CoreObjC/bits.h +315 -0
- package/ios/Packages/CoreObjC/bwdet.c +129 -0
- package/ios/Packages/CoreObjC/bwdet.h +69 -0
- package/ios/Packages/CoreObjC/common.h +151 -0
- package/ios/Packages/CoreObjC/energy.c +70 -0
- package/ios/Packages/CoreObjC/energy.h +43 -0
- package/ios/Packages/CoreObjC/fastmath.h +158 -0
- package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/lc3.c +704 -0
- package/ios/Packages/CoreObjC/lc3.h +313 -0
- package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
- package/ios/Packages/CoreObjC/lc3_private.h +163 -0
- package/ios/Packages/CoreObjC/ltpf.c +905 -0
- package/ios/Packages/CoreObjC/ltpf.h +111 -0
- package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
- package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
- package/ios/Packages/CoreObjC/makefile.mk +35 -0
- package/ios/Packages/CoreObjC/mdct.c +469 -0
- package/ios/Packages/CoreObjC/mdct.h +57 -0
- package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
- package/ios/Packages/CoreObjC/meson.build +61 -0
- package/ios/Packages/CoreObjC/plc.c +61 -0
- package/ios/Packages/CoreObjC/plc.h +57 -0
- package/ios/Packages/CoreObjC/rnnoise.h +114 -0
- package/ios/Packages/CoreObjC/sns.c +880 -0
- package/ios/Packages/CoreObjC/sns.h +103 -0
- package/ios/Packages/CoreObjC/spec.c +907 -0
- package/ios/Packages/CoreObjC/spec.h +119 -0
- package/ios/Packages/CoreObjC/tables.c +3457 -0
- package/ios/Packages/CoreObjC/tables.h +94 -0
- package/ios/Packages/CoreObjC/tns.c +457 -0
- package/ios/Packages/CoreObjC/tns.h +99 -0
- package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
- package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +2383 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +48 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Packages/libbz2/module.modulemap +5 -0
- package/ios/Packages/libbz2/shim.h +1 -0
- package/ios/PhotoReceiverModule.swift +129 -0
- package/ios/Source/BluetoothSDK.swift +2303 -0
- package/ios/Source/BluetoothSDKDebug.swift +12 -0
- package/ios/Source/BluetoothSdkDefaults.swift +29 -0
- package/ios/Source/Bridge.swift +708 -0
- package/ios/Source/Bridging-Header.h +16 -0
- package/ios/Source/DeviceManager.swift +2109 -0
- package/ios/Source/DeviceStore.swift +336 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +7 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
- package/ios/Source/ObservableStore.swift +118 -0
- package/ios/Source/OtaManifest.swift +211 -0
- package/ios/Source/PrivacyInfo.xcprivacy +23 -0
- package/ios/Source/ReleaseChangelog.swift +65 -0
- package/ios/Source/Resources/G2Firmware/README.md +10 -0
- package/ios/Source/audio/AudioModels.swift +178 -0
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +866 -0
- package/ios/Source/connection/ScanSession.swift +27 -0
- package/ios/Source/controllers/ControllerManager.swift +184 -0
- package/ios/Source/controllers/R1.swift +829 -0
- package/ios/Source/errors/BluetoothSdkError.swift +19 -0
- package/ios/Source/events/BluetoothEvents.swift +342 -0
- package/ios/Source/internal/BleTraceLogger.swift +192 -0
- package/ios/Source/internal/BluetoothAvailability.swift +78 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +199 -0
- package/ios/Source/internal/ValueParsing.swift +99 -0
- package/ios/Source/requests/DisplayRequests.swift +82 -0
- package/ios/Source/services/PhoneMic.swift +662 -0
- package/ios/Source/sgcs/Ar99.swift +2451 -0
- package/ios/Source/sgcs/Frame.swift +649 -0
- package/ios/Source/sgcs/G1.swift +2554 -0
- package/ios/Source/sgcs/G2.swift +6887 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +43 -0
- package/ios/Source/sgcs/G2CfwRenderer.swift +1279 -0
- package/ios/Source/sgcs/G2CfwTextureCache.swift +233 -0
- package/ios/Source/sgcs/G2FlashProtocol.swift +312 -0
- package/ios/Source/sgcs/G2Flasher.swift +773 -0
- package/ios/Source/sgcs/Mach1.swift +541 -0
- package/ios/Source/sgcs/MentraLive.swift +7011 -0
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
- package/ios/Source/sgcs/MentraNex.swift +3221 -0
- package/ios/Source/sgcs/Nimo.swift +1886 -0
- package/ios/Source/sgcs/SGCManager.swift +608 -0
- package/ios/Source/sgcs/Simulated.swift +274 -0
- package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
- package/ios/Source/sgcs/glassly_ble.pb.swift +4354 -0
- package/ios/Source/speakerid/SpeakerIdEngine.swift +573 -0
- package/ios/Source/status/DeviceStatus.swift +981 -0
- package/ios/Source/status/RuntimeState.swift +359 -0
- package/ios/Source/status/WifiHotspotStatus.swift +345 -0
- package/ios/Source/streaming/StreamModels.swift +693 -0
- package/ios/Source/stt/STTTools.swift +191 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +466 -0
- package/ios/Source/tts/TTSTools.swift +171 -0
- package/ios/Source/types/DeviceModels.swift +177 -0
- package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
- package/ios/Source/utils/BinaryCodec.swift +61 -0
- package/ios/Source/utils/BleJsonCompact.swift +405 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/Constants.swift +78 -0
- package/ios/Source/utils/Enums.swift +95 -0
- package/ios/Source/utils/G1Text.swift +2096 -0
- package/ios/Source/utils/JSCExperiment.swift +241 -0
- package/ios/Source/utils/MemoryMonitor.swift +44 -0
- package/ios/Source/utils/MessageChunkReassembler.swift +190 -0
- package/ios/Source/utils/MessageChunker.swift +294 -0
- package/ios/Source/utils/Models.swift +135 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +230 -0
- package/ios/Source/utils/TarBz2Extractor.swift +257 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +78 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +372 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +162 -0
- package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +182 -0
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
- package/ios/Tests/ReleaseChangelogTests.swift +13 -0
- package/package.json +129 -0
- package/plugin/build/index.d.ts +10 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.d.ts +3 -0
- package/plugin/build/withAndroid.js +190 -0
- package/plugin/build/withIos.d.ts +5 -0
- package/plugin/build/withIos.js +108 -0
- package/scripts/inject-ios-sdk-version.mjs +65 -0
- package/scripts/public-ota-api.test.mjs +37 -0
- package/scripts/verify-release-package.mjs +45 -0
- package/scripts/write-release-metadata.mjs +152 -0
- package/scripts/write-release-metadata.test.mjs +37 -0
- package/src/BluetoothSdk.types.ts +1623 -0
- package/src/_internal.ts +16 -0
- package/src/_private/BluetoothSdkModule.ts +684 -0
- package/src/_private/LocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +34 -0
- package/src/_private/photoRequestPayload.ts +90 -0
- package/src/_private/sdkOtaManifest.ts +18 -0
- package/src/changelogs.ts +46 -0
- package/src/debug.ts +9 -0
- package/src/generated/changelogCatalog.ts +13 -0
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +321 -0
- package/src/ota-server/OtaServer.types.ts +10 -0
- package/src/ota-server/OtaServerModule.ts +23 -0
- package/src/ota-server/OtaServerModule.web.ts +17 -0
- package/src/ota-server/index.ts +2 -0
- package/src/ota-transport/index.ts +116 -0
- package/src/photo-receiver/PhotoReceiver.types.ts +20 -0
- package/src/photo-receiver/PhotoReceiverModule.ts +11 -0
- package/src/photo-receiver/PhotoReceiverModule.web.ts +17 -0
- package/src/photo-receiver/index.ts +2 -0
- package/src/react/index.ts +21 -0
- package/src/react/useBluetooth.ts +309 -0
- package/src/react/useBluetoothEvent.ts +35 -0
- package/src/react/useBluetoothScan.ts +173 -0
- package/src/react/useBluetoothStatus.ts +180 -0
- package/src/react/useGlassesConnection.ts +202 -0
- 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
|
+
}
|