@mentra/bluetooth-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -0
- package/android/build.gradle +167 -0
- package/android/lc3Lib/CMakeLists.txt +6 -0
- package/android/lc3Lib/build.gradle +109 -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/mentra/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 +1 -0
- package/android/src/main/AndroidManifest.xml +40 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +528 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +610 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +1616 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +314 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +1710 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +554 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/ObservableStore.kt +68 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +158 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +934 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +175 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +1109 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +3974 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3551 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1327 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +6959 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1655 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +53675 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +202 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +224 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +340 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +465 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +221 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/AES.java +106 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +179 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +44 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/G1Text.kt +436 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +76 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +791 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +186 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +589 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +303 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/app.plugin.js +1 -0
- package/build/BluetoothSdk.types.d.ts +498 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -0
- package/build/BluetoothSdk.types.js +23 -0
- package/build/BluetoothSdk.types.js.map +1 -0
- package/build/BluetoothSdkModule.d.ts +71 -0
- package/build/BluetoothSdkModule.d.ts.map +1 -0
- package/build/BluetoothSdkModule.js +106 -0
- package/build/BluetoothSdkModule.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +5 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/BluetoothSdkModule.swift +665 -0
- package/ios/MentraBluetoothSDK.podspec +77 -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/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 +1659 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +162 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +1852 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +674 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +44 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
- package/ios/Packages/VAD/Common/VAD/VADQuality.swift +41 -0
- package/ios/Packages/VAD/Common/VAD/VADState.swift +26 -0
- package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +29 -0
- package/ios/Packages/VAD/Common/VAD/VADType.swift +14 -0
- package/ios/Packages/VAD/Data/Configuration.swift +68 -0
- package/ios/Packages/VAD/Data/FrameSize.swift +39 -0
- package/ios/Packages/VAD/Data/Record.swift +13 -0
- package/ios/Packages/VAD/Data/Result.swift +22 -0
- package/ios/Packages/VAD/Data/SampleRate.swift +48 -0
- package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
- package/ios/Packages/VAD/Silero/SileroVAD.swift +284 -0
- package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +64 -0
- package/ios/Packages/libbz2/module.modulemap +5 -0
- package/ios/Packages/libbz2/shim.h +1 -0
- package/ios/Source/Bridge.swift +353 -0
- package/ios/Source/Bridging-Header.h +16 -0
- package/ios/Source/DeviceManager.swift +1478 -0
- package/ios/Source/DeviceStore.swift +295 -0
- package/ios/Source/MentraBluetoothSDK.swift +2439 -0
- package/ios/Source/ObservableStore.swift +88 -0
- package/ios/Source/PrivacyInfo.xcprivacy +23 -0
- package/ios/Source/controllers/ControllerManager.swift +187 -0
- package/ios/Source/controllers/R1.swift +827 -0
- package/ios/Source/services/PhoneMic.swift +662 -0
- package/ios/Source/sgcs/Frame.swift +651 -0
- package/ios/Source/sgcs/G1.swift +2505 -0
- package/ios/Source/sgcs/G2.swift +3730 -0
- package/ios/Source/sgcs/Mach1.swift +543 -0
- package/ios/Source/sgcs/MentraLive.swift +4708 -0
- package/ios/Source/sgcs/MentraNex.swift +2479 -0
- package/ios/Source/sgcs/SGCManager.swift +243 -0
- package/ios/Source/sgcs/Simulated.swift +270 -0
- package/ios/Source/sgcs/mentraos_ble.pb.swift +3811 -0
- package/ios/Source/stt/STTTools.swift +154 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +401 -0
- package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
- package/ios/Source/utils/Constants.swift +74 -0
- package/ios/Source/utils/Enums.swift +95 -0
- package/ios/Source/utils/G1Text.swift +2067 -0
- package/ios/Source/utils/JSCExperiment.swift +241 -0
- package/ios/Source/utils/MemoryMonitor.swift +44 -0
- package/ios/Source/utils/MessageChunker.swift +164 -0
- package/ios/Source/utils/Models.swift +135 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +226 -0
- package/ios/Source/utils/TarBz2Extractor.swift +206 -0
- package/package.json +78 -0
- package/plugin/build/index.d.ts +6 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.d.ts +4 -0
- package/plugin/build/withAndroid.js +80 -0
- package/plugin/build/withIos.d.ts +4 -0
- package/plugin/build/withIos.js +64 -0
- package/src/BluetoothSdk.types.ts +581 -0
- package/src/BluetoothSdkModule.ts +259 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1,1655 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk.sgcs
|
|
2
|
+
|
|
3
|
+
import com.mentra.bluetoothsdk.DeviceManager
|
|
4
|
+
import com.mentra.bluetoothsdk.DeviceStore
|
|
5
|
+
|
|
6
|
+
import android.graphics.BitmapFactory
|
|
7
|
+
import android.bluetooth.BluetoothAdapter
|
|
8
|
+
import android.bluetooth.BluetoothDevice
|
|
9
|
+
import android.bluetooth.BluetoothGatt
|
|
10
|
+
import android.bluetooth.BluetoothGattCallback
|
|
11
|
+
import android.bluetooth.BluetoothGattDescriptor
|
|
12
|
+
import android.bluetooth.BluetoothGattService
|
|
13
|
+
import android.bluetooth.BluetoothGattCharacteristic
|
|
14
|
+
import android.bluetooth.BluetoothProfile
|
|
15
|
+
import android.bluetooth.le.BluetoothLeScanner
|
|
16
|
+
import android.bluetooth.le.ScanCallback
|
|
17
|
+
import android.bluetooth.le.ScanFilter
|
|
18
|
+
import android.bluetooth.le.ScanResult
|
|
19
|
+
import android.bluetooth.le.ScanSettings
|
|
20
|
+
import android.os.Message
|
|
21
|
+
import android.os.Handler
|
|
22
|
+
import android.os.Looper
|
|
23
|
+
import android.util.Log
|
|
24
|
+
import android.content.Context
|
|
25
|
+
|
|
26
|
+
import mentraos.ble.MentraosBle.GlassesToPhone
|
|
27
|
+
import mentraos.ble.MentraosBle.PhoneToGlasses
|
|
28
|
+
import mentraos.ble.MentraosBle.ChargingState
|
|
29
|
+
import mentraos.ble.MentraosBle.BatteryStatus
|
|
30
|
+
import mentraos.ble.MentraosBle.VersionResponse
|
|
31
|
+
import mentraos.ble.MentraosBle.HeadGesture
|
|
32
|
+
import mentraos.ble.MentraosBle.ButtonEvent
|
|
33
|
+
import mentraos.ble.MentraosBle.ImuData
|
|
34
|
+
import mentraos.ble.MentraosBle.ImageTransferComplete
|
|
35
|
+
import mentraos.ble.MentraosBle.HeadUpAngleResponse
|
|
36
|
+
import mentraos.ble.MentraosBle.HeadPosition
|
|
37
|
+
import mentraos.ble.MentraosBle.DeviceInfo
|
|
38
|
+
|
|
39
|
+
import com.mentra.bluetoothsdk.sgcs.SGCManager
|
|
40
|
+
import com.mentra.bluetoothsdk.Bridge
|
|
41
|
+
import com.mentra.bluetoothsdk.utils.DeviceTypes
|
|
42
|
+
import com.mentra.bluetoothsdk.utils.NexProtobufUtils
|
|
43
|
+
import com.mentra.bluetoothsdk.utils.NexEventUtils
|
|
44
|
+
import com.mentra.bluetoothsdk.utils.NexBluetoothConstants
|
|
45
|
+
import com.mentra.bluetoothsdk.utils.NexDisplayConstants
|
|
46
|
+
import com.mentra.bluetoothsdk.utils.NexBluetoothPacketTypes
|
|
47
|
+
import com.mentra.bluetoothsdk.utils.BitmapJavaUtils
|
|
48
|
+
import com.mentra.bluetoothsdk.utils.G1FontLoaderKt
|
|
49
|
+
import com.mentra.bluetoothsdk.utils.G1Text
|
|
50
|
+
import com.mentra.bluetoothsdk.utils.ConnTypes
|
|
51
|
+
import com.mentra.lc3Lib.Lc3Cpp
|
|
52
|
+
import com.mentra.bluetoothsdk.utils.audio.Lc3Player
|
|
53
|
+
|
|
54
|
+
import java.util.UUID
|
|
55
|
+
import java.util.concurrent.LinkedBlockingQueue
|
|
56
|
+
import java.nio.charset.Charset
|
|
57
|
+
import java.util.Random
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
import org.json.JSONObject
|
|
61
|
+
|
|
62
|
+
import com.google.protobuf.InvalidProtocolBufferException
|
|
63
|
+
|
|
64
|
+
class MentraNex : SGCManager() {
|
|
65
|
+
companion object {
|
|
66
|
+
private const val TAG = "MentraNex";
|
|
67
|
+
|
|
68
|
+
private const val MTU_517: Int = 517
|
|
69
|
+
private const val MTU_DEFAULT: Int = 23
|
|
70
|
+
|
|
71
|
+
private const val MAX_CHUNK_SIZE_DEFAULT: Int = 176 // Maximum chunk size for BLE packets
|
|
72
|
+
private const val DELAY_BETWEEN_CHUNKS_SEND: Long = 10 // Adjust this value as needed
|
|
73
|
+
|
|
74
|
+
private const val INITIAL_CONNECTION_DELAY_MS = 350L // Adjust this value as needed
|
|
75
|
+
private const val MICBEAT_INTERVAL_MS: Long = (1000 * 60) * 30; // micbeat every 30 minutes
|
|
76
|
+
|
|
77
|
+
private const val MAIN_TASK_HANDLER_CODE_GATT_STATUS_CHANGED: Int = 110
|
|
78
|
+
private const val MAIN_TASK_HANDLER_CODE_DISCOVER_SERVICES: Int = 120
|
|
79
|
+
private const val MAIN_TASK_HANDLER_CODE_CHARACTERISTIC_VALUE_NOTIFIED: Int = 210
|
|
80
|
+
|
|
81
|
+
// actions of device or gatt
|
|
82
|
+
private const val MAIN_TASK_HANDLER_CODE_CONNECT_DEVICE: Int = 310
|
|
83
|
+
private const val MAIN_TASK_HANDLER_CODE_DISCONNECT_DEVICE: Int = 320
|
|
84
|
+
private const val MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE: Int = 350
|
|
85
|
+
private const val MAIN_TASK_HANDLER_CODE_CANCEL_RECONNECT_DEVICE: Int = 360
|
|
86
|
+
private const val MAIN_TASK_HANDLER_CODE_RECONNECT_GATT: Int = 370
|
|
87
|
+
private const val MAIN_TASK_HANDLER_CODE_SCAN_START: Int = 410
|
|
88
|
+
private const val MAIN_TASK_HANDLER_CODE_SCAN_END: Int = 420
|
|
89
|
+
|
|
90
|
+
// actions of NEX Glasses
|
|
91
|
+
private const val MAIN_TASK_HANDLER_CODE_BATTERY_QUERY: Int = 620
|
|
92
|
+
private const val MAIN_TASK_HANDLER_CODE_HEART_BEAT: Int = 630
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private var heartbeatCount: Int = 0;
|
|
96
|
+
private var micBeatCount: Int = 0;
|
|
97
|
+
|
|
98
|
+
private var context: Context? = null
|
|
99
|
+
// private var isDebug: Boolean = true
|
|
100
|
+
|
|
101
|
+
private var isLc3AudioEnabled: Boolean = true
|
|
102
|
+
private var lc3AudioPlayer: Lc3Player? = null
|
|
103
|
+
|
|
104
|
+
private var lc3DecoderPtr: Long = 0
|
|
105
|
+
|
|
106
|
+
@Volatile private var isImageSendProgressing: Boolean = false
|
|
107
|
+
|
|
108
|
+
private var mainDevice: BluetoothDevice? = null
|
|
109
|
+
private var bluetoothAdapter: BluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
|
|
110
|
+
|
|
111
|
+
private var isScanningForCompatibleDevices: Boolean = false
|
|
112
|
+
|
|
113
|
+
private var isMainConnected = false
|
|
114
|
+
private var isKilled: Boolean = false
|
|
115
|
+
private var lastConnectionTimestamp: Long = 0
|
|
116
|
+
private var lastSendTimestamp: Long = 0
|
|
117
|
+
private var mainReconnectAttempts: Int = 0
|
|
118
|
+
|
|
119
|
+
private var lastReceivedLc3Sequence: Int = -1
|
|
120
|
+
|
|
121
|
+
private val fontLoader: G1FontLoaderKt = G1FontLoaderKt()
|
|
122
|
+
private val textRenderer: G1Text = G1Text()
|
|
123
|
+
private var preferredMainDeviceId: String? = null
|
|
124
|
+
private var savedNexMainName: String? = null
|
|
125
|
+
private var savedNexMainAddress: String? = null
|
|
126
|
+
|
|
127
|
+
private var mainGlassGatt: BluetoothGatt? = null
|
|
128
|
+
private var mainWriteChar: BluetoothGattCharacteristic? = null
|
|
129
|
+
private var mainNotifyChar: BluetoothGattCharacteristic? = null
|
|
130
|
+
private var currentMTU: Int = 0
|
|
131
|
+
private var deviceMaxMTU: Int = 0
|
|
132
|
+
|
|
133
|
+
private var maxChunkSize: Int = MAX_CHUNK_SIZE_DEFAULT // Maximum chunk size for BLE packets
|
|
134
|
+
private var bmpChunkSize: Int = 194 // BMP chunk size
|
|
135
|
+
|
|
136
|
+
@Volatile private var isWorkerRunning = false
|
|
137
|
+
// Queue to hold pending requests
|
|
138
|
+
private val sendQueue = LinkedBlockingQueue<Array<SendRequest>>()
|
|
139
|
+
private val mainWaiter = BooleanWaiter()
|
|
140
|
+
private val mainServicesWaiter = BooleanWaiter()
|
|
141
|
+
|
|
142
|
+
private var shouldUseGlassesMic: Boolean = false
|
|
143
|
+
private var microphoneStateBeforeDisconnection: Boolean = false
|
|
144
|
+
|
|
145
|
+
private var whiteListedAlready: Boolean = false
|
|
146
|
+
private var isMicrophoneEnabled: Boolean = true
|
|
147
|
+
private var lastThingDisplayedWasAnImage: Boolean = false
|
|
148
|
+
private var isScanning: Boolean = false
|
|
149
|
+
|
|
150
|
+
private var protobufVersionPosted: Boolean = false
|
|
151
|
+
|
|
152
|
+
private var hasConnectedThisSession: Boolean = false
|
|
153
|
+
|
|
154
|
+
private var bleScanCallback: ScanCallback? = null
|
|
155
|
+
|
|
156
|
+
private val modernScanCallback = object : ScanCallback() {
|
|
157
|
+
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
|
158
|
+
val device = result.getDevice()
|
|
159
|
+
val name = device.getName()
|
|
160
|
+
|
|
161
|
+
// Now you can reference the bluetoothAdapter field if needed:
|
|
162
|
+
if (!bluetoothAdapter.isEnabled) {
|
|
163
|
+
Log.e(TAG, "Bluetooth is disabled")
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
Bridge.log("=== New Device is Found ===$name ${device.address}")
|
|
167
|
+
Bridge.log("=== New Glasses Device Information ===")
|
|
168
|
+
|
|
169
|
+
// Log all available device information for debugging
|
|
170
|
+
Bridge.log("=== Device Information ===")
|
|
171
|
+
Bridge.log("Device Name: $name")
|
|
172
|
+
Bridge.log("Device Address: ${device.address}")
|
|
173
|
+
Bridge.log("Device Type: ${device.type}")
|
|
174
|
+
Bridge.log("Device Class: ${device.bluetoothClass}")
|
|
175
|
+
Bridge.log("Bond State: ${device.bondState}")
|
|
176
|
+
|
|
177
|
+
if (name == null || (!name.contains("Nex1-") && !name.contains("MENTRA_DISPLAY_"))) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// If we already have saved device names for main...
|
|
182
|
+
if (name != null && preferredMainDeviceId != null) {
|
|
183
|
+
if (!name.contains(preferredMainDeviceId!!)) {
|
|
184
|
+
return // Not a matching device
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Identify which side (main)
|
|
189
|
+
stopScan()
|
|
190
|
+
mainDevice = device
|
|
191
|
+
mainTaskHandler.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 0) // 1 second delay
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
override fun onScanFailed(errorCode: Int) {
|
|
195
|
+
Log.e(TAG,"Scan failed with error: $errorCode")
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
init {
|
|
200
|
+
context = Bridge.getContext()
|
|
201
|
+
// isDebug = isDebug(context)
|
|
202
|
+
type = DeviceTypes.NEX
|
|
203
|
+
hasMic = true
|
|
204
|
+
DeviceStore.apply("glasses", "micEnabled", false)
|
|
205
|
+
preferredMainDeviceId = DeviceManager.getInstance().deviceName
|
|
206
|
+
|
|
207
|
+
// Initialize LC3 audio player
|
|
208
|
+
lc3AudioPlayer = Lc3Player(context)
|
|
209
|
+
lc3AudioPlayer?.init()
|
|
210
|
+
if (isLc3AudioEnabled) {
|
|
211
|
+
lc3AudioPlayer?.startPlay()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private val random: Random = Random()
|
|
216
|
+
|
|
217
|
+
private val mainGattCallback: BluetoothGattCallback = createGattCallback()
|
|
218
|
+
|
|
219
|
+
private var mainTaskHandler: Handler = Handler(Looper.getMainLooper(), Handler.Callback(::handleMainTaskMessage))
|
|
220
|
+
private var whiteListHandler: Handler = Handler()
|
|
221
|
+
private var micEnableHandler: Handler = Handler()
|
|
222
|
+
private var micBeatHandler: Handler = Handler()
|
|
223
|
+
private var micBeatRunnable: Runnable? = null
|
|
224
|
+
private var notificationHandler: Handler = Handler()
|
|
225
|
+
private var notificationRunnable: Runnable? = null
|
|
226
|
+
private var textWallHandler: Handler = Handler()
|
|
227
|
+
private val textWallRunnable: Runnable? = null
|
|
228
|
+
private var findCompatibleDevicesHandler: Handler? = null
|
|
229
|
+
|
|
230
|
+
private var lastHeartbeatSentTime: Long = 0
|
|
231
|
+
private var lastHeartbeatReceivedTime: Long = 0
|
|
232
|
+
|
|
233
|
+
private var currentImageChunks: MutableList<ByteArray> = mutableListOf()
|
|
234
|
+
|
|
235
|
+
// Data class to represent a send request
|
|
236
|
+
private data class SendRequest( val data: ByteArray, var waitTime: Int = -1) {
|
|
237
|
+
override fun equals(other: Any?): Boolean {
|
|
238
|
+
if (this === other) return true
|
|
239
|
+
if (javaClass != other?.javaClass) return false
|
|
240
|
+
|
|
241
|
+
other as SendRequest
|
|
242
|
+
|
|
243
|
+
if (!data.contentEquals(other.data)) return false
|
|
244
|
+
if (waitTime != other.waitTime) return false
|
|
245
|
+
|
|
246
|
+
return true
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
override fun hashCode(): Int {
|
|
250
|
+
var result = data.contentHashCode()
|
|
251
|
+
result = 31 * result + waitTime
|
|
252
|
+
return result
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private class BooleanWaiter {
|
|
257
|
+
@Volatile
|
|
258
|
+
private var flag = true // initially true
|
|
259
|
+
|
|
260
|
+
@Synchronized
|
|
261
|
+
fun waitWhileTrue() {
|
|
262
|
+
while (flag) {
|
|
263
|
+
(this as Object).wait()
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@Synchronized
|
|
268
|
+
fun setTrue() {
|
|
269
|
+
flag = true
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@Synchronized
|
|
273
|
+
fun setFalse() {
|
|
274
|
+
flag = false
|
|
275
|
+
(this as Object).notifyAll()
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Network Management
|
|
280
|
+
override fun requestWifiScan () { Bridge.log("Nex: requestWifiScan operation not supported") }
|
|
281
|
+
override fun sendWifiCredentials(ssid: String, password: String) { Bridge.log("Nex: sendWifiCredentials operation not supported") }
|
|
282
|
+
override fun forgetWifiNetwork(ssid: String) { }
|
|
283
|
+
override fun sendHotspotState(enabled: Boolean) { Bridge.log("Nex: sendHotspotState operation not supported") }
|
|
284
|
+
|
|
285
|
+
// Gallery: Not supported on Nex (No camera)
|
|
286
|
+
override fun queryGalleryStatus() { Bridge.log("Nex: queryGalleryStatus operation not supported") }
|
|
287
|
+
override fun sendGalleryMode() { Bridge.log("Nex: sendGalleryMode operation not supported") }
|
|
288
|
+
|
|
289
|
+
// Version info: Not supported on Nex (uses protobuf for version info)
|
|
290
|
+
override fun requestVersionInfo() { Bridge.log("Nex: requestVersionInfo operation not supported") }
|
|
291
|
+
|
|
292
|
+
// Camera & Media: Not supported on Nex (No camera)
|
|
293
|
+
override fun requestPhoto(requestId: String, appId: String, size: String, webhookUrl: String?, authToken: String?, compress: String?, flash: Boolean, sound: Boolean) { Bridge.log("Nex: requestPhoto operation not supported") }
|
|
294
|
+
override fun startStream(message: MutableMap<String, Any>) { Bridge.log("Nex: startStream operation not supported") }
|
|
295
|
+
override fun stopStream() { Bridge.log("Nex: stopStream operation not supported") }
|
|
296
|
+
override fun sendStreamKeepAlive(message: MutableMap<String, Any>) { Bridge.log("Nex: sendStreamKeepAlive operation not supported") }
|
|
297
|
+
override fun startVideoRecording(requestId: String, save: Boolean, flash: Boolean, sound: Boolean) { Bridge.log("Nex: startVideoRecording operation not supported") }
|
|
298
|
+
override fun stopVideoRecording(requestId: String) { Bridge.log("Nex: stopVideoRecording operation not supported") }
|
|
299
|
+
|
|
300
|
+
// Button Settings: Not supported on Nex
|
|
301
|
+
override fun sendButtonPhotoSettings() { Bridge.log("Nex: sendButtonPhotoSettings operation not supported") }
|
|
302
|
+
override fun sendButtonVideoRecordingSettings() { Bridge.log("Nex: sendButtonVideoRecordingSettings operation not supported") }
|
|
303
|
+
override fun sendButtonMaxRecordingTime() { Bridge.log("Nex: sendButtonMaxRecordingTime operation not supported") }
|
|
304
|
+
override fun sendButtonCameraLedSetting() { Bridge.log("Nex: sendButtonCameraLedSetting operation not supported") }
|
|
305
|
+
|
|
306
|
+
override fun sendCameraFovSetting() { Bridge.log("Nex: sendCameraFovSetting operation not supported") }
|
|
307
|
+
|
|
308
|
+
override fun sendUserEmailToGlasses(email: String) { Bridge.log("Nex: sendUserEmailToGlasses operation not supported") }
|
|
309
|
+
|
|
310
|
+
override fun sendIncidentId(incidentId: String, apiBaseUrl: String?) {
|
|
311
|
+
Bridge.log("Nex: sendIncidentId operation not supported")
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Connection
|
|
315
|
+
override fun findCompatibleDevices() {
|
|
316
|
+
if (isScanningForCompatibleDevices) {
|
|
317
|
+
Bridge.log("Scan already in progress, skipping...")
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
isScanningForCompatibleDevices = true
|
|
322
|
+
val scanner = bluetoothAdapter.bluetoothLeScanner ?: run {
|
|
323
|
+
Log.e(TAG, "BluetoothLeScanner not available")
|
|
324
|
+
isScanningForCompatibleDevices = false
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
val foundDeviceNames = mutableListOf<String>()
|
|
329
|
+
if (findCompatibleDevicesHandler == null) {
|
|
330
|
+
findCompatibleDevicesHandler = Handler(Looper.getMainLooper())
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Optional: add filters if you want to narrow the scan
|
|
334
|
+
val filters = mutableListOf<ScanFilter>()
|
|
335
|
+
val settings = ScanSettings.Builder()
|
|
336
|
+
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
|
|
337
|
+
.build()
|
|
338
|
+
|
|
339
|
+
// Create a modern ScanCallback instead of the deprecated LeScanCallback
|
|
340
|
+
bleScanCallback = object : ScanCallback() {
|
|
341
|
+
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
|
342
|
+
val device = result.device
|
|
343
|
+
val name = device.name
|
|
344
|
+
val address = device.address
|
|
345
|
+
|
|
346
|
+
name?.let {
|
|
347
|
+
if (it.startsWith("Nex1-") || it.startsWith("MENTRA_DISPLAY_")) {
|
|
348
|
+
Bridge.log("bleScanCallback onScanResult: $name address $address")
|
|
349
|
+
synchronized(foundDeviceNames) {
|
|
350
|
+
if (!foundDeviceNames.contains(it)) {
|
|
351
|
+
foundDeviceNames.add(it)
|
|
352
|
+
Bridge.log("Found smart glasses: $name")
|
|
353
|
+
Bridge.sendDiscoveredDevice(DeviceTypes.NEX, it);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
override fun onBatchScanResults(results: List<ScanResult>) {
|
|
361
|
+
// If needed, handle batch results here
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
override fun onScanFailed(errorCode: Int) {
|
|
365
|
+
Log.e(TAG, "BLE scan failed with code: $errorCode")
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Start scanning
|
|
370
|
+
scanner.startScan(filters, settings, bleScanCallback)
|
|
371
|
+
Bridge.log("Started scanning for smart glasses with BluetoothLeScanner...")
|
|
372
|
+
scanner.flushPendingScanResults(bleScanCallback)
|
|
373
|
+
|
|
374
|
+
// Stop scanning after 10 seconds (adjust as needed)
|
|
375
|
+
findCompatibleDevicesHandler?.postDelayed({
|
|
376
|
+
bleScanCallback?.let { scanner.stopScan(it) }
|
|
377
|
+
isScanningForCompatibleDevices = false
|
|
378
|
+
bleScanCallback = null
|
|
379
|
+
Bridge.log("Stopped scanning for smart glasses.")
|
|
380
|
+
}, 10000)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
override fun connectById(id: String) {
|
|
384
|
+
preferredMainDeviceId = id
|
|
385
|
+
connectToSmartGlasses()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
override fun getConnectedBluetoothName(): String {
|
|
389
|
+
return mainDevice?.getName() ?: ""
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
override fun disconnect() {
|
|
393
|
+
DeviceStore.apply("glasses", "fullyBooted", false)
|
|
394
|
+
destroy();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
override fun forget() {
|
|
398
|
+
DeviceStore.apply("glasses", "fullyBooted", false)
|
|
399
|
+
destroy();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
override fun cleanup() {
|
|
403
|
+
// TODO: Later
|
|
404
|
+
Bridge.log("To be implemented")
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Device
|
|
408
|
+
override fun setHeadUpAngle(angle: Int) {
|
|
409
|
+
// Validate headUpAngle range (0 ~ 60)
|
|
410
|
+
val validatedAngle = angle.coerceIn(0, 60)
|
|
411
|
+
Bridge.log("Nex: === SENDING HEAD UP ANGLE COMMAND TO GLASSES ===")
|
|
412
|
+
Bridge.log("Nex: Head Up Angle: $validatedAngle degrees (validated range: 0-60)")
|
|
413
|
+
val cmdBytes = NexProtobufUtils.generateHeadUpAngleConfigCommandBytes(angle)
|
|
414
|
+
sendDataSequentially(cmdBytes, 10)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
override fun getBatteryStatus() {
|
|
418
|
+
Bridge.log("Nex: Requesting battery status");
|
|
419
|
+
queryBatteryStatus()
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
override fun exit() {
|
|
423
|
+
sendDataSequentially(byteArrayOf(0x18.toByte()), 100)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
override fun sendShutdown() {
|
|
427
|
+
Bridge.log("sendShutdown - not supported on Nex")
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
override fun sendReboot() {
|
|
431
|
+
Bridge.log("sendReboot - not supported on Nex")
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
override fun sendRgbLedControl(requestId: String, packageName: String?, action: String, color: String?, ontime: Int, offtime: Int, count: Int) {
|
|
435
|
+
Bridge.log("sendRgbLedControl - not supported on Nex");
|
|
436
|
+
Bridge.sendRgbLedControlResponse(requestId, false, "device_not_supported");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Not supported in Nex
|
|
440
|
+
override fun setSilentMode(enabled: Boolean) { Bridge.log("Nex: setSilentMode operation not supported") }
|
|
441
|
+
|
|
442
|
+
// Display
|
|
443
|
+
override fun setBrightness(level: Int, autoMode: Boolean) {
|
|
444
|
+
// TODO: test this logic. Is it correct? Should we send both or just one?
|
|
445
|
+
Bridge.log("Nex: setBrightness() - level: " + level + "%, autoMode: " + autoMode);
|
|
446
|
+
val brightnessCmdBytes = NexProtobufUtils.generateBrightnessConfigCommandBytes(level)
|
|
447
|
+
sendDataSequentially(brightnessCmdBytes, 10)
|
|
448
|
+
|
|
449
|
+
val autoBrightnessCmdBytes = NexProtobufUtils.generateAutoBrightnessConfigCommandBytes(autoMode)
|
|
450
|
+
sendDataSequentially(autoBrightnessCmdBytes, 10)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
override fun clearDisplay() {
|
|
454
|
+
Bridge.log("Nex: clearDisplay() - sending clear display request command bytes");
|
|
455
|
+
val clearDisplayPackets = NexProtobufUtils.generateClearDisplayRequestCommandBytes()
|
|
456
|
+
sendDataSequentially(clearDisplayPackets, 10)
|
|
457
|
+
// sendTextWall(" ")
|
|
458
|
+
Bridge.log("Nex: clearDisplay() - sent clear display request command bytes");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
override fun sendTextWall(text: String) {
|
|
462
|
+
Bridge.log("Nex: sendTextWall() - text: " + text);
|
|
463
|
+
val textChunks: ByteArray = createTextWallChunksForNex(text)
|
|
464
|
+
sendDataSequentially(textChunks);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
override fun sendDoubleTextWall(top: String, bottom: String) {
|
|
468
|
+
Bridge.log("Nex: sendDoubleTextWall() - top: " + top + ", bottom: " + bottom);
|
|
469
|
+
val finalText: String = buildString {
|
|
470
|
+
top?.let { append(it) }
|
|
471
|
+
top?.let { append("\n") }
|
|
472
|
+
bottom?.let { append(it) }
|
|
473
|
+
}
|
|
474
|
+
val textChunks = createTextWallChunksForNex(finalText)
|
|
475
|
+
sendDataSequentially(textChunks)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
override fun displayBitmap(base64ImageData: String): Boolean {
|
|
479
|
+
try {
|
|
480
|
+
val bmpData: ByteArray? = android.util.Base64.decode(base64ImageData, android.util.Base64.DEFAULT)
|
|
481
|
+
if (bmpData == null || bmpData.isEmpty()) {
|
|
482
|
+
Log.e(TAG, "Failed to decode base64 image data");
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
val bmp = BitmapFactory.decodeByteArray(bmpData, 0, bmpData.size)
|
|
486
|
+
|
|
487
|
+
displayBitmapImageForNexGlasses(bmpData, bmp.width, bmp.height)
|
|
488
|
+
return true
|
|
489
|
+
|
|
490
|
+
} catch (e: Exception) {
|
|
491
|
+
Log.e(TAG, "Error in displaying bitmap: " + e.message);
|
|
492
|
+
return false
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
}
|
|
496
|
+
override fun showDashboard() {
|
|
497
|
+
exit()
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
override fun setDashboardPosition(height: Int, depth: Int) {
|
|
501
|
+
Bridge.log("Nex: setDashboardPosition() - height: " + height + ", depth: " + depth)
|
|
502
|
+
// Send display_height then display_distance; adjust order if Nex firmware requires otherwise.
|
|
503
|
+
val heightBytes = NexProtobufUtils.generateDisplayHeightCommandBytes(height)
|
|
504
|
+
sendDataSequentially(heightBytes, 10)
|
|
505
|
+
val distanceCm = NexProtobufUtils.dashboardDepthToDistanceCm(depth)
|
|
506
|
+
val distanceBytes = NexProtobufUtils.generateDisplayDistanceCommandBytes(distanceCm)
|
|
507
|
+
sendDataSequentially(distanceBytes, 10)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
override fun setDashboardHeightOnly(height: Int) {
|
|
511
|
+
Bridge.log("Nex: setDashboardHeightOnly() - height: $height")
|
|
512
|
+
val heightBytes = NexProtobufUtils.generateDisplayHeightCommandBytes(height)
|
|
513
|
+
sendDataSequentially(heightBytes, 10)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
override fun setDashboardDepthOnly(depth: Int) {
|
|
517
|
+
Bridge.log("Nex: setDashboardDepthOnly() - depth: $depth")
|
|
518
|
+
val distanceCm = NexProtobufUtils.dashboardDepthToDistanceCm(depth)
|
|
519
|
+
val distanceBytes = NexProtobufUtils.generateDisplayDistanceCommandBytes(distanceCm)
|
|
520
|
+
sendDataSequentially(distanceBytes, 10)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
override fun ping() {
|
|
524
|
+
Bridge.log("Nex: ping()");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
override fun dbg1() {}
|
|
528
|
+
override fun dbg2() {}
|
|
529
|
+
|
|
530
|
+
// Audio Control
|
|
531
|
+
// TODO: Validate this logic. looks weird.
|
|
532
|
+
override fun setMicEnabled(enabled: Boolean) {
|
|
533
|
+
Bridge.log("Nex: setMicEnabled(): $enabled")
|
|
534
|
+
// Update the shouldUseGlassesMic flag to reflect the current state
|
|
535
|
+
shouldUseGlassesMic = enabled
|
|
536
|
+
Bridge.log("Nex: Updated shouldUseGlassesMic to: $shouldUseGlassesMic")
|
|
537
|
+
if (enabled) {
|
|
538
|
+
Bridge.log("Nex: Microphone enabled, starting audio input handling")
|
|
539
|
+
startMicBeat(MICBEAT_INTERVAL_MS.toInt())
|
|
540
|
+
} else {
|
|
541
|
+
Bridge.log("Nex: Microphone disabled, stopping audio input handling")
|
|
542
|
+
stopMicBeat()
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
override fun sortMicRanking(list: MutableList<String>): MutableList<String> {
|
|
547
|
+
return list
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
//==================== HELPER FUNCTIONS =================================================
|
|
551
|
+
private fun createGattCallback(): BluetoothGattCallback {
|
|
552
|
+
return object : BluetoothGattCallback() {
|
|
553
|
+
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
|
554
|
+
Bridge.log("onConnectionStateChange status State ${status == BluetoothGatt.GATT_SUCCESS}")
|
|
555
|
+
Bridge.log("onConnectionStateChange connected State ${newState == BluetoothProfile.STATE_CONNECTED}")
|
|
556
|
+
|
|
557
|
+
if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
558
|
+
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
|
559
|
+
Bridge.log("glass connected, discovering services...")
|
|
560
|
+
gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
|
|
561
|
+
gatt.setPreferredPhy(
|
|
562
|
+
BluetoothDevice.PHY_LE_2M_MASK,
|
|
563
|
+
BluetoothDevice.PHY_LE_2M_MASK,
|
|
564
|
+
BluetoothDevice.PHY_OPTION_NO_PREFERRED
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
isMainConnected = true
|
|
568
|
+
mainReconnectAttempts = 0
|
|
569
|
+
Bridge.log("Both glasses connected. Stopping BLE scan.")
|
|
570
|
+
stopScan()
|
|
571
|
+
|
|
572
|
+
if (!isWorkerRunning) {
|
|
573
|
+
Bridge.log("Worker thread is not running. Starting it.")
|
|
574
|
+
startWorkerIfNeeded()
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
Bridge.log("Discover services calling...")
|
|
578
|
+
gatt.discoverServices()
|
|
579
|
+
updateConnectionState()
|
|
580
|
+
mainDevice?.let { device ->
|
|
581
|
+
savedNexMainName = device.name
|
|
582
|
+
savedNexMainAddress = device.address
|
|
583
|
+
// savePairedDeviceNames()
|
|
584
|
+
// savePairedDeviceAddress()
|
|
585
|
+
}
|
|
586
|
+
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
|
587
|
+
handleDisconnection(gatt)
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
handleConnectionFailure(gatt, status)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private fun handleDisconnection(gatt: BluetoothGatt) {
|
|
595
|
+
Bridge.log("glass disconnected, stopping heartbeats")
|
|
596
|
+
Bridge.log("Entering STATE_DISCONNECTED branch")
|
|
597
|
+
|
|
598
|
+
// Save current microphone state before disconnection
|
|
599
|
+
microphoneStateBeforeDisconnection = isMicrophoneEnabled
|
|
600
|
+
Bridge.log("Saved microphone state before disconnection: $microphoneStateBeforeDisconnection")
|
|
601
|
+
|
|
602
|
+
// Reset protobuf version posted flag for next connection
|
|
603
|
+
protobufVersionPosted = false
|
|
604
|
+
|
|
605
|
+
// Reset chunk sizes to defaults
|
|
606
|
+
maxChunkSize = MAX_CHUNK_SIZE_DEFAULT
|
|
607
|
+
bmpChunkSize = MAX_CHUNK_SIZE_DEFAULT
|
|
608
|
+
mainServicesWaiter.setTrue()
|
|
609
|
+
mainWaiter.setFalse()
|
|
610
|
+
Bridge.log("[BLE] Disconnect: reset mainServicesWaiter=true, mainWaiter=false (unblock any in-flight write). isWorkerRunning=$isWorkerRunning")
|
|
611
|
+
forceSideDisconnection()
|
|
612
|
+
Bridge.log("Called forceSideDisconnection()")
|
|
613
|
+
currentMTU = 0
|
|
614
|
+
|
|
615
|
+
// Stop any periodic transmissions
|
|
616
|
+
stopMicBeat()
|
|
617
|
+
sendQueue.clear()
|
|
618
|
+
Bridge.log("Stopped heartbeat monitoring and mic beat; cleared sendQueue")
|
|
619
|
+
updateConnectionState()
|
|
620
|
+
Bridge.log("Updated connection state after disconnection")
|
|
621
|
+
|
|
622
|
+
// gatt.device?.let {
|
|
623
|
+
// Bridge.log("Closing GATT connection for device: ${it.address}")
|
|
624
|
+
// gatt.disconnect()
|
|
625
|
+
// gatt.close()
|
|
626
|
+
// Bridge.log("GATT connection closed")
|
|
627
|
+
// } ?: run {
|
|
628
|
+
// Bridge.log("No GATT device available to disconnect")
|
|
629
|
+
// }
|
|
630
|
+
|
|
631
|
+
mainTaskHandler?.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 2000)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private fun handleConnectionFailure(gatt: BluetoothGatt, status: Int) {
|
|
635
|
+
// Save current microphone state before connection failure
|
|
636
|
+
microphoneStateBeforeDisconnection = isMicrophoneEnabled
|
|
637
|
+
Bridge.log("Saved microphone state before connection failure: $microphoneStateBeforeDisconnection")
|
|
638
|
+
|
|
639
|
+
currentMTU = 0
|
|
640
|
+
maxChunkSize = MAX_CHUNK_SIZE_DEFAULT
|
|
641
|
+
bmpChunkSize = MAX_CHUNK_SIZE_DEFAULT
|
|
642
|
+
Bridge.log("Unexpected connection state encountered for glass: $status")
|
|
643
|
+
stopMicBeat()
|
|
644
|
+
sendQueue.clear()
|
|
645
|
+
|
|
646
|
+
// Mark as not ready
|
|
647
|
+
mainServicesWaiter.setTrue()
|
|
648
|
+
mainWaiter.setFalse()
|
|
649
|
+
Bridge.log("[BLE] ConnectionFailure: reset mainServicesWaiter=true, mainWaiter=false (unblock any in-flight write). isWorkerRunning=$isWorkerRunning")
|
|
650
|
+
Bridge.log("Stopped heartbeat monitoring and mic beat; cleared sendQueue due to connection failure")
|
|
651
|
+
Bridge.log("glass connection failed with status: $status")
|
|
652
|
+
|
|
653
|
+
isMainConnected = false
|
|
654
|
+
mainReconnectAttempts++
|
|
655
|
+
|
|
656
|
+
mainGlassGatt?.let {
|
|
657
|
+
it.disconnect()
|
|
658
|
+
it.close()
|
|
659
|
+
mainGlassGatt = null
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
forceSideDisconnection()
|
|
663
|
+
Bridge.log("Called forceSideDisconnection() after connection failure")
|
|
664
|
+
Bridge.log("GATT connection disconnected and closed due to failure")
|
|
665
|
+
updateConnectionState()
|
|
666
|
+
|
|
667
|
+
mainTaskHandler?.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 2000)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
private fun forceSideDisconnection() {
|
|
671
|
+
Bridge.log("forceSideDisconnection() called")
|
|
672
|
+
isMainConnected = false
|
|
673
|
+
mainReconnectAttempts++
|
|
674
|
+
Bridge.log("Main glass: Marked as disconnected and incremented mainReconnectAttempts to $mainReconnectAttempts")
|
|
675
|
+
|
|
676
|
+
mainGlassGatt?.let {
|
|
677
|
+
Bridge.log("Main glass GATT exists. Disconnecting and closing mainGlassGatt")
|
|
678
|
+
it.disconnect()
|
|
679
|
+
it.close()
|
|
680
|
+
mainGlassGatt = null
|
|
681
|
+
} ?: run {
|
|
682
|
+
Bridge.log("Main glass GATT is already null")
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
|
687
|
+
val statusBool = status == BluetoothGatt.GATT_SUCCESS
|
|
688
|
+
mainTaskHandler?.obtainMessage(
|
|
689
|
+
MAIN_TASK_HANDLER_CODE_DISCOVER_SERVICES,
|
|
690
|
+
statusBool
|
|
691
|
+
)?.sendToTarget()
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
override fun onCharacteristicWrite(
|
|
695
|
+
gatt: BluetoothGatt,
|
|
696
|
+
characteristic: BluetoothGattCharacteristic,
|
|
697
|
+
status: Int
|
|
698
|
+
) {
|
|
699
|
+
Bridge.log("onCharacteristicWrite callback - ")
|
|
700
|
+
val values = characteristic.value ?: byteArrayOf()
|
|
701
|
+
|
|
702
|
+
if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
703
|
+
Bridge.log("[BLE] onCharacteristicWrite: SUCCESS len=${values.size}")
|
|
704
|
+
val packetHex = values.joinToString("") { "%02X".format(it) }
|
|
705
|
+
Bridge.log("onCharacteristicWrite Values - $packetHex")
|
|
706
|
+
|
|
707
|
+
if (values.isNotEmpty()) {
|
|
708
|
+
val packetType = values[0]
|
|
709
|
+
val protobufData = values.copyOfRange(1, values.size)
|
|
710
|
+
|
|
711
|
+
if (packetType == NexBluetoothPacketTypes.PACKET_TYPE_PROTOBUF) {
|
|
712
|
+
// just for test
|
|
713
|
+
decodeProtobufsByWrite(protobufData, packetHex)
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
Log.e(TAG, "[BLE] onCharacteristicWrite: FAILED status=$status${if (status == 133) " (GATT_ERROR 133 - likely disconnected mid-write)" else ""}")
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// clear the waiter
|
|
721
|
+
Bridge.log("[BLE] onCharacteristicWrite: releasing mainWaiter")
|
|
722
|
+
mainWaiter.setFalse()
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
|
726
|
+
val statusStr = if (status == BluetoothGatt.GATT_SUCCESS) "SUCCESS" else "FAILED($status)"
|
|
727
|
+
Bridge.log("[BLE] onDescriptorWrite: $statusStr — releasing mainServicesWaiter, worker can now send")
|
|
728
|
+
mainServicesWaiter.setFalse()
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
|
|
732
|
+
val statusBool = status == BluetoothGatt.GATT_SUCCESS
|
|
733
|
+
Bridge.log("🔄 MTU Negotiation Result: Success=$statusBool, Device MTU=$mtu, Status=$status")
|
|
734
|
+
|
|
735
|
+
if (statusBool) {
|
|
736
|
+
// Store device capability and calculate actual negotiated MTU
|
|
737
|
+
deviceMaxMTU = mtu // Record what device actually supports
|
|
738
|
+
// The actual negotiated MTU is the minimum of our request and device capability
|
|
739
|
+
currentMTU = MTU_517.coerceAtMost(mtu)
|
|
740
|
+
|
|
741
|
+
Bridge.log("🎯 MTU Negotiation Complete:")
|
|
742
|
+
Bridge.log(" 📱 App Requested: $MTU_517 bytes")
|
|
743
|
+
Bridge.log(" 📡 Device Supports: $deviceMaxMTU bytes")
|
|
744
|
+
Bridge.log(" 🤝 Negotiated MTU: $currentMTU bytes")
|
|
745
|
+
|
|
746
|
+
// Calculate optimal chunk sizes based on negotiated MTU
|
|
747
|
+
maxChunkSize = currentMTU - 10
|
|
748
|
+
bmpChunkSize = currentMTU - 20 // BMP has more config bytes
|
|
749
|
+
|
|
750
|
+
Bridge.log("✅ MTU Configuration Complete:")
|
|
751
|
+
Bridge.log(" 📊 Final MTU: $currentMTU bytes")
|
|
752
|
+
Bridge.log(" 📦 Data Chunk Size: $maxChunkSize bytes")
|
|
753
|
+
Bridge.log(" 🖼️ Image Chunk Size: $bmpChunkSize bytes")
|
|
754
|
+
Bridge.log(" 🔧 Device Maximum: $deviceMaxMTU bytes")
|
|
755
|
+
} else {
|
|
756
|
+
Bridge.log("❌ MTU Request Failed - Status: $status, Requested: $mtu")
|
|
757
|
+
|
|
758
|
+
// Simple fallback strategy: 247 → 23
|
|
759
|
+
if (mtu == MTU_517) {
|
|
760
|
+
Bridge.log("🔄 247 bytes failed, trying default: $MTU_DEFAULT bytes...")
|
|
761
|
+
Bridge.log("📤 Requesting MTU: $MTU_DEFAULT bytes (fallback)")
|
|
762
|
+
gatt.requestMtu(MTU_DEFAULT)
|
|
763
|
+
} else {
|
|
764
|
+
Bridge.log("⚠️ All MTU requests failed, using defaults")
|
|
765
|
+
currentMTU = MTU_DEFAULT
|
|
766
|
+
deviceMaxMTU = MTU_DEFAULT
|
|
767
|
+
maxChunkSize = MAX_CHUNK_SIZE_DEFAULT
|
|
768
|
+
bmpChunkSize = MAX_CHUNK_SIZE_DEFAULT
|
|
769
|
+
|
|
770
|
+
Bridge.log("📋 Fallback Configuration:")
|
|
771
|
+
Bridge.log(" 📊 Default MTU: $MTU_DEFAULT bytes")
|
|
772
|
+
Bridge.log(" 📦 Data Chunk Size: $maxChunkSize bytes")
|
|
773
|
+
Bridge.log(" 🖼️ Image Chunk Size: $bmpChunkSize bytes")
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
|
779
|
+
val msg = Message.obtain().apply {
|
|
780
|
+
what = MAIN_TASK_HANDLER_CODE_CHARACTERISTIC_VALUE_NOTIFIED
|
|
781
|
+
obj = characteristic // Attach any object you want
|
|
782
|
+
}
|
|
783
|
+
mainTaskHandler.sendMessage(msg)
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
private fun handleMainTaskMessage(msg: Message): Boolean {
|
|
789
|
+
val msgCode = msg.what
|
|
790
|
+
Bridge.log("Nex: handleMessage msgCode: $msgCode")
|
|
791
|
+
Bridge.log("Nex: handleMessage obj: ${msg.obj}")
|
|
792
|
+
|
|
793
|
+
when (msgCode) {
|
|
794
|
+
MAIN_TASK_HANDLER_CODE_GATT_STATUS_CHANGED -> {}
|
|
795
|
+
MAIN_TASK_HANDLER_CODE_DISCOVER_SERVICES -> {
|
|
796
|
+
val statusBool = msg.obj as? Boolean ?: false
|
|
797
|
+
if (statusBool) {
|
|
798
|
+
mainGlassGatt?.let { initNexGlasses(it) }
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
MAIN_TASK_HANDLER_CODE_CHARACTERISTIC_VALUE_NOTIFIED -> {
|
|
802
|
+
val characteristic = msg.obj as? BluetoothGattCharacteristic
|
|
803
|
+
characteristic?.let { onCharacteristicChangedHandler(it) }
|
|
804
|
+
}
|
|
805
|
+
MAIN_TASK_HANDLER_CODE_CONNECT_DEVICE -> {}
|
|
806
|
+
MAIN_TASK_HANDLER_CODE_DISCONNECT_DEVICE -> {}
|
|
807
|
+
MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE -> {
|
|
808
|
+
mainDevice?.let { attemptGattConnection(it) }
|
|
809
|
+
}
|
|
810
|
+
MAIN_TASK_HANDLER_CODE_CANCEL_RECONNECT_DEVICE -> {}
|
|
811
|
+
MAIN_TASK_HANDLER_CODE_RECONNECT_GATT -> {}
|
|
812
|
+
MAIN_TASK_HANDLER_CODE_SCAN_START -> {}
|
|
813
|
+
MAIN_TASK_HANDLER_CODE_SCAN_END -> {}
|
|
814
|
+
MAIN_TASK_HANDLER_CODE_BATTERY_QUERY -> {
|
|
815
|
+
queryBatteryStatus()
|
|
816
|
+
}
|
|
817
|
+
MAIN_TASK_HANDLER_CODE_HEART_BEAT -> {
|
|
818
|
+
// Note: Heartbeat is now handled by receiving ping from glasses
|
|
819
|
+
// This case is kept for backward compatibility but no longer used
|
|
820
|
+
Bridge.log("Nex: Heartbeat handler called - no longer sending periodic pings")
|
|
821
|
+
}
|
|
822
|
+
else -> { }
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return true
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
private fun initNexGlasses(gatt: BluetoothGatt) {
|
|
829
|
+
// Start MTU discovery with our maximum target
|
|
830
|
+
Bridge.log("Nex: 🔍 MTU Discovery: Requesting maximum MTU size: $MTU_517")
|
|
831
|
+
Bridge.log("Nex: 🎯 Target: Use $MTU_517 bytes max, or $MTU_DEFAULT bytes default")
|
|
832
|
+
Bridge.log("Nex: 📤 Requesting MTU: $MTU_517 bytes")
|
|
833
|
+
gatt.requestMtu(MTU_517) // Request our maximum MTU
|
|
834
|
+
|
|
835
|
+
val uartService: BluetoothGattService = gatt.getService(NexBluetoothConstants.MAIN_SERVICE_UUID)
|
|
836
|
+
|
|
837
|
+
if (uartService != null) {
|
|
838
|
+
val writeChar = uartService.getCharacteristic(NexBluetoothConstants.WRITE_CHAR_UUID)
|
|
839
|
+
val notifyChar = uartService.getCharacteristic(NexBluetoothConstants.NOTIFY_CHAR_UUID)
|
|
840
|
+
|
|
841
|
+
writeChar?.let {
|
|
842
|
+
mainWriteChar = it
|
|
843
|
+
Bridge.log("Nex: glass TX characteristic found")
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
notifyChar?.let {
|
|
847
|
+
mainNotifyChar = it
|
|
848
|
+
enableNotification(gatt, it)
|
|
849
|
+
Bridge.log("Nex: glass RX characteristic found")
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Mark as connected but wait for setup below to update connection state
|
|
853
|
+
isMainConnected = true
|
|
854
|
+
Bridge.log("Nex: PROC_QUEUE - left side setup complete")
|
|
855
|
+
|
|
856
|
+
if (isMainConnected) {
|
|
857
|
+
// Do first battery status query
|
|
858
|
+
mainTaskHandler.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_BATTERY_QUERY, 10)
|
|
859
|
+
|
|
860
|
+
// Restore previous microphone state or disable if this is the first connection
|
|
861
|
+
val shouldRestoreMic = microphoneStateBeforeDisconnection
|
|
862
|
+
Bridge.log("Nex: `Restoring microphone state to: $shouldRestoreMic (previous state: $microphoneStateBeforeDisconnection)")
|
|
863
|
+
|
|
864
|
+
if (shouldRestoreMic) {
|
|
865
|
+
startMicBeat(MICBEAT_INTERVAL_MS.toInt())
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Enable our AugmentOS notification key
|
|
869
|
+
sendWhiteListCommand(10)
|
|
870
|
+
|
|
871
|
+
showHomeScreen() // Turn on the NexGlasses display
|
|
872
|
+
updateConnectionState()
|
|
873
|
+
|
|
874
|
+
if (hasConnectedThisSession) {
|
|
875
|
+
Bridge.log("Nex: BLE reconnect detected — showing reconnected banner")
|
|
876
|
+
sendTextWall("// MentraOS Reconnected")
|
|
877
|
+
mainTaskHandler.postDelayed({ clearDisplay() }, 3000)
|
|
878
|
+
} else {
|
|
879
|
+
hasConnectedThisSession = true
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Post protobuf schema version information (only once)
|
|
883
|
+
if (!protobufVersionPosted) {
|
|
884
|
+
postProtobufSchemaVersionInfo()
|
|
885
|
+
protobufVersionPosted = true
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// Query glasses protobuf version from firmware
|
|
889
|
+
Bridge.log("=== SENDING GLASSES PROTOBUF VERSION REQUEST ===")
|
|
890
|
+
val versionQueryPacket = NexProtobufUtils.generateVersionRequestCommandBytes()
|
|
891
|
+
sendDataSequentially(versionQueryPacket, 100)
|
|
892
|
+
Bridge.log("Sent glasses protobuf version request")
|
|
893
|
+
}
|
|
894
|
+
} else {
|
|
895
|
+
Log.e(TAG, " glass UART service not found")
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
private fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
|
900
|
+
Bridge.log("Nex: PROC_QUEUE - Starting notification setup")
|
|
901
|
+
|
|
902
|
+
// Enable notifications
|
|
903
|
+
val result = gatt.setCharacteristicNotification(characteristic, true)
|
|
904
|
+
Bridge.log("Nex: PROC_QUEUE - setCharacteristicNotification result: $result")
|
|
905
|
+
|
|
906
|
+
// Set write type for the characteristic
|
|
907
|
+
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
|
908
|
+
Bridge.log("Nex: PROC_QUEUE - write type set")
|
|
909
|
+
|
|
910
|
+
// Add delay
|
|
911
|
+
Bridge.log("Nex: PROC_QUEUE - waiting to enable notification...")
|
|
912
|
+
try {
|
|
913
|
+
Thread.sleep(100)
|
|
914
|
+
} catch (e: InterruptedException) {
|
|
915
|
+
Log.e(TAG, "Error sending data: ${e.message}")
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Get and configure descriptor
|
|
919
|
+
val descriptor = characteristic.getDescriptor(NexBluetoothConstants.CLIENT_CHARACTERISTIC_CONFIG_UUID)
|
|
920
|
+
descriptor?.let {
|
|
921
|
+
it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
|
922
|
+
val writeResult = gatt.writeDescriptor(it)
|
|
923
|
+
Bridge.log("Nex: PROC_QUEUE - set descriptor with result: $writeResult")
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
private fun onCharacteristicChangedHandler(characteristic: BluetoothGattCharacteristic) {
|
|
928
|
+
if (characteristic.uuid == NexBluetoothConstants.NOTIFY_CHAR_UUID) {
|
|
929
|
+
val data = characteristic.value
|
|
930
|
+
val deviceName = mainGlassGatt?.device?.name ?: return
|
|
931
|
+
val packetHex = data.joinToString("") { "%02X".format(it) }
|
|
932
|
+
Bridge.log("onCharacteristicChangedHandler len: ${data.size}")
|
|
933
|
+
Bridge.log("onCharacteristicChangedHandler: $packetHex")
|
|
934
|
+
|
|
935
|
+
if (data.isEmpty()) return
|
|
936
|
+
|
|
937
|
+
val packetType = data[0]
|
|
938
|
+
Bridge.log("onCharacteristicChangedHandler packetType: ${String.format("%02X ", packetType)}")
|
|
939
|
+
|
|
940
|
+
when (packetType) {
|
|
941
|
+
NexBluetoothPacketTypes.PACKET_TYPE_JSON -> {
|
|
942
|
+
val jsonData = data.copyOfRange(1, data.size)
|
|
943
|
+
decodeJsons(jsonData)
|
|
944
|
+
}
|
|
945
|
+
NexBluetoothPacketTypes.PACKET_TYPE_PROTOBUF -> {
|
|
946
|
+
val protobufData = data.copyOfRange(1, data.size)
|
|
947
|
+
decodeProtobufs(protobufData, packetHex)
|
|
948
|
+
}
|
|
949
|
+
NexBluetoothPacketTypes.PACKET_TYPE_AUDIO -> {
|
|
950
|
+
// Check for audio packet header
|
|
951
|
+
if (data[0] == 0xA0.toByte()) {
|
|
952
|
+
val sequenceNumber = data[1]
|
|
953
|
+
val receiveTime = System.currentTimeMillis()
|
|
954
|
+
|
|
955
|
+
// Basic sequence validation
|
|
956
|
+
if (lastReceivedLc3Sequence != -1 && (lastReceivedLc3Sequence + 1).toByte() != sequenceNumber) {
|
|
957
|
+
Bridge.log("LC3 packet sequence mismatch. Expected: ${lastReceivedLc3Sequence + 1}, Got: $sequenceNumber")
|
|
958
|
+
}
|
|
959
|
+
lastReceivedLc3Sequence = sequenceNumber.toInt()
|
|
960
|
+
|
|
961
|
+
val lc3Data = data.copyOfRange(2, data.size)
|
|
962
|
+
|
|
963
|
+
Bridge.log("Received LC3 audio packet seq=$sequenceNumber, size=${lc3Data.size}")
|
|
964
|
+
|
|
965
|
+
// Play LC3 audio directly through LC3 player
|
|
966
|
+
if (lc3AudioPlayer != null && isLc3AudioEnabled) {
|
|
967
|
+
// Use the original packet format that LC3 player expects
|
|
968
|
+
lc3AudioPlayer?.write(data, 0, data.size)
|
|
969
|
+
Bridge.log("Playing LC3 audio directly through LC3 player: ${data.size} bytes")
|
|
970
|
+
} else if (!isLc3AudioEnabled) {
|
|
971
|
+
Bridge.log("LC3 audio disabled - skipping LC3 audio output")
|
|
972
|
+
} else {
|
|
973
|
+
Bridge.log("LC3 player not available - skipping LC3 audio output")
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
DeviceManager.getInstance().handleGlassesMicData(lc3Data);
|
|
977
|
+
|
|
978
|
+
// Still decode for callback compatibility
|
|
979
|
+
// TODO: (Verify) Commenting because commented in G1
|
|
980
|
+
// if (lc3DecoderPtr != 0L) {
|
|
981
|
+
// val pcmData = Lc3Cpp.decodeLC3(lc3DecoderPtr, lc3Data)
|
|
982
|
+
// Bridge.log("pcmData size:${pcmData?.size ?: 0}")
|
|
983
|
+
// audioProcessingCallback?.let { callback ->
|
|
984
|
+
// if (!pcmData.isNullOrEmpty()) {
|
|
985
|
+
// callback.onAudioDataAvailable(pcmData)
|
|
986
|
+
// }
|
|
987
|
+
// } ?: run {
|
|
988
|
+
// // If we get here, it means the callback wasn't properly registered
|
|
989
|
+
// Log.e(TAG,"Audio processing callback is null - callback registration failed!")
|
|
990
|
+
// }
|
|
991
|
+
// }
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
NexBluetoothPacketTypes.PACKET_TYPE_IMAGE -> {
|
|
995
|
+
// Handle image packet if needed
|
|
996
|
+
}
|
|
997
|
+
else -> {
|
|
998
|
+
Bridge.log("unknown packetType: ${String.format("%02X ", packetType)}")
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
private fun attemptGattConnection(device: BluetoothDevice) {
|
|
1005
|
+
if (device == null) {
|
|
1006
|
+
Bridge.log("Cannot connect to GATT: Device is null")
|
|
1007
|
+
return
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
val deviceName = device.name
|
|
1011
|
+
if (deviceName.isNullOrEmpty()) {
|
|
1012
|
+
Log.e(TAG,"Skipping null/empty device name: ${device.address}... this means something horrific has occurred. Look into this.")
|
|
1013
|
+
return
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
Bridge.log("attemptGattConnection called for device: $deviceName (${device.address})")
|
|
1017
|
+
|
|
1018
|
+
DeviceStore.apply("glasses", "connectionState", ConnTypes.CONNECTING)
|
|
1019
|
+
Bridge.log("Setting connectionState to CONNECTING. Notifying connectionEvent.")
|
|
1020
|
+
// connectionEvent(connectionState)
|
|
1021
|
+
|
|
1022
|
+
if (mainGlassGatt == null) {
|
|
1023
|
+
Bridge.log("Attempting GATT connection for Main Glass...")
|
|
1024
|
+
mainGlassGatt = device.connectGatt(context, false, mainGattCallback)
|
|
1025
|
+
isMainConnected = false
|
|
1026
|
+
Bridge.log("Main GATT connection initiated. isMainConnected set to false.")
|
|
1027
|
+
} else {
|
|
1028
|
+
Bridge.log("Main Glass GATT already exists")
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
private fun postProtobufSchemaVersionInfo() {
|
|
1033
|
+
try {
|
|
1034
|
+
// Call the version method only once
|
|
1035
|
+
val schemaVersion = NexProtobufUtils.getProtobufSchemaVersion(context!!)
|
|
1036
|
+
|
|
1037
|
+
// Build the info string directly instead of calling getProtobufBuildInfo()
|
|
1038
|
+
val fileDescriptorName = mentraos.ble.MentraosBle.getDescriptor().file.name
|
|
1039
|
+
val buildInfo = "Schema v$schemaVersion | $fileDescriptorName"
|
|
1040
|
+
|
|
1041
|
+
// protobufSchemaVersion = schemaVersion.toString()
|
|
1042
|
+
|
|
1043
|
+
// val event = ProtobufSchemaVersionEvent(
|
|
1044
|
+
// schemaVersion,
|
|
1045
|
+
// buildInfo,
|
|
1046
|
+
// smartGlassesDevice?.deviceModelName ?: "Unknown"
|
|
1047
|
+
// )
|
|
1048
|
+
|
|
1049
|
+
// EventBus.getDefault().post(event)
|
|
1050
|
+
Bridge.log("Posted protobuf schema version event: $buildInfo")
|
|
1051
|
+
} catch (e: Exception) {
|
|
1052
|
+
Log.e(TAG,"Error posting protobuf schema version event: ${e.message}", e)
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
private fun connectToSmartGlasses() {
|
|
1057
|
+
val deviceModelName = DeviceManager.getInstance().deviceName
|
|
1058
|
+
val deviceAddress = DeviceManager.getInstance().deviceAddress
|
|
1059
|
+
|
|
1060
|
+
// Register bonding receiver
|
|
1061
|
+
Bridge.log("connectToSmartGlasses start")
|
|
1062
|
+
Bridge.log("try to ConnectToSmartGlassesing deviceModelName: ${deviceModelName} deviceAddress: ${deviceAddress}")
|
|
1063
|
+
preferredMainDeviceId = DeviceManager.getInstance().deviceName
|
|
1064
|
+
if (!bluetoothAdapter.isEnabled) {
|
|
1065
|
+
return
|
|
1066
|
+
}
|
|
1067
|
+
when {
|
|
1068
|
+
!deviceModelName.isNullOrEmpty() && !deviceAddress.isNullOrEmpty() -> {
|
|
1069
|
+
stopScan()
|
|
1070
|
+
mainDevice = bluetoothAdapter.getRemoteDevice(deviceAddress)
|
|
1071
|
+
mainTaskHandler?.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 0)
|
|
1072
|
+
}
|
|
1073
|
+
savedNexMainAddress != null -> {
|
|
1074
|
+
mainDevice = bluetoothAdapter.getRemoteDevice(savedNexMainAddress)
|
|
1075
|
+
mainTaskHandler?.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_RECONNECT_DEVICE, 0)
|
|
1076
|
+
}
|
|
1077
|
+
else -> {
|
|
1078
|
+
// Start scanning for devices
|
|
1079
|
+
stopScan()
|
|
1080
|
+
DeviceStore.apply("glasses", "connectionState", ConnTypes.SCANNING)
|
|
1081
|
+
// connectionEvent(connectionState) // TODO: Figure out where is connection event defined????
|
|
1082
|
+
startScan()
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
private fun destroy() {
|
|
1088
|
+
Bridge.log("Nex: MentraNexSGC ONDESTROY")
|
|
1089
|
+
showHomeScreen()
|
|
1090
|
+
isKilled = true
|
|
1091
|
+
// stop BLE scanning
|
|
1092
|
+
stopScan()
|
|
1093
|
+
// Save current microphone state before destroying
|
|
1094
|
+
microphoneStateBeforeDisconnection = isMicrophoneEnabled
|
|
1095
|
+
Bridge.log("Nex: Saved microphone state during destroy: $microphoneStateBeforeDisconnection")
|
|
1096
|
+
// disable the microphone and stop sending micbeat
|
|
1097
|
+
stopMicBeat()
|
|
1098
|
+
// Stop periodic notifications
|
|
1099
|
+
stopPeriodicNotifications()
|
|
1100
|
+
mainGlassGatt?.let { gatt ->
|
|
1101
|
+
gatt.disconnect()
|
|
1102
|
+
gatt.close()
|
|
1103
|
+
mainGlassGatt = null
|
|
1104
|
+
}
|
|
1105
|
+
lc3AudioPlayer?.let { player ->
|
|
1106
|
+
try {
|
|
1107
|
+
player.stopPlay()
|
|
1108
|
+
Bridge.log("Nex: LC3 audio player stopped and cleaned up")
|
|
1109
|
+
} catch (e: Exception) {
|
|
1110
|
+
Log.e(TAG,"Nex: Error stopping LC3 audio player during destroy: ${e.message}")
|
|
1111
|
+
} finally {
|
|
1112
|
+
lc3AudioPlayer = null
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
// Clean up handlers
|
|
1116
|
+
mainTaskHandler?.removeCallbacksAndMessages(null)
|
|
1117
|
+
whiteListHandler?.removeCallbacksAndMessages(null)
|
|
1118
|
+
micEnableHandler?.removeCallbacksAndMessages(null)
|
|
1119
|
+
notificationRunnable?.let {
|
|
1120
|
+
notificationHandler?.removeCallbacks(it)
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
textWallRunnable?.let {
|
|
1124
|
+
textWallHandler?.removeCallbacks(it)
|
|
1125
|
+
}
|
|
1126
|
+
findCompatibleDevicesHandler?.removeCallbacksAndMessages(null)
|
|
1127
|
+
// Free LC3 decoder
|
|
1128
|
+
if (lc3DecoderPtr != 0L) {
|
|
1129
|
+
Lc3Cpp.freeDecoder(lc3DecoderPtr)
|
|
1130
|
+
lc3DecoderPtr = 0L
|
|
1131
|
+
}
|
|
1132
|
+
currentImageChunks.clear()
|
|
1133
|
+
isImageSendProgressing = false
|
|
1134
|
+
sendQueue.clear()
|
|
1135
|
+
// Add a dummy element to unblock the take() call if needed
|
|
1136
|
+
sendQueue.offer(emptyArray()) // is this needed?
|
|
1137
|
+
isWorkerRunning = false
|
|
1138
|
+
isMainConnected = false
|
|
1139
|
+
hasConnectedThisSession = false
|
|
1140
|
+
Bridge.log("Nex: MentraNexSGC cleanup complete")
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
private fun startScan() {
|
|
1144
|
+
val scanner: BluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
|
|
1145
|
+
if (scanner == null) {
|
|
1146
|
+
Log.e(TAG,"BluetoothLeScanner not available.")
|
|
1147
|
+
return
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// Optionally, define filters if needed
|
|
1151
|
+
val filters = mutableListOf<ScanFilter>()
|
|
1152
|
+
// For example, to filter by device name:
|
|
1153
|
+
// filters.add(ScanFilter.Builder().setDeviceName("Even G1_").build())
|
|
1154
|
+
|
|
1155
|
+
// Set desired scan settings
|
|
1156
|
+
val settings = ScanSettings.Builder()
|
|
1157
|
+
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
|
1158
|
+
.build()
|
|
1159
|
+
|
|
1160
|
+
// Start scanning
|
|
1161
|
+
isScanning = true
|
|
1162
|
+
scanner.startScan(filters, settings, modernScanCallback)
|
|
1163
|
+
scanner.flushPendingScanResults(modernScanCallback)
|
|
1164
|
+
Bridge.log("CALL START SCAN - Started scanning for devices...")
|
|
1165
|
+
|
|
1166
|
+
// Ensure scanning state is immediately communicated to UI
|
|
1167
|
+
DeviceStore.apply("glasses", "connectionState", ConnTypes.SCANNING)
|
|
1168
|
+
// connectionEvent(connectionState)
|
|
1169
|
+
|
|
1170
|
+
// Stop the scan after some time (e.g., 10-15s instead of 60 to avoid
|
|
1171
|
+
// throttling)
|
|
1172
|
+
// handler.postDelayed({ stopScan() }, 10000)
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
override fun stopScan() {
|
|
1176
|
+
val scanner = bluetoothAdapter.bluetoothLeScanner
|
|
1177
|
+
scanner?.stopScan(modernScanCallback)
|
|
1178
|
+
isScanning = false
|
|
1179
|
+
Bridge.log("Stopped scanning for devices")
|
|
1180
|
+
|
|
1181
|
+
if (bleScanCallback != null && isScanningForCompatibleDevices) {
|
|
1182
|
+
scanner?.stopScan(bleScanCallback)
|
|
1183
|
+
isScanningForCompatibleDevices = false
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
private fun displayBitmapImageForNexGlasses(bmpData: ByteArray, width: Int, height: Int) {
|
|
1188
|
+
Bridge.log("Starting BMP display process for ${width}x$height image")
|
|
1189
|
+
|
|
1190
|
+
try {
|
|
1191
|
+
if (bmpData.isEmpty()) {
|
|
1192
|
+
Log.e(TAG,"Invalid BMP data provided")
|
|
1193
|
+
return
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
Bridge.log("Processing BMP data, size: ${bmpData.size} bytes")
|
|
1197
|
+
|
|
1198
|
+
// Generate proper 2-byte hex stream ID (e.g., "002A") as per protobuf specification
|
|
1199
|
+
val totalChunks = (bmpData.size + bmpChunkSize - 1) / bmpChunkSize
|
|
1200
|
+
val streamId = "%04X".format(random.nextInt(0x10000)) // 4-digit hex format
|
|
1201
|
+
|
|
1202
|
+
val startImageSendingBytes = NexProtobufUtils.generateDisplayImageCommandBytes(streamId, totalChunks, width, height)
|
|
1203
|
+
sendDataSequentially(startImageSendingBytes)
|
|
1204
|
+
|
|
1205
|
+
// Send all chunks with proper stream ID parsing
|
|
1206
|
+
val chunks = NexProtobufUtils.createBmpChunksForNexGlasses(streamId, bmpData, totalChunks, bmpChunkSize)
|
|
1207
|
+
currentImageChunks.clear()
|
|
1208
|
+
currentImageChunks.addAll(chunks)
|
|
1209
|
+
sendDataSequentially(chunks)
|
|
1210
|
+
|
|
1211
|
+
// Note: The following are commented out in the original
|
|
1212
|
+
// sendBmpEndCommand()
|
|
1213
|
+
// sendBmpCRC(bmpData)
|
|
1214
|
+
// lastThingDisplayedWasAnImage = true
|
|
1215
|
+
|
|
1216
|
+
} catch (e: Exception) {
|
|
1217
|
+
Log.e(TAG,"Error in displayBitmapImage: ${e.message}")
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
private fun stopPeriodicNotifications() {
|
|
1222
|
+
notificationRunnable?.let { runnable ->
|
|
1223
|
+
notificationHandler?.removeCallbacks(runnable)
|
|
1224
|
+
Bridge.log("Stopped periodic notifications")
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
private fun startMicBeat(delay: Int) {
|
|
1228
|
+
Bridge.log("Nex: Starting micbeat")
|
|
1229
|
+
if (micBeatCount > 0) {
|
|
1230
|
+
stopMicBeat()
|
|
1231
|
+
}
|
|
1232
|
+
sendSetMicEnabled(true, 10)
|
|
1233
|
+
micBeatRunnable = Runnable {
|
|
1234
|
+
Bridge.log("Nex: SENDING MIC BEAT")
|
|
1235
|
+
sendSetMicEnabled(shouldUseGlassesMic, 1)
|
|
1236
|
+
micBeatHandler.postDelayed(micBeatRunnable!!, MICBEAT_INTERVAL_MS)
|
|
1237
|
+
}
|
|
1238
|
+
micBeatHandler.postDelayed(micBeatRunnable!!, delay.toLong())
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
private fun stopMicBeat() {
|
|
1242
|
+
sendSetMicEnabled(false, 10)
|
|
1243
|
+
if (micBeatHandler != null) {
|
|
1244
|
+
micBeatHandler.removeCallbacksAndMessages(null);
|
|
1245
|
+
micBeatHandler.removeCallbacksAndMessages(micBeatRunnable);
|
|
1246
|
+
micBeatRunnable = null;
|
|
1247
|
+
micBeatCount = 0;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
private fun sendWhiteListCommand(delay: Int) {
|
|
1252
|
+
if (whiteListedAlready) return
|
|
1253
|
+
|
|
1254
|
+
whiteListedAlready = true
|
|
1255
|
+
Bridge.log("Nex: Sending whitelist command")
|
|
1256
|
+
|
|
1257
|
+
whiteListHandler.postDelayed({
|
|
1258
|
+
val chunks = NexProtobufUtils.getWhitelistChunks(maxChunkSize)
|
|
1259
|
+
sendDataSequentially(chunks)
|
|
1260
|
+
|
|
1261
|
+
// Uncomment if needed for debugging:
|
|
1262
|
+
// chunks.forEach { chunk ->
|
|
1263
|
+
// Bridge.log("Nex: Sending this chunk for white list: ${bytesToUtf8(chunk)}")
|
|
1264
|
+
// sendDataSequentially(chunk)
|
|
1265
|
+
// // Thread.sleep(150) // Uncomment if delay between chunks is needed
|
|
1266
|
+
// }
|
|
1267
|
+
}, delay.toLong())
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
private fun showHomeScreen() {
|
|
1271
|
+
Bridge.log("Nex: showHomeScreen")
|
|
1272
|
+
|
|
1273
|
+
if (lastThingDisplayedWasAnImage) {
|
|
1274
|
+
// clearNexScreen()
|
|
1275
|
+
lastThingDisplayedWasAnImage = false
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
private fun updateConnectionState() {
|
|
1280
|
+
if (isMainConnected) {
|
|
1281
|
+
DeviceStore.apply("glasses", "connectionState", ConnTypes.CONNECTED)
|
|
1282
|
+
Bridge.log("Nex: Main glasses connected")
|
|
1283
|
+
lastConnectionTimestamp = System.currentTimeMillis()
|
|
1284
|
+
DeviceStore.apply("glasses", "fullyBooted", true)
|
|
1285
|
+
DeviceStore.apply("glasses", "connected", true)
|
|
1286
|
+
// Removed commented sleep code as it's not needed
|
|
1287
|
+
// connectionEvent(it)
|
|
1288
|
+
} else {
|
|
1289
|
+
DeviceStore.apply("glasses", "connectionState", ConnTypes.DISCONNECTED)
|
|
1290
|
+
Bridge.log("Nex: No Main glasses connected")
|
|
1291
|
+
DeviceStore.apply("glasses", "fullyBooted", false)
|
|
1292
|
+
DeviceStore.apply("glasses", "connected", false)
|
|
1293
|
+
// connectionEvent(it)
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
private fun decodeJsons(jsonBytes: ByteArray) {
|
|
1298
|
+
val jsonString = String(jsonBytes, Charset.defaultCharset())
|
|
1299
|
+
try {
|
|
1300
|
+
val commandObject = JSONObject(jsonString)
|
|
1301
|
+
when (val type = commandObject.getString("type")) {
|
|
1302
|
+
"image_transfer_complete" -> {
|
|
1303
|
+
// Handle image transfer complete
|
|
1304
|
+
}
|
|
1305
|
+
"disconnect" -> {
|
|
1306
|
+
// Handle disconnect
|
|
1307
|
+
}
|
|
1308
|
+
"request_battery_state" -> {
|
|
1309
|
+
// Handle battery state request
|
|
1310
|
+
}
|
|
1311
|
+
"charging_state" -> {
|
|
1312
|
+
// Handle charging state
|
|
1313
|
+
}
|
|
1314
|
+
"device_info" -> {
|
|
1315
|
+
// val deviceInfo = gson.fromJson(jsonString, DeviceInfo::class.java)
|
|
1316
|
+
}
|
|
1317
|
+
"enter_pairing_mode" -> {
|
|
1318
|
+
// Handle pairing mode
|
|
1319
|
+
}
|
|
1320
|
+
"request_head_position" -> {
|
|
1321
|
+
// Handle head position request
|
|
1322
|
+
}
|
|
1323
|
+
"set_head_up_angle" -> {
|
|
1324
|
+
// Handle head up angle
|
|
1325
|
+
}
|
|
1326
|
+
"ping" -> {
|
|
1327
|
+
// Handle ping
|
|
1328
|
+
}
|
|
1329
|
+
"vad_event" -> {
|
|
1330
|
+
// Handle VAD event
|
|
1331
|
+
}
|
|
1332
|
+
"imu_data" -> {
|
|
1333
|
+
// Handle IMU data
|
|
1334
|
+
}
|
|
1335
|
+
"button_event" -> {
|
|
1336
|
+
// Handle button event
|
|
1337
|
+
}
|
|
1338
|
+
"head_gesture" -> {
|
|
1339
|
+
// Handle head gesture
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
} catch (e: Exception) {
|
|
1343
|
+
Log.e(TAG,"Error decoding JSON: ${e.message}")
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
private fun decodeProtobufs(protobufBytes: ByteArray, packetHex: String) {
|
|
1348
|
+
try {
|
|
1349
|
+
val glassesToPhone = GlassesToPhone.parseFrom(protobufBytes)
|
|
1350
|
+
val payloadCase = glassesToPhone.payloadCase.toString()
|
|
1351
|
+
|
|
1352
|
+
Bridge.log("decodeProtobufs glassesToPhone: $glassesToPhone")
|
|
1353
|
+
Bridge.log("decodeProtobufs glassesToPhone payloadCase: $payloadCase")
|
|
1354
|
+
|
|
1355
|
+
NexEventUtils.sendBleCommandReceivedEvent(payloadCase, packetHex, System.currentTimeMillis())
|
|
1356
|
+
// if (isDebugMode) {
|
|
1357
|
+
// EventBus.getDefault().post(BleCommandReceiver(payloadCase, packetHex))
|
|
1358
|
+
// }
|
|
1359
|
+
|
|
1360
|
+
when (glassesToPhone.payloadCase) {
|
|
1361
|
+
GlassesToPhone.PayloadCase.BATTERY_STATUS -> {
|
|
1362
|
+
val batteryStatus: BatteryStatus = glassesToPhone.batteryStatus
|
|
1363
|
+
DeviceStore.apply("glasses", "batteryLevel", batteryStatus.level)
|
|
1364
|
+
// EventBus.getDefault().post(BatteryLevelEvent(batteryStatus.level, batteryStatus.charging))
|
|
1365
|
+
Bridge.log("batteryStatus: $batteryStatus")
|
|
1366
|
+
}
|
|
1367
|
+
GlassesToPhone.PayloadCase.CHARGING_STATE -> {
|
|
1368
|
+
val chargingState: ChargingState = glassesToPhone.chargingState
|
|
1369
|
+
// EventBus.getDefault().post(BatteryLevelEvent(batteryLevel, chargingState.state == State.CHARGING))
|
|
1370
|
+
Bridge.log("chargingState: $chargingState")
|
|
1371
|
+
}
|
|
1372
|
+
GlassesToPhone.PayloadCase.DEVICE_INFO -> {
|
|
1373
|
+
val deviceInfo: DeviceInfo = glassesToPhone.deviceInfo
|
|
1374
|
+
Bridge.log("deviceInfo: $deviceInfo")
|
|
1375
|
+
}
|
|
1376
|
+
GlassesToPhone.PayloadCase.HEAD_POSITION -> {
|
|
1377
|
+
val headPosition: HeadPosition = glassesToPhone.headPosition
|
|
1378
|
+
// EventBus.getDefault().post(HeadUpAngleEvent(headPosition.angle))
|
|
1379
|
+
Bridge.log("headPosition: $headPosition")
|
|
1380
|
+
}
|
|
1381
|
+
GlassesToPhone.PayloadCase.HEAD_UP_ANGLE_SET -> {
|
|
1382
|
+
val headUpAngleResponse: HeadUpAngleResponse = glassesToPhone.headUpAngleSet
|
|
1383
|
+
Bridge.log("headUpAngleResponse: $headUpAngleResponse")
|
|
1384
|
+
}
|
|
1385
|
+
GlassesToPhone.PayloadCase.PING -> {
|
|
1386
|
+
lastHeartbeatReceivedTime = System.currentTimeMillis()
|
|
1387
|
+
Bridge.log("=== RECEIVED PING FROM GLASSES === (Time: $lastHeartbeatReceivedTime)")
|
|
1388
|
+
sendPongResponse()
|
|
1389
|
+
}
|
|
1390
|
+
GlassesToPhone.PayloadCase.VAD_EVENT -> {
|
|
1391
|
+
// val vadEvent = glassesToPhone.vadEvent
|
|
1392
|
+
// EventBus.getDefault().post(VadEvent(vadEvent.vad))
|
|
1393
|
+
}
|
|
1394
|
+
GlassesToPhone.PayloadCase.IMAGE_TRANSFER_COMPLETE -> {
|
|
1395
|
+
val transferComplete: ImageTransferComplete = glassesToPhone.imageTransferComplete
|
|
1396
|
+
Bridge.log("transferComplete: $transferComplete")
|
|
1397
|
+
|
|
1398
|
+
when (transferComplete.status) {
|
|
1399
|
+
ImageTransferComplete.Status.OK -> {
|
|
1400
|
+
currentImageChunks.clear()
|
|
1401
|
+
isImageSendProgressing = false
|
|
1402
|
+
}
|
|
1403
|
+
ImageTransferComplete.Status.INCOMPLETE -> {
|
|
1404
|
+
val missingChunksList = transferComplete.missingChunksList
|
|
1405
|
+
reSendImageMissingChunks(missingChunksList)
|
|
1406
|
+
}
|
|
1407
|
+
else -> {}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
GlassesToPhone.PayloadCase.IMU_DATA -> {
|
|
1411
|
+
val imuData: ImuData = glassesToPhone.imuData
|
|
1412
|
+
Bridge.log("imuData: $imuData")
|
|
1413
|
+
}
|
|
1414
|
+
GlassesToPhone.PayloadCase.BUTTON_EVENT -> {
|
|
1415
|
+
val buttonEvent: ButtonEvent = glassesToPhone.buttonEvent
|
|
1416
|
+
Bridge.log("buttonEvent: $buttonEvent")
|
|
1417
|
+
// buttonEvent.button.number
|
|
1418
|
+
// EventBus.getDefault().post(ButtonPressEvent(
|
|
1419
|
+
// smartGlassesDevice.deviceModelName,
|
|
1420
|
+
// buttonId,
|
|
1421
|
+
// pressType,
|
|
1422
|
+
// timestamp
|
|
1423
|
+
// ))
|
|
1424
|
+
}
|
|
1425
|
+
GlassesToPhone.PayloadCase.HEAD_GESTURE -> {
|
|
1426
|
+
val headGesture: HeadGesture = glassesToPhone.headGesture
|
|
1427
|
+
Bridge.log("headGesture: $headGesture")
|
|
1428
|
+
// EventBus.getDefault().post(GlassesHeadUpEvent())
|
|
1429
|
+
// EventBus.getDefault().post(GlassesHeadDownEvent())
|
|
1430
|
+
// EventBus.getDefault().post(GlassesTapOutputEvent(2, isRight, System.currentTimeMillis()))
|
|
1431
|
+
}
|
|
1432
|
+
GlassesToPhone.PayloadCase.VERSION_RESPONSE -> {
|
|
1433
|
+
val versionResponse: VersionResponse = glassesToPhone.versionResponse
|
|
1434
|
+
Bridge.log("=== RECEIVED GLASSES PROTOBUF VERSION RESPONSE ===")
|
|
1435
|
+
Bridge.log("Glasses Protobuf Version: ${versionResponse.version}")
|
|
1436
|
+
Bridge.log("Message ID: ${versionResponse.msgId}")
|
|
1437
|
+
DeviceStore.apply("glasses", "protobufVersion", versionResponse.version.toString())
|
|
1438
|
+
|
|
1439
|
+
if (versionResponse.commit.isNotEmpty()) {
|
|
1440
|
+
Bridge.log("Commit: ${versionResponse.commit}")
|
|
1441
|
+
}
|
|
1442
|
+
if (versionResponse.buildDate.isNotEmpty()) {
|
|
1443
|
+
Bridge.log("Build Date: ${versionResponse.buildDate}")
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// Post glasses protobuf version event to update UI
|
|
1447
|
+
// EventBus.getDefault().post(ProtocolVersionResponseEvent(
|
|
1448
|
+
// versionResponse.version,
|
|
1449
|
+
// versionResponse.commit,
|
|
1450
|
+
// versionResponse.buildDate,
|
|
1451
|
+
// versionResponse.msgId
|
|
1452
|
+
// ))
|
|
1453
|
+
}
|
|
1454
|
+
GlassesToPhone.PayloadCase.PAYLOAD_NOT_SET,
|
|
1455
|
+
null -> {
|
|
1456
|
+
// Do nothing
|
|
1457
|
+
}
|
|
1458
|
+
else -> {
|
|
1459
|
+
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
} catch (e: InvalidProtocolBufferException) {
|
|
1463
|
+
Log.e(TAG,"Error decoding protobuf: ${e.message}")
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
private fun decodeProtobufsByWrite(protobufBytes: ByteArray, packetHex: String) {
|
|
1468
|
+
try {
|
|
1469
|
+
val phoneToGlasses = PhoneToGlasses.parseFrom(protobufBytes)
|
|
1470
|
+
Bridge.log("decodeProtobufsByWrite phoneToGlasses: $phoneToGlasses")
|
|
1471
|
+
Bridge.log("decodeProtobufsByWrite phoneToGlasses payloadCase: ${phoneToGlasses.payloadCase}")
|
|
1472
|
+
val payloadCase = phoneToGlasses.payloadCase.toString()
|
|
1473
|
+
NexEventUtils.sendBleCommandSentEvent(payloadCase, packetHex, System.currentTimeMillis())
|
|
1474
|
+
// if (isDebugMode) {
|
|
1475
|
+
// EventBus.getDefault().post(BleCommandSender(payloadCase, packetHex))
|
|
1476
|
+
// }
|
|
1477
|
+
} catch (e: Exception) {
|
|
1478
|
+
Log.e(TAG,"Error in decodeProtobufsByWrite: ${e.message}")
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
private fun reSendImageMissingChunks(missingChunksIndexList: List<Int>) {
|
|
1483
|
+
if (!isImageSendProgressing || currentImageChunks.isEmpty() || missingChunksIndexList.isEmpty()) {
|
|
1484
|
+
return
|
|
1485
|
+
}
|
|
1486
|
+
val missingChunks = missingChunksIndexList.map { index ->
|
|
1487
|
+
currentImageChunks[index]
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
sendDataSequentially(missingChunks)
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
private fun sendPongResponse() {
|
|
1494
|
+
// Respond to ping from glasses with pong
|
|
1495
|
+
lastHeartbeatReceivedTime = System.currentTimeMillis()
|
|
1496
|
+
Bridge.log("=== SENDING PONG RESPONSE TO GLASSES === (Time: $lastHeartbeatReceivedTime)")
|
|
1497
|
+
|
|
1498
|
+
val pongPacket = NexProtobufUtils.constructPongResponse()
|
|
1499
|
+
|
|
1500
|
+
// Send the pong response
|
|
1501
|
+
if (pongPacket != null) {
|
|
1502
|
+
sendDataSequentially(pongPacket, 100)
|
|
1503
|
+
Bridge.log("Pong response sent successfully")
|
|
1504
|
+
|
|
1505
|
+
// Notify mobile app about pong sent
|
|
1506
|
+
lastHeartbeatSentTime = System.currentTimeMillis()
|
|
1507
|
+
NexEventUtils.sendHeartbeatSentEvent(lastHeartbeatSentTime)
|
|
1508
|
+
// EventBus.getDefault().post(HeartbeatSentEvent(timestamp))
|
|
1509
|
+
} else {
|
|
1510
|
+
Log.e(TAG,"Failed to construct pong response packet")
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// Still query battery periodically (every 10 pings received)
|
|
1514
|
+
if (batteryLevel == -1 || heartbeatCount % 10 == 0) {
|
|
1515
|
+
mainTaskHandler.sendEmptyMessageDelayed(MAIN_TASK_HANDLER_CODE_BATTERY_QUERY, 500)
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
heartbeatCount++
|
|
1519
|
+
|
|
1520
|
+
// Notify mobile app about heartbeat received
|
|
1521
|
+
NexEventUtils.sendHeartbeatReceivedEvent(lastHeartbeatReceivedTime)
|
|
1522
|
+
// EventBus.getDefault().post(HeartbeatReceivedEvent(lastHeartbeatReceivedTime))
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
private fun createTextWallChunksForNex(text: String): ByteArray {
|
|
1526
|
+
// Create the PhoneToGlasses using its builder and set the DisplayText
|
|
1527
|
+
return NexProtobufUtils.generateDisplayTextCommandBytes(text)
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
private fun sendSetMicEnabled(enable: Boolean, delay: Long) {
|
|
1531
|
+
Bridge.log("Nex: setMicEnabled called with enable: $enable and delay: $delay")
|
|
1532
|
+
Bridge.log("Nex: Running set mic enabled: $enable")
|
|
1533
|
+
isMicrophoneEnabled = enable // Update the state tracker
|
|
1534
|
+
DeviceStore.apply("glasses", "micEnabled", enable)
|
|
1535
|
+
|
|
1536
|
+
micEnableHandler?.postDelayed({
|
|
1537
|
+
if (connectionState != ConnTypes.CONNECTED) {
|
|
1538
|
+
Bridge.log("Nex: Tryna start mic: Not connected to glasses")
|
|
1539
|
+
return@postDelayed
|
|
1540
|
+
}
|
|
1541
|
+
Bridge.log("Nex: === SENDING MICROPHONE STATE COMMAND TO GLASSES ===")
|
|
1542
|
+
val micConfigBytes = NexProtobufUtils.generateMicStateConfigCommandBytes(enable)
|
|
1543
|
+
sendDataSequentially(micConfigBytes, 10) // wait some time to setup the mic
|
|
1544
|
+
Bridge.log("Nex: Sent MIC command: ${micConfigBytes.joinToString("") { "%02x".format(it) }}")
|
|
1545
|
+
}, delay)
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
private fun queryBatteryStatus() {
|
|
1549
|
+
val batteryQueryPacket = NexProtobufUtils.generateBatteryStateRequestCommandBytes()
|
|
1550
|
+
sendDataSequentially(batteryQueryPacket, 250)
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
///// PROCESSING THREAD /////////////////
|
|
1554
|
+
|
|
1555
|
+
// Start the worker thread if it's not already running
|
|
1556
|
+
@Synchronized
|
|
1557
|
+
private fun startWorkerIfNeeded() {
|
|
1558
|
+
if (!isWorkerRunning) {
|
|
1559
|
+
isWorkerRunning = true
|
|
1560
|
+
Thread({ processQueue() }, "MentraNexSGCProcessQueue").start()
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// Non-blocking function to add new send request
|
|
1565
|
+
private fun sendDataSequentially(data: ByteArray) {
|
|
1566
|
+
val chunks = arrayOf(SendRequest(data))
|
|
1567
|
+
sendQueue.offer(chunks)
|
|
1568
|
+
startWorkerIfNeeded()
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// Non-blocking function to add new send request with wait time
|
|
1572
|
+
private fun sendDataSequentially(data: ByteArray, waitTime: Int) {
|
|
1573
|
+
val chunks = arrayOf(SendRequest(data, waitTime))
|
|
1574
|
+
sendQueue.offer(chunks)
|
|
1575
|
+
startWorkerIfNeeded()
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
private fun sendDataSequentially(data: List<ByteArray>) {
|
|
1579
|
+
val chunks = Array(data.size) { i -> SendRequest(data[i]) }
|
|
1580
|
+
sendQueue.offer(chunks)
|
|
1581
|
+
startWorkerIfNeeded()
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
private fun processQueue() {
|
|
1585
|
+
// First wait until the services are ready to receive data
|
|
1586
|
+
Bridge.log("[BLE] PROC_QUEUE started — waiting for descriptor write (mainServicesWaiter)")
|
|
1587
|
+
try {
|
|
1588
|
+
mainServicesWaiter.waitWhileTrue()
|
|
1589
|
+
} catch (e: InterruptedException) {
|
|
1590
|
+
Log.e(TAG,"Nex: Interrupted waiting for descriptor writes: $e")
|
|
1591
|
+
}
|
|
1592
|
+
Bridge.log("[BLE] PROC_QUEUE ready — descriptor write done, entering send loop")
|
|
1593
|
+
|
|
1594
|
+
while (!isKilled) {
|
|
1595
|
+
try {
|
|
1596
|
+
// Make sure services are ready before processing requests
|
|
1597
|
+
mainServicesWaiter.waitWhileTrue()
|
|
1598
|
+
|
|
1599
|
+
// This will block until data is available
|
|
1600
|
+
Bridge.log("[BLE] PROC_QUEUE waiting for data in sendQueue (size=${sendQueue.size})")
|
|
1601
|
+
val requests = sendQueue.take()
|
|
1602
|
+
Bridge.log("[BLE] PROC_QUEUE dequeued ${requests.size} request(s)")
|
|
1603
|
+
|
|
1604
|
+
for (request in requests) {
|
|
1605
|
+
if (isKilled) {
|
|
1606
|
+
isWorkerRunning = false
|
|
1607
|
+
break
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
try {
|
|
1611
|
+
// Force an initial delay so BLE gets all setup
|
|
1612
|
+
val timeSinceConnection = System.currentTimeMillis() - lastConnectionTimestamp
|
|
1613
|
+
if (timeSinceConnection < INITIAL_CONNECTION_DELAY_MS) {
|
|
1614
|
+
Thread.sleep(INITIAL_CONNECTION_DELAY_MS - timeSinceConnection)
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// Send to main glass
|
|
1618
|
+
val canSend = mainGlassGatt != null && mainWriteChar != null && isMainConnected
|
|
1619
|
+
Bridge.log("[BLE] PROC_QUEUE send check: gatt=${mainGlassGatt != null} writeChar=${mainWriteChar != null} connected=$isMainConnected → canSend=$canSend len=${request.data.size}")
|
|
1620
|
+
if (canSend) {
|
|
1621
|
+
mainWaiter.setTrue()
|
|
1622
|
+
mainWriteChar?.value = request.data
|
|
1623
|
+
mainGlassGatt?.writeCharacteristic(mainWriteChar)
|
|
1624
|
+
lastSendTimestamp = System.currentTimeMillis()
|
|
1625
|
+
Bridge.log("[BLE] PROC_QUEUE writeCharacteristic issued, waiting for onCharacteristicWrite callback")
|
|
1626
|
+
} else {
|
|
1627
|
+
Bridge.log("[BLE] PROC_QUEUE skipping write — not ready, packet dropped")
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
mainWaiter.waitWhileTrue()
|
|
1631
|
+
|
|
1632
|
+
Thread.sleep(DELAY_BETWEEN_CHUNKS_SEND)
|
|
1633
|
+
|
|
1634
|
+
// If the packet asked us to do a delay, then do it
|
|
1635
|
+
if (request.waitTime != -1) {
|
|
1636
|
+
Thread.sleep(request.waitTime.toLong())
|
|
1637
|
+
}
|
|
1638
|
+
} catch (e: InterruptedException) {
|
|
1639
|
+
Log.e(TAG,"Nex: Error sending data: ${e.message}")
|
|
1640
|
+
if (isKilled) break
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
} catch (e: InterruptedException) {
|
|
1644
|
+
if (isKilled) {
|
|
1645
|
+
Bridge.log("Nex: Process queue thread interrupted - shutting down")
|
|
1646
|
+
break
|
|
1647
|
+
}
|
|
1648
|
+
Log.e(TAG,"Nex: Error in queue processing: ${e.message}")
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
Bridge.log("Nex: Process queue thread exiting")
|
|
1653
|
+
isWorkerRunning = false
|
|
1654
|
+
}
|
|
1655
|
+
}
|