@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,2439 @@
|
|
|
1
|
+
import CoreBluetooth
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
private func intValue(_ value: Any?) -> Int? {
|
|
5
|
+
if let int = value as? Int { return int }
|
|
6
|
+
if let double = value as? Double { return Int(double) }
|
|
7
|
+
if let number = value as? NSNumber { return number.intValue }
|
|
8
|
+
return nil
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private func stringValue(_ values: [String: Any], _ keys: String...) -> String? {
|
|
12
|
+
stringValue(values, keys)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private func stringValue(_ values: [String: Any], _ keys: [String]) -> String? {
|
|
16
|
+
for key in keys {
|
|
17
|
+
if let value = values[key] as? String {
|
|
18
|
+
return value
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return nil
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private func boolValue(_ values: [String: Any], _ keys: String...) -> Bool? {
|
|
25
|
+
boolValue(values, keys)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private func boolValue(_ values: [String: Any], _ keys: [String]) -> Bool? {
|
|
29
|
+
for key in keys {
|
|
30
|
+
if let value = values[key] as? Bool { return value }
|
|
31
|
+
if let value = values[key] as? NSNumber { return value.boolValue }
|
|
32
|
+
}
|
|
33
|
+
return nil
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private func hasAnyKey(_ values: [String: Any], _ keys: String...) -> Bool {
|
|
37
|
+
hasAnyKey(values, keys)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private func hasAnyKey(_ values: [String: Any], _ keys: [String]) -> Bool {
|
|
41
|
+
keys.contains { values.keys.contains($0) }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private func optionalStringValue(_ values: [String: Any], _ keys: String...) -> String? {
|
|
45
|
+
hasAnyKey(values, keys) ? (stringValue(values, keys) ?? "") : nil
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private func nonEmptyStringValue(_ values: [String: Any], _ keys: String...) -> String? {
|
|
49
|
+
for key in keys {
|
|
50
|
+
guard let value = values[key] as? String else { continue }
|
|
51
|
+
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
52
|
+
if !trimmed.isEmpty {
|
|
53
|
+
return value
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return nil
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private func optionalIntValue(_ values: [String: Any], _ keys: String...) -> Int? {
|
|
60
|
+
guard hasAnyKey(values, keys) else { return nil }
|
|
61
|
+
for key in keys {
|
|
62
|
+
if let value = intValue(values[key]) { return value }
|
|
63
|
+
}
|
|
64
|
+
return nil
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private func optionalBoolValue(_ values: [String: Any], _ keys: String...) -> Bool? {
|
|
68
|
+
hasAnyKey(values, keys) ? (boolValue(values, keys) ?? false) : nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private func stringListValue(_ values: [String: Any], _ key: String) -> [String] {
|
|
72
|
+
values[key] as? [String] ?? []
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private func optionalStringListValue(_ values: [String: Any], _ key: String) -> [String]? {
|
|
76
|
+
values.keys.contains(key) ? stringListValue(values, key) : nil
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private func dictionaryListValue(_ values: [String: Any], _ key: String) -> [[String: Any]] {
|
|
80
|
+
values[key] as? [[String: Any]] ?? []
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private func optionalDictionaryListValue(_ values: [String: Any], _ key: String) -> [[String: Any]]? {
|
|
84
|
+
values.keys.contains(key) ? dictionaryListValue(values, key) : nil
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private func putIfNotNil(_ map: inout [String: Any], _ key: String, _ value: Any?) {
|
|
88
|
+
if let value {
|
|
89
|
+
map[key] = value
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public struct MentraBluetoothSDKConfiguration {
|
|
94
|
+
public static let `default` = MentraBluetoothSDKConfiguration()
|
|
95
|
+
|
|
96
|
+
public init() {}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public enum DeviceModel: String {
|
|
100
|
+
case g1
|
|
101
|
+
case g2
|
|
102
|
+
case mentraLive
|
|
103
|
+
case mentraNex
|
|
104
|
+
case mach1
|
|
105
|
+
case z100
|
|
106
|
+
case frame
|
|
107
|
+
case simulated
|
|
108
|
+
case r1
|
|
109
|
+
|
|
110
|
+
public var deviceType: String {
|
|
111
|
+
switch self {
|
|
112
|
+
case .g1:
|
|
113
|
+
DeviceTypes.G1
|
|
114
|
+
case .g2:
|
|
115
|
+
DeviceTypes.G2
|
|
116
|
+
case .mentraLive:
|
|
117
|
+
DeviceTypes.LIVE
|
|
118
|
+
case .mentraNex:
|
|
119
|
+
DeviceTypes.NEX
|
|
120
|
+
case .mach1:
|
|
121
|
+
DeviceTypes.MACH1
|
|
122
|
+
case .z100:
|
|
123
|
+
DeviceTypes.Z100
|
|
124
|
+
case .frame:
|
|
125
|
+
DeviceTypes.FRAME
|
|
126
|
+
case .simulated:
|
|
127
|
+
DeviceTypes.SIMULATED
|
|
128
|
+
case .r1:
|
|
129
|
+
ControllerTypes.R1
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public static func fromDeviceType(_ deviceType: String?) -> DeviceModel {
|
|
134
|
+
switch deviceType {
|
|
135
|
+
case DeviceTypes.G1:
|
|
136
|
+
.g1
|
|
137
|
+
case DeviceTypes.G2:
|
|
138
|
+
.g2
|
|
139
|
+
case DeviceTypes.LIVE:
|
|
140
|
+
.mentraLive
|
|
141
|
+
case DeviceTypes.NEX:
|
|
142
|
+
.mentraNex
|
|
143
|
+
case DeviceTypes.MACH1:
|
|
144
|
+
.mach1
|
|
145
|
+
case DeviceTypes.Z100:
|
|
146
|
+
.z100
|
|
147
|
+
case DeviceTypes.FRAME:
|
|
148
|
+
.frame
|
|
149
|
+
case DeviceTypes.SIMULATED:
|
|
150
|
+
.simulated
|
|
151
|
+
case ControllerTypes.R1:
|
|
152
|
+
.r1
|
|
153
|
+
default:
|
|
154
|
+
.mentraLive
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
160
|
+
public let model: DeviceModel
|
|
161
|
+
public let name: String
|
|
162
|
+
public let identifier: String?
|
|
163
|
+
public let rssi: Int?
|
|
164
|
+
public let id: String
|
|
165
|
+
|
|
166
|
+
public init(
|
|
167
|
+
model: DeviceModel,
|
|
168
|
+
name: String,
|
|
169
|
+
identifier: String? = nil,
|
|
170
|
+
rssi: Int? = nil,
|
|
171
|
+
id: String? = nil
|
|
172
|
+
) {
|
|
173
|
+
self.model = model
|
|
174
|
+
self.name = name
|
|
175
|
+
self.identifier = identifier
|
|
176
|
+
self.rssi = rssi
|
|
177
|
+
self.id = id ?? identifier.flatMap { $0.isEmpty ? nil : $0 } ?? "\(model.deviceType):\(name)"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public var description: String {
|
|
181
|
+
"Device(model: \(model), name: \(name))"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
var dictionary: [String: Any] {
|
|
185
|
+
var values: [String: Any] = [
|
|
186
|
+
"id": id,
|
|
187
|
+
"model": model.deviceType,
|
|
188
|
+
"name": name,
|
|
189
|
+
]
|
|
190
|
+
if let identifier, !identifier.isEmpty {
|
|
191
|
+
values["address"] = identifier
|
|
192
|
+
}
|
|
193
|
+
if let rssi {
|
|
194
|
+
values["rssi"] = rssi
|
|
195
|
+
}
|
|
196
|
+
return values
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
init?(values: [String: Any]) {
|
|
200
|
+
guard let model = stringValue(values, "model", "deviceModel", "device_model") else { return nil }
|
|
201
|
+
guard let name = stringValue(values, "name", "deviceName", "device_name") else { return nil }
|
|
202
|
+
let identifier = stringValue(values, "address", "deviceAddress", "device_address").flatMap { $0.isEmpty ? nil : $0 }
|
|
203
|
+
let rssi = intValue(values["rssi"]) ?? intValue(values["signalStrength"]) ?? intValue(values["signal_strength"])
|
|
204
|
+
self.init(
|
|
205
|
+
model: DeviceModel.fromDeviceType(model),
|
|
206
|
+
name: name,
|
|
207
|
+
identifier: identifier,
|
|
208
|
+
rssi: rssi,
|
|
209
|
+
id: stringValue(values, "id")
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public struct ConnectOptions {
|
|
215
|
+
public let saveAsDefault: Bool
|
|
216
|
+
public let cancelExistingConnectionAttempt: Bool
|
|
217
|
+
|
|
218
|
+
public init(saveAsDefault: Bool = true, cancelExistingConnectionAttempt: Bool = true) {
|
|
219
|
+
self.saveAsDefault = saveAsDefault
|
|
220
|
+
self.cancelExistingConnectionAttempt = cancelExistingConnectionAttempt
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public struct WifiScanResult: CustomStringConvertible {
|
|
225
|
+
public let ssid: String
|
|
226
|
+
public let requiresPassword: Bool
|
|
227
|
+
public let signalStrength: Int
|
|
228
|
+
public let frequency: Int?
|
|
229
|
+
|
|
230
|
+
public init(ssid: String, requiresPassword: Bool, signalStrength: Int, frequency: Int? = nil) {
|
|
231
|
+
self.ssid = ssid
|
|
232
|
+
self.requiresPassword = requiresPassword
|
|
233
|
+
self.signalStrength = signalStrength
|
|
234
|
+
self.frequency = frequency
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
init(values: [String: Any]) {
|
|
238
|
+
ssid = stringValue(values, "ssid") ?? ""
|
|
239
|
+
requiresPassword = boolValue(values, "requiresPassword", "requires_password", "auth_required") ?? false
|
|
240
|
+
signalStrength = intValue(values["signalStrength"] ?? values["signal_strength"] ?? values["rssi"]) ?? -1
|
|
241
|
+
frequency = intValue(values["frequency"])
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
var dictionary: [String: Any] {
|
|
245
|
+
var values: [String: Any] = [
|
|
246
|
+
"ssid": ssid,
|
|
247
|
+
"requiresPassword": requiresPassword,
|
|
248
|
+
"signalStrength": signalStrength,
|
|
249
|
+
]
|
|
250
|
+
if let frequency {
|
|
251
|
+
values["frequency"] = frequency
|
|
252
|
+
}
|
|
253
|
+
return values
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public var description: String {
|
|
257
|
+
"WifiScanResult(ssid: \(ssid), signalStrength: \(signalStrength))"
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public enum GlassesConnectionState: String, CustomStringConvertible, Equatable {
|
|
262
|
+
case disconnected = "DISCONNECTED"
|
|
263
|
+
case scanning = "SCANNING"
|
|
264
|
+
case connecting = "CONNECTING"
|
|
265
|
+
case bonding = "BONDING"
|
|
266
|
+
case connected = "CONNECTED"
|
|
267
|
+
|
|
268
|
+
public init(_ value: String?) {
|
|
269
|
+
self = Self.fromValue(value) ?? .disconnected
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public static func fromValue(_ value: String?) -> GlassesConnectionState? {
|
|
273
|
+
guard let normalized = value?.trimmingCharacters(in: .whitespacesAndNewlines).uppercased(),
|
|
274
|
+
!normalized.isEmpty
|
|
275
|
+
else {
|
|
276
|
+
return nil
|
|
277
|
+
}
|
|
278
|
+
return Self(rawValue: normalized)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public var isConnected: Bool {
|
|
282
|
+
self == .connected
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public var isBusy: Bool {
|
|
286
|
+
self == .scanning || self == .connecting || self == .bonding
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func statusValues(connected: Bool, fullyBooted: Bool) -> [String: Any] {
|
|
290
|
+
if self == .connected || connected || fullyBooted {
|
|
291
|
+
return ["state": "connected", "fullyBooted": fullyBooted]
|
|
292
|
+
}
|
|
293
|
+
switch self {
|
|
294
|
+
case .scanning:
|
|
295
|
+
return ["state": "scanning"]
|
|
296
|
+
case .connecting:
|
|
297
|
+
return ["state": "connecting"]
|
|
298
|
+
case .bonding:
|
|
299
|
+
return ["state": "bonding"]
|
|
300
|
+
case .connected:
|
|
301
|
+
return ["state": "connected", "fullyBooted": fullyBooted]
|
|
302
|
+
case .disconnected:
|
|
303
|
+
return ["state": "disconnected"]
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public var description: String {
|
|
308
|
+
rawValue
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
public struct GlassesStatus: CustomStringConvertible {
|
|
313
|
+
let values: [String: Any]
|
|
314
|
+
|
|
315
|
+
init(values: [String: Any]) {
|
|
316
|
+
self.values = values
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
public func applying(_ update: GlassesStatusUpdate) -> GlassesStatus {
|
|
320
|
+
GlassesStatus(values: values.merging(update.values) { _, new in new })
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
public func withBattery(level: Int, charging: Bool) -> GlassesStatus {
|
|
324
|
+
applying(GlassesStatusUpdate(values: ["batteryLevel": level, "charging": charging]))
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
public func withWifi(_ wifi: WifiStatus) -> GlassesStatus {
|
|
328
|
+
applying(GlassesStatusUpdate(values: wifi.storeValues))
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
public func withHotspot(_ hotspot: HotspotStatus) -> GlassesStatus {
|
|
332
|
+
applying(GlassesStatusUpdate(values: hotspot.storeValues))
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public func disconnected() -> GlassesStatus {
|
|
336
|
+
applying(GlassesStatusUpdate(values: [
|
|
337
|
+
"connected": false,
|
|
338
|
+
"connectionState": "DISCONNECTED",
|
|
339
|
+
"fullyBooted": false,
|
|
340
|
+
"batteryLevel": -1,
|
|
341
|
+
"charging": false,
|
|
342
|
+
"hotspotEnabled": false,
|
|
343
|
+
"hotspotGatewayIp": "",
|
|
344
|
+
"hotspotPassword": "",
|
|
345
|
+
"hotspotSsid": "",
|
|
346
|
+
"wifiConnected": false,
|
|
347
|
+
"wifiSsid": "",
|
|
348
|
+
"wifiLocalIp": "",
|
|
349
|
+
"signalStrength": -1,
|
|
350
|
+
"signalStrengthUpdatedAt": 0,
|
|
351
|
+
]))
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
public var fullyBooted: Bool { boolValue(values, "fullyBooted") ?? false }
|
|
355
|
+
public var connected: Bool { boolValue(values, "connected") ?? false }
|
|
356
|
+
public var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
|
|
357
|
+
public var connectionState: GlassesConnectionState { GlassesConnectionState(stringValue(values, "connectionState")) }
|
|
358
|
+
public var btcConnected: Bool { boolValue(values, "btcConnected") ?? false }
|
|
359
|
+
public var signalStrength: Int { intValue(values["signalStrength"]) ?? -1 }
|
|
360
|
+
public var signalStrengthUpdatedAt: Int { intValue(values["signalStrengthUpdatedAt"]) ?? 0 }
|
|
361
|
+
public var deviceModel: String { stringValue(values, "deviceModel") ?? "" }
|
|
362
|
+
public var androidVersion: String { stringValue(values, "androidVersion") ?? "" }
|
|
363
|
+
public var firmwareVersion: String { stringValue(values, "firmwareVersion", "fwVersion") ?? "" }
|
|
364
|
+
public var besFirmwareVersion: String { stringValue(values, "besFwVersion", "besFirmwareVersion") ?? "" }
|
|
365
|
+
public var mtkFirmwareVersion: String { stringValue(values, "mtkFwVersion", "mtkFirmwareVersion") ?? "" }
|
|
366
|
+
public var btMacAddress: String { stringValue(values, "btMacAddress") ?? "" }
|
|
367
|
+
public var leftMacAddress: String { stringValue(values, "leftMacAddress") ?? "" }
|
|
368
|
+
public var rightMacAddress: String { stringValue(values, "rightMacAddress") ?? "" }
|
|
369
|
+
public var macAddress: String { stringValue(values, "macAddress") ?? "" }
|
|
370
|
+
public var buildNumber: String { stringValue(values, "buildNumber") ?? "" }
|
|
371
|
+
public var otaVersionUrl: String { stringValue(values, "otaVersionUrl") ?? "" }
|
|
372
|
+
public var appVersion: String { stringValue(values, "appVersion") ?? "" }
|
|
373
|
+
public var bluetoothName: String { stringValue(values, "bluetoothName") ?? "" }
|
|
374
|
+
public var serialNumber: String { stringValue(values, "serialNumber") ?? "" }
|
|
375
|
+
public var style: String { stringValue(values, "style") ?? "" }
|
|
376
|
+
public var color: String { stringValue(values, "color") ?? "" }
|
|
377
|
+
public var wifi: WifiStatus { WifiStatus.fromStoreValues(values) ?? .disconnected }
|
|
378
|
+
public var hotspot: HotspotStatus { HotspotStatus.fromStoreValues(values) ?? .disabled }
|
|
379
|
+
public var dictionary: [String: Any] { Self.dictionary(from: values) }
|
|
380
|
+
public var batteryLevel: Int { intValue(values["batteryLevel"]) ?? -1 }
|
|
381
|
+
public var charging: Bool { boolValue(values, "charging") ?? false }
|
|
382
|
+
public var caseBatteryLevel: Int { intValue(values["caseBatteryLevel"]) ?? -1 }
|
|
383
|
+
public var caseCharging: Bool { boolValue(values, "caseCharging") ?? false }
|
|
384
|
+
public var caseOpen: Bool { boolValue(values, "caseOpen") ?? true }
|
|
385
|
+
public var caseRemoved: Bool { boolValue(values, "caseRemoved") ?? true }
|
|
386
|
+
public var headUp: Bool { boolValue(values, "headUp") ?? false }
|
|
387
|
+
public var controllerConnected: Bool { boolValue(values, "controllerConnected") ?? false }
|
|
388
|
+
public var controllerFullyBooted: Bool { boolValue(values, "controllerFullyBooted") ?? false }
|
|
389
|
+
public var controllerMacAddress: String { stringValue(values, "controllerMacAddress") ?? "" }
|
|
390
|
+
public var controllerBatteryLevel: Int { intValue(values["controllerBatteryLevel"]) ?? -1 }
|
|
391
|
+
public var controllerSignalStrength: Int { intValue(values["controllerSignalStrength"]) ?? -1 }
|
|
392
|
+
public var ringSignalStrength: Int { intValue(values["ringSignalStrength"]) ?? -1 }
|
|
393
|
+
|
|
394
|
+
public var description: String {
|
|
395
|
+
values.description
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
static func dictionary(from values: [String: Any]) -> [String: Any] {
|
|
399
|
+
var dictionary = values
|
|
400
|
+
dictionary["connection"] = GlassesConnectionState(stringValue(values, "connectionState")).statusValues(
|
|
401
|
+
connected: boolValue(values, "connected") ?? false,
|
|
402
|
+
fullyBooted: boolValue(values, "fullyBooted") ?? false
|
|
403
|
+
)
|
|
404
|
+
dictionary.removeValue(forKey: "connected")
|
|
405
|
+
dictionary.removeValue(forKey: "fullyBooted")
|
|
406
|
+
dictionary.removeValue(forKey: "connectionState")
|
|
407
|
+
dictionary["wifi"] = (WifiStatus.fromStoreValues(values) ?? .disconnected).values
|
|
408
|
+
dictionary["hotspot"] = (HotspotStatus.fromStoreValues(values) ?? .disabled).values
|
|
409
|
+
dictionary.removeValue(forKey: "wifiConnected")
|
|
410
|
+
dictionary.removeValue(forKey: "wifiSsid")
|
|
411
|
+
dictionary.removeValue(forKey: "wifiLocalIp")
|
|
412
|
+
dictionary.removeValue(forKey: "hotspotEnabled")
|
|
413
|
+
dictionary.removeValue(forKey: "hotspotSsid")
|
|
414
|
+
dictionary.removeValue(forKey: "hotspotPassword")
|
|
415
|
+
dictionary.removeValue(forKey: "hotspotGatewayIp")
|
|
416
|
+
dictionary.removeValue(forKey: "hotspotLocalIp")
|
|
417
|
+
return dictionary
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
static func updateDictionary(from values: [String: Any]) -> [String: Any] {
|
|
421
|
+
var dictionary = values
|
|
422
|
+
if hasAnyKey(values, "connection", "connected", "fullyBooted", "connectionState") {
|
|
423
|
+
dictionary["connection"] = (values["connection"] as? [String: Any])
|
|
424
|
+
?? GlassesConnectionState(stringValue(values, "connectionState")).statusValues(
|
|
425
|
+
connected: boolValue(values, "connected") ?? false,
|
|
426
|
+
fullyBooted: boolValue(values, "fullyBooted") ?? false
|
|
427
|
+
)
|
|
428
|
+
dictionary.removeValue(forKey: "connected")
|
|
429
|
+
dictionary.removeValue(forKey: "fullyBooted")
|
|
430
|
+
dictionary.removeValue(forKey: "connectionState")
|
|
431
|
+
}
|
|
432
|
+
if hasAnyKey(values, "wifi", "wifiConnected", "wifiSsid", "wifiLocalIp") {
|
|
433
|
+
let wifi = (values["wifi"] as? [String: Any]).flatMap(WifiStatus.init(values:))
|
|
434
|
+
?? WifiStatus.fromStoreValues(values)
|
|
435
|
+
if let wifi {
|
|
436
|
+
dictionary["wifi"] = wifi.values
|
|
437
|
+
}
|
|
438
|
+
dictionary.removeValue(forKey: "wifiConnected")
|
|
439
|
+
dictionary.removeValue(forKey: "wifiSsid")
|
|
440
|
+
dictionary.removeValue(forKey: "wifiLocalIp")
|
|
441
|
+
}
|
|
442
|
+
if hasAnyKey(values, "hotspot") {
|
|
443
|
+
if let hotspot = (values["hotspot"] as? [String: Any]).flatMap(HotspotStatus.init(values:)) {
|
|
444
|
+
dictionary["hotspot"] = hotspot.values
|
|
445
|
+
}
|
|
446
|
+
} else if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp", "hotspotLocalIp") {
|
|
447
|
+
if let hotspot = HotspotStatus.fromStoreValues(values) {
|
|
448
|
+
dictionary["hotspot"] = hotspot.values
|
|
449
|
+
}
|
|
450
|
+
dictionary.removeValue(forKey: "hotspotEnabled")
|
|
451
|
+
dictionary.removeValue(forKey: "hotspotSsid")
|
|
452
|
+
dictionary.removeValue(forKey: "hotspotPassword")
|
|
453
|
+
dictionary.removeValue(forKey: "hotspotGatewayIp")
|
|
454
|
+
dictionary.removeValue(forKey: "hotspotLocalIp")
|
|
455
|
+
}
|
|
456
|
+
return dictionary
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
public struct BluetoothStatus: CustomStringConvertible {
|
|
461
|
+
let values: [String: Any]
|
|
462
|
+
|
|
463
|
+
init(values: [String: Any]) {
|
|
464
|
+
self.values = Self.normalized(values)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private static func normalized(_ values: [String: Any]) -> [String: Any] {
|
|
468
|
+
var normalizedValues = values
|
|
469
|
+
if let searchResults = values["searchResults"] as? [[String: Any]] {
|
|
470
|
+
normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
|
|
471
|
+
}
|
|
472
|
+
return normalizedValues
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
public func applying(_ update: BluetoothStatusUpdate) -> BluetoothStatus {
|
|
476
|
+
BluetoothStatus(values: values.merging(update.values) { _, new in new })
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
public func withDefaultDevice(_ device: Device?) -> BluetoothStatus {
|
|
480
|
+
applying(BluetoothStatusUpdate(values: [
|
|
481
|
+
"default_wearable": device?.model.deviceType ?? "",
|
|
482
|
+
"device_name": device?.name ?? "",
|
|
483
|
+
"device_address": device?.identifier ?? "",
|
|
484
|
+
]))
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
public var searching: Bool { boolValue(values, "searching") ?? false }
|
|
488
|
+
public var searchingController: Bool { boolValue(values, "searchingController") ?? false }
|
|
489
|
+
public var systemMicUnavailable: Bool { boolValue(values, "systemMicUnavailable") ?? false }
|
|
490
|
+
public var micEnabled: Bool { boolValue(values, "micEnabled") ?? false }
|
|
491
|
+
public var currentMic: String { stringValue(values, "currentMic") ?? "" }
|
|
492
|
+
public var micRanking: [String] { stringListValue(values, "micRanking") }
|
|
493
|
+
public var searchResults: [Device] {
|
|
494
|
+
dictionaryListValue(values, "searchResults").compactMap(Device.init(values:))
|
|
495
|
+
}
|
|
496
|
+
public var wifiScanResults: [WifiScanResult] {
|
|
497
|
+
dictionaryListValue(values, "wifiScanResults").map(WifiScanResult.init(values:))
|
|
498
|
+
}
|
|
499
|
+
public var lastLog: [String] { stringListValue(values, "lastLog") }
|
|
500
|
+
public var otherBtConnected: Bool { boolValue(values, "otherBtConnected") ?? false }
|
|
501
|
+
public var defaultWearable: String { stringValue(values, "default_wearable") ?? "" }
|
|
502
|
+
public var pendingWearable: String { stringValue(values, "pending_wearable") ?? "" }
|
|
503
|
+
public var deviceName: String { stringValue(values, "device_name") ?? "" }
|
|
504
|
+
public var deviceAddress: String { stringValue(values, "device_address") ?? "" }
|
|
505
|
+
public var defaultController: String { stringValue(values, "default_controller") ?? "" }
|
|
506
|
+
public var pendingController: String { stringValue(values, "pending_controller") ?? "" }
|
|
507
|
+
public var controllerDeviceName: String { stringValue(values, "controller_device_name") ?? "" }
|
|
508
|
+
public var screenDisabled: Bool { boolValue(values, "screen_disabled") ?? false }
|
|
509
|
+
public var preferredMic: String { stringValue(values, "preferred_mic") ?? "auto" }
|
|
510
|
+
public var sensingEnabled: Bool { boolValue(values, "sensing_enabled") ?? true }
|
|
511
|
+
public var powerSavingMode: Bool { boolValue(values, "power_saving_mode") ?? false }
|
|
512
|
+
public var brightness: Int { intValue(values["brightness"]) ?? 50 }
|
|
513
|
+
public var autoBrightness: Bool { boolValue(values, "auto_brightness") ?? true }
|
|
514
|
+
public var dashboardHeight: Int { intValue(values["dashboard_height"]) ?? 4 }
|
|
515
|
+
public var dashboardDepth: Int { intValue(values["dashboard_depth"]) ?? 2 }
|
|
516
|
+
public var headUpAngle: Int { intValue(values["head_up_angle"]) ?? 30 }
|
|
517
|
+
public var contextualDashboard: Bool { boolValue(values, "contextual_dashboard") ?? true }
|
|
518
|
+
public var galleryModeAuto: Bool { boolValue(values, "gallery_mode") ?? true }
|
|
519
|
+
public var buttonPhotoSize: ButtonPhotoSize {
|
|
520
|
+
ButtonPhotoSize(rawValue: stringValue(values, "button_photo_size") ?? "") ?? .medium
|
|
521
|
+
}
|
|
522
|
+
public var buttonCameraLed: Bool { boolValue(values, "button_camera_led") ?? true }
|
|
523
|
+
public var buttonMaxRecordingTime: Int { intValue(values["button_max_recording_time"]) ?? 10 }
|
|
524
|
+
public var buttonVideoWidth: Int { intValue(values["button_video_width"]) ?? 1280 }
|
|
525
|
+
public var buttonVideoHeight: Int { intValue(values["button_video_height"]) ?? 720 }
|
|
526
|
+
public var buttonVideoFps: Int { intValue(values["button_video_fps"]) ?? 30 }
|
|
527
|
+
public var shouldSendPcm: Bool { boolValue(values, "should_send_pcm") ?? false }
|
|
528
|
+
public var shouldSendLc3: Bool { boolValue(values, "should_send_lc3") ?? false }
|
|
529
|
+
public var shouldSendTranscript: Bool { boolValue(values, "should_send_transcript") ?? false }
|
|
530
|
+
public var bypassVad: Bool { boolValue(values, "bypass_vad") ?? false }
|
|
531
|
+
public var offlineCaptionsRunning: Bool { boolValue(values, "offline_captions_running") ?? false }
|
|
532
|
+
public var localSttFallbackActive: Bool { boolValue(values, "local_stt_fallback_active") ?? false }
|
|
533
|
+
public var shouldSendBootingMessage: Bool { boolValue(values, "shouldSendBootingMessage") ?? true }
|
|
534
|
+
|
|
535
|
+
public var defaultDevice: Device? {
|
|
536
|
+
guard !defaultWearable.isEmpty else { return nil }
|
|
537
|
+
return Device(
|
|
538
|
+
model: DeviceModel.fromDeviceType(defaultWearable),
|
|
539
|
+
name: deviceName,
|
|
540
|
+
identifier: deviceAddress.isEmpty ? nil : deviceAddress
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
public var description: String {
|
|
545
|
+
values.description
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
public struct GlassesStatusUpdate: CustomStringConvertible {
|
|
550
|
+
let values: [String: Any]
|
|
551
|
+
|
|
552
|
+
init(values: [String: Any]) {
|
|
553
|
+
self.values = values
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
public var fullyBooted: Bool? { optionalBoolValue(values, "fullyBooted") }
|
|
557
|
+
public var connected: Bool? { optionalBoolValue(values, "connected") }
|
|
558
|
+
public var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
|
|
559
|
+
public var connectionState: GlassesConnectionState? {
|
|
560
|
+
GlassesConnectionState.fromValue(optionalStringValue(values, "connectionState"))
|
|
561
|
+
}
|
|
562
|
+
public var btcConnected: Bool? { optionalBoolValue(values, "btcConnected") }
|
|
563
|
+
public var signalStrength: Int? { optionalIntValue(values, "signalStrength") }
|
|
564
|
+
public var signalStrengthUpdatedAt: Int? { optionalIntValue(values, "signalStrengthUpdatedAt") }
|
|
565
|
+
public var deviceModel: String? { optionalStringValue(values, "deviceModel") }
|
|
566
|
+
public var androidVersion: String? { optionalStringValue(values, "androidVersion") }
|
|
567
|
+
public var firmwareVersion: String? { optionalStringValue(values, "firmwareVersion", "fwVersion") }
|
|
568
|
+
public var besFirmwareVersion: String? { optionalStringValue(values, "besFwVersion", "besFirmwareVersion") }
|
|
569
|
+
public var mtkFirmwareVersion: String? { optionalStringValue(values, "mtkFwVersion", "mtkFirmwareVersion") }
|
|
570
|
+
public var btMacAddress: String? { optionalStringValue(values, "btMacAddress") }
|
|
571
|
+
public var leftMacAddress: String? { optionalStringValue(values, "leftMacAddress") }
|
|
572
|
+
public var rightMacAddress: String? { optionalStringValue(values, "rightMacAddress") }
|
|
573
|
+
public var macAddress: String? { optionalStringValue(values, "macAddress") }
|
|
574
|
+
public var buildNumber: String? { optionalStringValue(values, "buildNumber") }
|
|
575
|
+
public var otaVersionUrl: String? { optionalStringValue(values, "otaVersionUrl") }
|
|
576
|
+
public var appVersion: String? { optionalStringValue(values, "appVersion") }
|
|
577
|
+
public var bluetoothName: String? { optionalStringValue(values, "bluetoothName") }
|
|
578
|
+
public var serialNumber: String? { optionalStringValue(values, "serialNumber") }
|
|
579
|
+
public var style: String? { optionalStringValue(values, "style") }
|
|
580
|
+
public var color: String? { optionalStringValue(values, "color") }
|
|
581
|
+
public var wifi: WifiStatus? {
|
|
582
|
+
if let wifi = values["wifi"] as? [String: Any] {
|
|
583
|
+
return WifiStatus(values: wifi)
|
|
584
|
+
}
|
|
585
|
+
if hasAnyKey(values, "wifiConnected", "wifiSsid", "wifiLocalIp") {
|
|
586
|
+
return WifiStatus.fromStoreValues(values)
|
|
587
|
+
}
|
|
588
|
+
return nil
|
|
589
|
+
}
|
|
590
|
+
public var hotspot: HotspotStatus? {
|
|
591
|
+
if let hotspot = values["hotspot"] as? [String: Any] {
|
|
592
|
+
return HotspotStatus(values: hotspot)
|
|
593
|
+
}
|
|
594
|
+
if hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp", "hotspotLocalIp") {
|
|
595
|
+
return HotspotStatus.fromStoreValues(values)
|
|
596
|
+
}
|
|
597
|
+
return nil
|
|
598
|
+
}
|
|
599
|
+
public var dictionary: [String: Any] { GlassesStatus.updateDictionary(from: values) }
|
|
600
|
+
public var batteryLevel: Int? { optionalIntValue(values, "batteryLevel") }
|
|
601
|
+
public var charging: Bool? { optionalBoolValue(values, "charging") }
|
|
602
|
+
public var caseBatteryLevel: Int? { optionalIntValue(values, "caseBatteryLevel") }
|
|
603
|
+
public var caseCharging: Bool? { optionalBoolValue(values, "caseCharging") }
|
|
604
|
+
public var caseOpen: Bool? { optionalBoolValue(values, "caseOpen") }
|
|
605
|
+
public var caseRemoved: Bool? { optionalBoolValue(values, "caseRemoved") }
|
|
606
|
+
public var headUp: Bool? { optionalBoolValue(values, "headUp") }
|
|
607
|
+
public var controllerConnected: Bool? { optionalBoolValue(values, "controllerConnected") }
|
|
608
|
+
public var controllerFullyBooted: Bool? { optionalBoolValue(values, "controllerFullyBooted") }
|
|
609
|
+
public var controllerMacAddress: String? { optionalStringValue(values, "controllerMacAddress") }
|
|
610
|
+
public var controllerBatteryLevel: Int? { optionalIntValue(values, "controllerBatteryLevel") }
|
|
611
|
+
public var controllerSignalStrength: Int? { optionalIntValue(values, "controllerSignalStrength") }
|
|
612
|
+
public var ringSignalStrength: Int? { optionalIntValue(values, "ringSignalStrength") }
|
|
613
|
+
|
|
614
|
+
public var description: String {
|
|
615
|
+
values.description
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
public struct BluetoothStatusUpdate: CustomStringConvertible {
|
|
620
|
+
let values: [String: Any]
|
|
621
|
+
|
|
622
|
+
init(values: [String: Any]) {
|
|
623
|
+
var normalizedValues = values
|
|
624
|
+
if let searchResults = values["searchResults"] as? [[String: Any]] {
|
|
625
|
+
normalizedValues["searchResults"] = searchResults.compactMap { Device(values: $0)?.dictionary }
|
|
626
|
+
}
|
|
627
|
+
self.values = normalizedValues
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
public var searching: Bool? { optionalBoolValue(values, "searching") }
|
|
631
|
+
public var searchingController: Bool? { optionalBoolValue(values, "searchingController") }
|
|
632
|
+
public var systemMicUnavailable: Bool? { optionalBoolValue(values, "systemMicUnavailable") }
|
|
633
|
+
public var micEnabled: Bool? { optionalBoolValue(values, "micEnabled") }
|
|
634
|
+
public var currentMic: String? { optionalStringValue(values, "currentMic") }
|
|
635
|
+
public var micRanking: [String]? { optionalStringListValue(values, "micRanking") }
|
|
636
|
+
public var searchResults: [Device]? {
|
|
637
|
+
optionalDictionaryListValue(values, "searchResults")?.compactMap(Device.init(values:))
|
|
638
|
+
}
|
|
639
|
+
public var wifiScanResults: [WifiScanResult]? {
|
|
640
|
+
optionalDictionaryListValue(values, "wifiScanResults")?.map(WifiScanResult.init(values:))
|
|
641
|
+
}
|
|
642
|
+
public var lastLog: [String]? { optionalStringListValue(values, "lastLog") }
|
|
643
|
+
public var otherBtConnected: Bool? { optionalBoolValue(values, "otherBtConnected") }
|
|
644
|
+
public var defaultWearable: String? { optionalStringValue(values, "default_wearable") }
|
|
645
|
+
public var pendingWearable: String? { optionalStringValue(values, "pending_wearable") }
|
|
646
|
+
public var deviceName: String? { optionalStringValue(values, "device_name") }
|
|
647
|
+
public var deviceAddress: String? { optionalStringValue(values, "device_address") }
|
|
648
|
+
public var defaultController: String? { optionalStringValue(values, "default_controller") }
|
|
649
|
+
public var pendingController: String? { optionalStringValue(values, "pending_controller") }
|
|
650
|
+
public var controllerDeviceName: String? { optionalStringValue(values, "controller_device_name") }
|
|
651
|
+
public var screenDisabled: Bool? { optionalBoolValue(values, "screen_disabled") }
|
|
652
|
+
public var preferredMic: String? { optionalStringValue(values, "preferred_mic") }
|
|
653
|
+
public var sensingEnabled: Bool? { optionalBoolValue(values, "sensing_enabled") }
|
|
654
|
+
public var powerSavingMode: Bool? { optionalBoolValue(values, "power_saving_mode") }
|
|
655
|
+
public var brightness: Int? { optionalIntValue(values, "brightness") }
|
|
656
|
+
public var autoBrightness: Bool? { optionalBoolValue(values, "auto_brightness") }
|
|
657
|
+
public var dashboardHeight: Int? { optionalIntValue(values, "dashboard_height") }
|
|
658
|
+
public var dashboardDepth: Int? { optionalIntValue(values, "dashboard_depth") }
|
|
659
|
+
public var headUpAngle: Int? { optionalIntValue(values, "head_up_angle") }
|
|
660
|
+
public var contextualDashboard: Bool? { optionalBoolValue(values, "contextual_dashboard") }
|
|
661
|
+
public var galleryModeAuto: Bool? { optionalBoolValue(values, "gallery_mode") }
|
|
662
|
+
public var buttonPhotoSize: ButtonPhotoSize? {
|
|
663
|
+
optionalStringValue(values, "button_photo_size").flatMap(ButtonPhotoSize.init(rawValue:))
|
|
664
|
+
}
|
|
665
|
+
public var buttonCameraLed: Bool? { optionalBoolValue(values, "button_camera_led") }
|
|
666
|
+
public var buttonMaxRecordingTime: Int? { optionalIntValue(values, "button_max_recording_time") }
|
|
667
|
+
public var buttonVideoWidth: Int? { optionalIntValue(values, "button_video_width") }
|
|
668
|
+
public var buttonVideoHeight: Int? { optionalIntValue(values, "button_video_height") }
|
|
669
|
+
public var buttonVideoFps: Int? { optionalIntValue(values, "button_video_fps") }
|
|
670
|
+
public var shouldSendPcm: Bool? { optionalBoolValue(values, "should_send_pcm") }
|
|
671
|
+
public var shouldSendLc3: Bool? { optionalBoolValue(values, "should_send_lc3") }
|
|
672
|
+
public var shouldSendTranscript: Bool? { optionalBoolValue(values, "should_send_transcript") }
|
|
673
|
+
public var bypassVad: Bool? { optionalBoolValue(values, "bypass_vad") }
|
|
674
|
+
public var offlineCaptionsRunning: Bool? { optionalBoolValue(values, "offline_captions_running") }
|
|
675
|
+
public var localSttFallbackActive: Bool? { optionalBoolValue(values, "local_stt_fallback_active") }
|
|
676
|
+
public var shouldSendBootingMessage: Bool? { optionalBoolValue(values, "shouldSendBootingMessage") }
|
|
677
|
+
|
|
678
|
+
public var description: String {
|
|
679
|
+
values.description
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
public struct DisplayTextRequest {
|
|
684
|
+
public let text: String
|
|
685
|
+
public let x: Int
|
|
686
|
+
public let y: Int
|
|
687
|
+
public let size: Int
|
|
688
|
+
|
|
689
|
+
public init(text: String, x: Int = 0, y: Int = 0, size: Int = 24) {
|
|
690
|
+
self.text = text
|
|
691
|
+
self.x = x
|
|
692
|
+
self.y = y
|
|
693
|
+
self.size = size
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
var dictionary: [String: Any] {
|
|
697
|
+
[
|
|
698
|
+
"text": text,
|
|
699
|
+
"x": x,
|
|
700
|
+
"y": y,
|
|
701
|
+
"size": size,
|
|
702
|
+
]
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
public struct DisplayEventRequest {
|
|
707
|
+
public let values: [String: Any]
|
|
708
|
+
|
|
709
|
+
public init(values: [String: Any]) {
|
|
710
|
+
self.values = values
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
public struct DashboardPositionRequest {
|
|
715
|
+
public let height: Int
|
|
716
|
+
public let depth: Int
|
|
717
|
+
|
|
718
|
+
public init(height: Int, depth: Int) {
|
|
719
|
+
self.height = height
|
|
720
|
+
self.depth = depth
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
public struct DashboardMenuItem {
|
|
725
|
+
public let title: String
|
|
726
|
+
public let packageName: String
|
|
727
|
+
public let values: [String: Any]
|
|
728
|
+
|
|
729
|
+
public init(title: String, packageName: String, values: [String: Any] = [:]) {
|
|
730
|
+
self.title = title
|
|
731
|
+
self.packageName = packageName
|
|
732
|
+
self.values = values
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
var dictionary: [String: Any] {
|
|
736
|
+
values.merging(["title": title, "packageName": packageName]) { _, new in new }
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
public enum GalleryMode {
|
|
741
|
+
case auto
|
|
742
|
+
case manual
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
public enum PhotoSize: String {
|
|
746
|
+
case small
|
|
747
|
+
case medium
|
|
748
|
+
case large
|
|
749
|
+
case full
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
public enum ButtonPhotoSize: String {
|
|
753
|
+
case small
|
|
754
|
+
case medium
|
|
755
|
+
case large
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
public enum PhotoCompression: String {
|
|
759
|
+
case none
|
|
760
|
+
case medium
|
|
761
|
+
case heavy
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
public struct ButtonPhotoSettings {
|
|
765
|
+
public let size: ButtonPhotoSize
|
|
766
|
+
|
|
767
|
+
public init(size: ButtonPhotoSize) {
|
|
768
|
+
self.size = size
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
public struct ButtonVideoRecordingSettings {
|
|
773
|
+
public let width: Int
|
|
774
|
+
public let height: Int
|
|
775
|
+
public let fps: Int
|
|
776
|
+
|
|
777
|
+
public init(width: Int, height: Int, fps: Int) {
|
|
778
|
+
self.width = width
|
|
779
|
+
self.height = height
|
|
780
|
+
self.fps = fps
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
public enum CameraFov {
|
|
785
|
+
case standard
|
|
786
|
+
case wide
|
|
787
|
+
|
|
788
|
+
var value: [String: Int] {
|
|
789
|
+
switch self {
|
|
790
|
+
case .standard:
|
|
791
|
+
["fov": 118, "roi_position": 0]
|
|
792
|
+
case .wide:
|
|
793
|
+
["fov": 118, "roi_position": 0]
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
public struct MicConfiguration {
|
|
799
|
+
public let sendPcmData: Bool
|
|
800
|
+
public let sendTranscript: Bool
|
|
801
|
+
public let bypassVad: Bool
|
|
802
|
+
public let sendLc3Data: Bool
|
|
803
|
+
|
|
804
|
+
public init(
|
|
805
|
+
sendPcmData: Bool,
|
|
806
|
+
sendTranscript: Bool,
|
|
807
|
+
bypassVad: Bool,
|
|
808
|
+
sendLc3Data: Bool = false
|
|
809
|
+
) {
|
|
810
|
+
self.sendPcmData = sendPcmData
|
|
811
|
+
self.sendTranscript = sendTranscript
|
|
812
|
+
self.bypassVad = bypassVad
|
|
813
|
+
self.sendLc3Data = sendLc3Data
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
public enum MicPreference: String {
|
|
818
|
+
case auto
|
|
819
|
+
case phone
|
|
820
|
+
case glasses
|
|
821
|
+
case bluetooth
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
public struct PhotoRequest {
|
|
825
|
+
public let requestId: String
|
|
826
|
+
public let appId: String
|
|
827
|
+
public let size: PhotoSize
|
|
828
|
+
public let webhookUrl: String?
|
|
829
|
+
public let authToken: String?
|
|
830
|
+
public let compress: PhotoCompression?
|
|
831
|
+
public let flash: Bool
|
|
832
|
+
public let sound: Bool
|
|
833
|
+
|
|
834
|
+
public init(
|
|
835
|
+
requestId: String,
|
|
836
|
+
appId: String,
|
|
837
|
+
size: PhotoSize,
|
|
838
|
+
webhookUrl: String? = nil,
|
|
839
|
+
authToken: String? = nil,
|
|
840
|
+
compress: PhotoCompression? = nil,
|
|
841
|
+
flash: Bool,
|
|
842
|
+
sound: Bool
|
|
843
|
+
) {
|
|
844
|
+
self.requestId = requestId
|
|
845
|
+
self.appId = appId
|
|
846
|
+
self.size = size
|
|
847
|
+
self.webhookUrl = webhookUrl
|
|
848
|
+
self.authToken = authToken
|
|
849
|
+
self.compress = compress
|
|
850
|
+
self.flash = flash
|
|
851
|
+
self.sound = sound
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
public struct StreamVideoConfig {
|
|
856
|
+
public let width: Int?
|
|
857
|
+
public let height: Int?
|
|
858
|
+
public let bitrate: Int?
|
|
859
|
+
public let frameRate: Int?
|
|
860
|
+
|
|
861
|
+
public init(
|
|
862
|
+
width: Int? = nil,
|
|
863
|
+
height: Int? = nil,
|
|
864
|
+
bitrate: Int? = nil,
|
|
865
|
+
frameRate: Int? = nil
|
|
866
|
+
) {
|
|
867
|
+
self.width = width
|
|
868
|
+
self.height = height
|
|
869
|
+
self.bitrate = bitrate
|
|
870
|
+
self.frameRate = frameRate
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
var dictionary: [String: Any] {
|
|
874
|
+
var values: [String: Any] = [:]
|
|
875
|
+
if let width { values["width"] = width }
|
|
876
|
+
if let height { values["height"] = height }
|
|
877
|
+
if let bitrate { values["bitrate"] = bitrate }
|
|
878
|
+
if let frameRate { values["frameRate"] = frameRate }
|
|
879
|
+
return values
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
init?(values: [String: Any]?) {
|
|
883
|
+
guard let values else { return nil }
|
|
884
|
+
self.init(
|
|
885
|
+
width: intValue(values["width"] ?? values["w"]),
|
|
886
|
+
height: intValue(values["height"] ?? values["h"]),
|
|
887
|
+
bitrate: intValue(values["bitrate"] ?? values["br"]),
|
|
888
|
+
frameRate: intValue(values["frameRate"] ?? values["fr"])
|
|
889
|
+
)
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
public struct StreamAudioConfig {
|
|
894
|
+
public let bitrate: Int?
|
|
895
|
+
public let sampleRate: Int?
|
|
896
|
+
public let echoCancellation: Bool?
|
|
897
|
+
public let noiseSuppression: Bool?
|
|
898
|
+
|
|
899
|
+
public init(
|
|
900
|
+
bitrate: Int? = nil,
|
|
901
|
+
sampleRate: Int? = nil,
|
|
902
|
+
echoCancellation: Bool? = nil,
|
|
903
|
+
noiseSuppression: Bool? = nil
|
|
904
|
+
) {
|
|
905
|
+
self.bitrate = bitrate
|
|
906
|
+
self.sampleRate = sampleRate
|
|
907
|
+
self.echoCancellation = echoCancellation
|
|
908
|
+
self.noiseSuppression = noiseSuppression
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
var dictionary: [String: Any] {
|
|
912
|
+
var values: [String: Any] = [:]
|
|
913
|
+
if let bitrate { values["bitrate"] = bitrate }
|
|
914
|
+
if let sampleRate { values["sampleRate"] = sampleRate }
|
|
915
|
+
if let echoCancellation { values["echoCancellation"] = echoCancellation }
|
|
916
|
+
if let noiseSuppression { values["noiseSuppression"] = noiseSuppression }
|
|
917
|
+
return values
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
init?(values: [String: Any]?) {
|
|
921
|
+
guard let values else { return nil }
|
|
922
|
+
self.init(
|
|
923
|
+
bitrate: intValue(values["bitrate"] ?? values["br"]),
|
|
924
|
+
sampleRate: intValue(values["sampleRate"] ?? values["sr"]),
|
|
925
|
+
echoCancellation: values["echoCancellation"] as? Bool ?? values["ec"] as? Bool,
|
|
926
|
+
noiseSuppression: values["noiseSuppression"] as? Bool ?? values["ns"] as? Bool
|
|
927
|
+
)
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
public struct StreamRequest {
|
|
932
|
+
public let streamUrl: String
|
|
933
|
+
public let streamId: String
|
|
934
|
+
public let keepAlive: Bool
|
|
935
|
+
public let keepAliveIntervalSeconds: Int
|
|
936
|
+
public let flash: Bool
|
|
937
|
+
public let sound: Bool
|
|
938
|
+
public let video: StreamVideoConfig?
|
|
939
|
+
public let audio: StreamAudioConfig?
|
|
940
|
+
public let extraValues: [String: Any]
|
|
941
|
+
|
|
942
|
+
public init(
|
|
943
|
+
streamUrl: String,
|
|
944
|
+
streamId: String = "",
|
|
945
|
+
keepAlive: Bool = true,
|
|
946
|
+
keepAliveIntervalSeconds: Int = 15,
|
|
947
|
+
flash: Bool = true,
|
|
948
|
+
sound: Bool = true,
|
|
949
|
+
video: StreamVideoConfig? = nil,
|
|
950
|
+
audio: StreamAudioConfig? = nil,
|
|
951
|
+
extraValues: [String: Any] = [:]
|
|
952
|
+
) {
|
|
953
|
+
self.streamUrl = streamUrl
|
|
954
|
+
self.streamId = streamId
|
|
955
|
+
self.keepAlive = keepAlive
|
|
956
|
+
self.keepAliveIntervalSeconds = keepAliveIntervalSeconds
|
|
957
|
+
self.flash = flash
|
|
958
|
+
self.sound = sound
|
|
959
|
+
self.video = video
|
|
960
|
+
self.audio = audio
|
|
961
|
+
self.extraValues = extraValues
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
init(values: [String: Any]) {
|
|
965
|
+
self.init(
|
|
966
|
+
streamUrl: values["streamUrl"] as? String
|
|
967
|
+
?? values["rtmpUrl"] as? String
|
|
968
|
+
?? values["srtUrl"] as? String
|
|
969
|
+
?? values["whipUrl"] as? String
|
|
970
|
+
?? "",
|
|
971
|
+
streamId: values["streamId"] as? String ?? "",
|
|
972
|
+
keepAlive: values["keepAlive"] as? Bool ?? true,
|
|
973
|
+
keepAliveIntervalSeconds: intValue(values["keepAliveIntervalSeconds"]) ?? 15,
|
|
974
|
+
flash: values["flash"] as? Bool ?? true,
|
|
975
|
+
sound: values["sound"] as? Bool ?? true,
|
|
976
|
+
video: StreamVideoConfig(values: (values["video"] ?? values["v"]) as? [String: Any]),
|
|
977
|
+
audio: StreamAudioConfig(values: (values["audio"] ?? values["a"]) as? [String: Any]),
|
|
978
|
+
extraValues: values
|
|
979
|
+
)
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
public var values: [String: Any] {
|
|
983
|
+
var values = extraValues
|
|
984
|
+
values["type"] = "start_stream"
|
|
985
|
+
values["streamUrl"] = streamUrl
|
|
986
|
+
values["streamId"] = streamId
|
|
987
|
+
values["keepAlive"] = keepAlive
|
|
988
|
+
values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
|
|
989
|
+
values["flash"] = flash
|
|
990
|
+
values["sound"] = sound
|
|
991
|
+
if let videoValues = video?.dictionary, !videoValues.isEmpty {
|
|
992
|
+
values["video"] = videoValues
|
|
993
|
+
}
|
|
994
|
+
if let audioValues = audio?.dictionary, !audioValues.isEmpty {
|
|
995
|
+
values["audio"] = audioValues
|
|
996
|
+
}
|
|
997
|
+
return values
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
public struct StreamKeepAliveRequest {
|
|
1002
|
+
public let streamId: String
|
|
1003
|
+
public let ackId: String
|
|
1004
|
+
public let extraValues: [String: Any]
|
|
1005
|
+
|
|
1006
|
+
public init(streamId: String, ackId: String, extraValues: [String: Any] = [:]) {
|
|
1007
|
+
self.streamId = streamId
|
|
1008
|
+
self.ackId = ackId
|
|
1009
|
+
self.extraValues = extraValues
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
init(values: [String: Any]) {
|
|
1013
|
+
self.init(
|
|
1014
|
+
streamId: values["streamId"] as? String ?? "",
|
|
1015
|
+
ackId: values["ackId"] as? String ?? "",
|
|
1016
|
+
extraValues: values
|
|
1017
|
+
)
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
public var values: [String: Any] {
|
|
1021
|
+
var values = extraValues
|
|
1022
|
+
values["type"] = "keep_stream_alive"
|
|
1023
|
+
values["streamId"] = streamId
|
|
1024
|
+
values["ackId"] = ackId
|
|
1025
|
+
return values
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
public enum RgbLedAction: String {
|
|
1030
|
+
case on
|
|
1031
|
+
case off
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
public enum RgbLedColor: String {
|
|
1035
|
+
case red
|
|
1036
|
+
case green
|
|
1037
|
+
case blue
|
|
1038
|
+
case orange
|
|
1039
|
+
case white
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
public struct RgbLedRequest {
|
|
1043
|
+
public let requestId: String
|
|
1044
|
+
public let packageName: String?
|
|
1045
|
+
public let action: RgbLedAction
|
|
1046
|
+
public let color: RgbLedColor?
|
|
1047
|
+
public let ontime: Int
|
|
1048
|
+
public let offtime: Int
|
|
1049
|
+
public let count: Int
|
|
1050
|
+
|
|
1051
|
+
public init(
|
|
1052
|
+
requestId: String,
|
|
1053
|
+
packageName: String?,
|
|
1054
|
+
action: RgbLedAction,
|
|
1055
|
+
color: RgbLedColor?,
|
|
1056
|
+
ontime: Int,
|
|
1057
|
+
offtime: Int,
|
|
1058
|
+
count: Int
|
|
1059
|
+
) {
|
|
1060
|
+
self.requestId = requestId
|
|
1061
|
+
self.packageName = packageName
|
|
1062
|
+
self.action = action
|
|
1063
|
+
self.color = color
|
|
1064
|
+
self.ontime = ontime
|
|
1065
|
+
self.offtime = offtime
|
|
1066
|
+
self.count = count
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
public struct VideoRecordingRequest {
|
|
1071
|
+
public let requestId: String
|
|
1072
|
+
public let save: Bool
|
|
1073
|
+
public let flash: Bool
|
|
1074
|
+
public let sound: Bool
|
|
1075
|
+
|
|
1076
|
+
public init(requestId: String, save: Bool, flash: Bool, sound: Bool) {
|
|
1077
|
+
self.requestId = requestId
|
|
1078
|
+
self.save = save
|
|
1079
|
+
self.flash = flash
|
|
1080
|
+
self.sound = sound
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
public struct ButtonPressEvent: CustomStringConvertible {
|
|
1085
|
+
public let buttonId: String
|
|
1086
|
+
public let pressType: String
|
|
1087
|
+
public let timestamp: Int?
|
|
1088
|
+
|
|
1089
|
+
public init(buttonId: String, pressType: String, timestamp: Int? = nil) {
|
|
1090
|
+
self.buttonId = buttonId
|
|
1091
|
+
self.pressType = pressType
|
|
1092
|
+
self.timestamp = timestamp
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
public var description: String {
|
|
1096
|
+
"ButtonPressEvent(buttonId: \(buttonId), pressType: \(pressType))"
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
public struct TouchEvent: CustomStringConvertible {
|
|
1101
|
+
public let values: [String: Any]
|
|
1102
|
+
|
|
1103
|
+
public init(values: [String: Any]) {
|
|
1104
|
+
self.values = values
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
public var deviceModel: String? {
|
|
1108
|
+
stringValue(values, "device_model", "deviceModel")
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
public var gestureName: String? {
|
|
1112
|
+
stringValue(values, "gesture_name", "gestureName")
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
public var timestamp: Int? {
|
|
1116
|
+
intValue(values["timestamp"])
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
public var isSwipe: Bool {
|
|
1120
|
+
gestureName?.localizedCaseInsensitiveContains("swipe") == true
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
public var description: String {
|
|
1124
|
+
"TouchEvent(gestureName: \(gestureName ?? "unknown"))"
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
public enum WifiStatus: CustomStringConvertible, Equatable {
|
|
1129
|
+
public enum State: String {
|
|
1130
|
+
case disconnected
|
|
1131
|
+
case connected
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
case disconnected
|
|
1135
|
+
case connected(ssid: String, localIp: String?)
|
|
1136
|
+
|
|
1137
|
+
private init?(connected: Bool, ssid: String?, localIp: String?) {
|
|
1138
|
+
if connected {
|
|
1139
|
+
guard
|
|
1140
|
+
let ssid = ssid?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
1141
|
+
!ssid.isEmpty
|
|
1142
|
+
else {
|
|
1143
|
+
return nil
|
|
1144
|
+
}
|
|
1145
|
+
let trimmedLocalIp = localIp?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1146
|
+
self = .connected(
|
|
1147
|
+
ssid: ssid,
|
|
1148
|
+
localIp: trimmedLocalIp?.isEmpty == false ? trimmedLocalIp : nil
|
|
1149
|
+
)
|
|
1150
|
+
} else {
|
|
1151
|
+
self = .disconnected
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
init?(values: [String: Any]) {
|
|
1156
|
+
if let nested = values["wifi"] as? [String: Any] {
|
|
1157
|
+
guard let wifi = WifiStatus(values: nested) else {
|
|
1158
|
+
return nil
|
|
1159
|
+
}
|
|
1160
|
+
self = wifi
|
|
1161
|
+
return
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
if let state = stringValue(values, "state")?.lowercased() {
|
|
1165
|
+
switch state {
|
|
1166
|
+
case State.connected.rawValue:
|
|
1167
|
+
guard let wifi = WifiStatus(
|
|
1168
|
+
connected: true,
|
|
1169
|
+
ssid: nonEmptyStringValue(values, "ssid"),
|
|
1170
|
+
localIp: nonEmptyStringValue(values, "localIp")
|
|
1171
|
+
) else {
|
|
1172
|
+
return nil
|
|
1173
|
+
}
|
|
1174
|
+
self = wifi
|
|
1175
|
+
case State.disconnected.rawValue:
|
|
1176
|
+
self = .disconnected
|
|
1177
|
+
default:
|
|
1178
|
+
return nil
|
|
1179
|
+
}
|
|
1180
|
+
return
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
return nil
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
static func fromStoreValues(_ values: [String: Any]) -> WifiStatus? {
|
|
1187
|
+
guard let connected = boolValue(values, "wifiConnected") else { return nil }
|
|
1188
|
+
return fromStoreFields(
|
|
1189
|
+
connected: connected,
|
|
1190
|
+
ssid: nonEmptyStringValue(values, "wifiSsid"),
|
|
1191
|
+
localIp: nonEmptyStringValue(values, "wifiLocalIp")
|
|
1192
|
+
)
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
static func fromStoreFields(connected: Bool, ssid: String?, localIp: String?) -> WifiStatus? {
|
|
1196
|
+
WifiStatus(connected: connected, ssid: ssid, localIp: localIp)
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
public var state: State {
|
|
1200
|
+
switch self {
|
|
1201
|
+
case .disconnected:
|
|
1202
|
+
.disconnected
|
|
1203
|
+
case .connected:
|
|
1204
|
+
.connected
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
public var isConnected: Bool {
|
|
1209
|
+
if case .connected = self {
|
|
1210
|
+
return true
|
|
1211
|
+
}
|
|
1212
|
+
return false
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
public var values: [String: Any] {
|
|
1216
|
+
switch self {
|
|
1217
|
+
case .disconnected:
|
|
1218
|
+
return ["state": State.disconnected.rawValue]
|
|
1219
|
+
case let .connected(ssid, localIp):
|
|
1220
|
+
var values: [String: Any] = [
|
|
1221
|
+
"state": State.connected.rawValue,
|
|
1222
|
+
"ssid": ssid,
|
|
1223
|
+
]
|
|
1224
|
+
if let localIp = localIp {
|
|
1225
|
+
values["localIp"] = localIp
|
|
1226
|
+
}
|
|
1227
|
+
return values
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
var storeValues: [String: Any] {
|
|
1232
|
+
switch self {
|
|
1233
|
+
case .disconnected:
|
|
1234
|
+
[
|
|
1235
|
+
"wifiConnected": false,
|
|
1236
|
+
"wifiSsid": "",
|
|
1237
|
+
"wifiLocalIp": "",
|
|
1238
|
+
]
|
|
1239
|
+
case let .connected(ssid, localIp):
|
|
1240
|
+
[
|
|
1241
|
+
"wifiConnected": true,
|
|
1242
|
+
"wifiSsid": ssid,
|
|
1243
|
+
"wifiLocalIp": localIp ?? "",
|
|
1244
|
+
]
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
public var description: String {
|
|
1249
|
+
switch self {
|
|
1250
|
+
case .disconnected:
|
|
1251
|
+
"WifiStatus(disconnected)"
|
|
1252
|
+
case let .connected(ssid, localIp):
|
|
1253
|
+
"WifiStatus(connected: \(ssid), localIp: \(localIp ?? "unknown"))"
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
public struct WifiStatusEvent: CustomStringConvertible {
|
|
1259
|
+
public let status: WifiStatus
|
|
1260
|
+
|
|
1261
|
+
public init(status: WifiStatus) {
|
|
1262
|
+
self.status = status
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
init(connected: Bool, ssid: String?, localIp: String?) {
|
|
1266
|
+
self.status = WifiStatus.fromStoreFields(connected: connected, ssid: ssid, localIp: localIp) ?? .disconnected
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
init(values: [String: Any]) {
|
|
1270
|
+
self.status = WifiStatus(values: values) ?? .disconnected
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
public var values: [String: Any] {
|
|
1274
|
+
status.values.merging(["type": "wifi_status_change"]) { _, new in new }
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
public var description: String {
|
|
1278
|
+
"WifiStatusEvent(\(status))"
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
public enum HotspotStatus: CustomStringConvertible, Equatable {
|
|
1283
|
+
public enum State: String {
|
|
1284
|
+
case disabled
|
|
1285
|
+
case enabled
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
case disabled
|
|
1289
|
+
case enabled(ssid: String, password: String, localIp: String)
|
|
1290
|
+
|
|
1291
|
+
public init?(enabled: Bool, ssid: String?, password: String?, localIp: String?) {
|
|
1292
|
+
if enabled {
|
|
1293
|
+
guard
|
|
1294
|
+
let ssid = ssid?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
1295
|
+
!ssid.isEmpty,
|
|
1296
|
+
let password = password?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
1297
|
+
!password.isEmpty,
|
|
1298
|
+
let localIp = localIp?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
1299
|
+
!localIp.isEmpty
|
|
1300
|
+
else {
|
|
1301
|
+
return nil
|
|
1302
|
+
}
|
|
1303
|
+
self = .enabled(ssid: ssid, password: password, localIp: localIp)
|
|
1304
|
+
} else {
|
|
1305
|
+
self = .disabled
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
public init?(values: [String: Any]) {
|
|
1310
|
+
if let nested = values["hotspot"] as? [String: Any] {
|
|
1311
|
+
self.init(values: nested)
|
|
1312
|
+
return
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
guard let state = stringValue(values, "state")?.lowercased() else {
|
|
1316
|
+
return nil
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
switch state {
|
|
1320
|
+
case State.enabled.rawValue:
|
|
1321
|
+
self.init(
|
|
1322
|
+
enabled: true,
|
|
1323
|
+
ssid: nonEmptyStringValue(values, "ssid"),
|
|
1324
|
+
password: nonEmptyStringValue(values, "password"),
|
|
1325
|
+
localIp: nonEmptyStringValue(values, "localIp")
|
|
1326
|
+
)
|
|
1327
|
+
case State.disabled.rawValue:
|
|
1328
|
+
self = .disabled
|
|
1329
|
+
default:
|
|
1330
|
+
return nil
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
static func fromStoreValues(_ values: [String: Any]) -> HotspotStatus? {
|
|
1335
|
+
guard let enabled = boolValue(values, "hotspotEnabled") else {
|
|
1336
|
+
return nil
|
|
1337
|
+
}
|
|
1338
|
+
return fromStoreFields(
|
|
1339
|
+
enabled: enabled,
|
|
1340
|
+
ssid: nonEmptyStringValue(values, "hotspotSsid"),
|
|
1341
|
+
password: nonEmptyStringValue(values, "hotspotPassword"),
|
|
1342
|
+
localIp: nonEmptyStringValue(values, "hotspotGatewayIp", "hotspotLocalIp")
|
|
1343
|
+
)
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
static func fromStoreFields(enabled: Bool, ssid: String?, password: String?, localIp: String?) -> HotspotStatus? {
|
|
1347
|
+
HotspotStatus(enabled: enabled, ssid: ssid, password: password, localIp: localIp)
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
var storeValues: [String: Any] {
|
|
1351
|
+
switch self {
|
|
1352
|
+
case .disabled:
|
|
1353
|
+
[
|
|
1354
|
+
"hotspotEnabled": false,
|
|
1355
|
+
"hotspotSsid": "",
|
|
1356
|
+
"hotspotPassword": "",
|
|
1357
|
+
"hotspotGatewayIp": "",
|
|
1358
|
+
]
|
|
1359
|
+
case let .enabled(ssid, password, localIp):
|
|
1360
|
+
[
|
|
1361
|
+
"hotspotEnabled": true,
|
|
1362
|
+
"hotspotSsid": ssid,
|
|
1363
|
+
"hotspotPassword": password,
|
|
1364
|
+
"hotspotGatewayIp": localIp,
|
|
1365
|
+
]
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
public var values: [String: Any] {
|
|
1370
|
+
switch self {
|
|
1371
|
+
case .disabled:
|
|
1372
|
+
["state": State.disabled.rawValue]
|
|
1373
|
+
case let .enabled(ssid, password, localIp):
|
|
1374
|
+
[
|
|
1375
|
+
"state": State.enabled.rawValue,
|
|
1376
|
+
"ssid": ssid,
|
|
1377
|
+
"password": password,
|
|
1378
|
+
"localIp": localIp,
|
|
1379
|
+
]
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
public var state: State {
|
|
1384
|
+
switch self {
|
|
1385
|
+
case .disabled:
|
|
1386
|
+
.disabled
|
|
1387
|
+
case .enabled:
|
|
1388
|
+
.enabled
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
public var isEnabled: Bool {
|
|
1393
|
+
if case .enabled = self {
|
|
1394
|
+
return true
|
|
1395
|
+
}
|
|
1396
|
+
return false
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
public var description: String {
|
|
1400
|
+
switch self {
|
|
1401
|
+
case .disabled:
|
|
1402
|
+
"HotspotStatus(disabled)"
|
|
1403
|
+
case let .enabled(ssid, _, localIp):
|
|
1404
|
+
"HotspotStatus(enabled: \(ssid), localIp: \(localIp))"
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
public struct HotspotStatusEvent: CustomStringConvertible {
|
|
1410
|
+
public let status: HotspotStatus
|
|
1411
|
+
|
|
1412
|
+
public init(status: HotspotStatus) {
|
|
1413
|
+
self.status = status
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
init(enabled: Bool, ssid: String?, password: String?, localIp: String?) {
|
|
1417
|
+
self.status = HotspotStatus.fromStoreFields(enabled: enabled, ssid: ssid, password: password, localIp: localIp) ?? .disabled
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
init(values: [String: Any]) {
|
|
1421
|
+
self.status = HotspotStatus(values: values) ?? .disabled
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
public var values: [String: Any] {
|
|
1425
|
+
status.values.merging(["type": "hotspot_status_change"]) { _, new in new }
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
public var description: String {
|
|
1429
|
+
"HotspotStatusEvent(\(status))"
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
public struct HotspotErrorEvent: CustomStringConvertible {
|
|
1434
|
+
public let values: [String: Any]
|
|
1435
|
+
|
|
1436
|
+
public init(values: [String: Any]) {
|
|
1437
|
+
self.values = values
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
public var message: String? {
|
|
1441
|
+
stringValue(values, "error_message", "message", "error")
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
public var timestamp: Int? {
|
|
1445
|
+
intValue(values["timestamp"])
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
public var description: String {
|
|
1449
|
+
"HotspotErrorEvent(message: \(message ?? "unknown"))"
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
public enum PhotoResponse: CustomStringConvertible, Equatable {
|
|
1454
|
+
public enum State: String {
|
|
1455
|
+
case success
|
|
1456
|
+
case error
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
case success(requestId: String, photoUrl: String, timestamp: Int)
|
|
1460
|
+
case error(requestId: String, errorCode: String?, errorMessage: String, timestamp: Int)
|
|
1461
|
+
|
|
1462
|
+
public init(values: [String: Any]) {
|
|
1463
|
+
let requestId = stringValue(values, "requestId", "request_id") ?? ""
|
|
1464
|
+
let timestamp = intValue(values["timestamp"]) ?? Int(Date().timeIntervalSince1970 * 1000)
|
|
1465
|
+
let state = stringValue(values, "state", "status")?.lowercased()
|
|
1466
|
+
if state == State.success.rawValue || boolValue(values, "success") == true {
|
|
1467
|
+
self = .success(
|
|
1468
|
+
requestId: requestId,
|
|
1469
|
+
photoUrl: stringValue(values, "photoUrl", "photo_url") ?? "",
|
|
1470
|
+
timestamp: timestamp
|
|
1471
|
+
)
|
|
1472
|
+
} else {
|
|
1473
|
+
self = .error(
|
|
1474
|
+
requestId: requestId,
|
|
1475
|
+
errorCode: stringValue(values, "errorCode", "error_code"),
|
|
1476
|
+
errorMessage: stringValue(values, "errorMessage", "error_message", "error") ?? "Unknown photo error",
|
|
1477
|
+
timestamp: timestamp
|
|
1478
|
+
)
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
public var state: State {
|
|
1483
|
+
switch self {
|
|
1484
|
+
case .success:
|
|
1485
|
+
.success
|
|
1486
|
+
case .error:
|
|
1487
|
+
.error
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
public var requestId: String {
|
|
1492
|
+
switch self {
|
|
1493
|
+
case let .success(requestId, _, _), let .error(requestId, _, _, _):
|
|
1494
|
+
requestId
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
public var timestamp: Int {
|
|
1499
|
+
switch self {
|
|
1500
|
+
case let .success(_, _, timestamp), let .error(_, _, _, timestamp):
|
|
1501
|
+
timestamp
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
public var values: [String: Any] {
|
|
1506
|
+
switch self {
|
|
1507
|
+
case let .success(requestId, photoUrl, timestamp):
|
|
1508
|
+
return [
|
|
1509
|
+
"state": State.success.rawValue,
|
|
1510
|
+
"requestId": requestId,
|
|
1511
|
+
"photoUrl": photoUrl,
|
|
1512
|
+
"timestamp": timestamp,
|
|
1513
|
+
]
|
|
1514
|
+
case let .error(requestId, errorCode, errorMessage, timestamp):
|
|
1515
|
+
var values: [String: Any] = [
|
|
1516
|
+
"state": State.error.rawValue,
|
|
1517
|
+
"requestId": requestId,
|
|
1518
|
+
"errorMessage": errorMessage,
|
|
1519
|
+
"timestamp": timestamp,
|
|
1520
|
+
]
|
|
1521
|
+
if let errorCode, !errorCode.isEmpty {
|
|
1522
|
+
values["errorCode"] = errorCode
|
|
1523
|
+
}
|
|
1524
|
+
return values
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
public var description: String {
|
|
1529
|
+
"PhotoResponse(requestId: \(requestId), state: \(state.rawValue))"
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
public struct PhotoResponseEvent: CustomStringConvertible {
|
|
1534
|
+
public let response: PhotoResponse
|
|
1535
|
+
|
|
1536
|
+
public init(response: PhotoResponse) {
|
|
1537
|
+
self.response = response
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
public init(values: [String: Any]) {
|
|
1541
|
+
self.response = PhotoResponse(values: values)
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
public var requestId: String {
|
|
1545
|
+
response.requestId
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
public var values: [String: Any] {
|
|
1549
|
+
var values = response.values
|
|
1550
|
+
values["type"] = "photo_response"
|
|
1551
|
+
return values
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
public var description: String {
|
|
1555
|
+
"PhotoResponseEvent(requestId: \(requestId), state: \(response.state.rawValue))"
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
public enum StreamState: String, Equatable {
|
|
1560
|
+
case initializing
|
|
1561
|
+
case streaming
|
|
1562
|
+
case stopping
|
|
1563
|
+
case stopped
|
|
1564
|
+
case reconnecting
|
|
1565
|
+
case reconnected
|
|
1566
|
+
case reconnectFailed = "reconnect_failed"
|
|
1567
|
+
case error
|
|
1568
|
+
|
|
1569
|
+
fileprivate static func from(_ value: String?) -> StreamState? {
|
|
1570
|
+
switch value?.lowercased() {
|
|
1571
|
+
case "initializing", "starting", "connecting":
|
|
1572
|
+
return .initializing
|
|
1573
|
+
case "streaming", "streaming_started", "active":
|
|
1574
|
+
return .streaming
|
|
1575
|
+
case "stopping":
|
|
1576
|
+
return .stopping
|
|
1577
|
+
case "stopped", "not_streaming", "disconnected", "timeout":
|
|
1578
|
+
return .stopped
|
|
1579
|
+
case "reconnecting":
|
|
1580
|
+
return .reconnecting
|
|
1581
|
+
case "reconnected":
|
|
1582
|
+
return .reconnected
|
|
1583
|
+
case "reconnect_failed":
|
|
1584
|
+
return .reconnectFailed
|
|
1585
|
+
case "error", "error_not_streaming":
|
|
1586
|
+
return .error
|
|
1587
|
+
default:
|
|
1588
|
+
return nil
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
public enum StreamStatusKind: String, Equatable {
|
|
1594
|
+
case lifecycle
|
|
1595
|
+
case reconnect
|
|
1596
|
+
case error
|
|
1597
|
+
case snapshot
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
public enum StreamStatus: CustomStringConvertible, Equatable {
|
|
1601
|
+
case lifecycle(state: StreamState, streamId: String?, timestamp: Int?)
|
|
1602
|
+
case reconnecting(streamId: String?, attempt: Int, maxAttempts: Int, reason: String, timestamp: Int?)
|
|
1603
|
+
case reconnected(streamId: String?, attempt: Int, timestamp: Int?)
|
|
1604
|
+
case reconnectFailed(streamId: String?, maxAttempts: Int, timestamp: Int?)
|
|
1605
|
+
case error(streamId: String?, errorDetails: String, timestamp: Int?)
|
|
1606
|
+
case snapshot(state: StreamState, streaming: Bool, reconnecting: Bool, streamId: String?, attempt: Int?, timestamp: Int?)
|
|
1607
|
+
|
|
1608
|
+
public init(values: [String: Any]) {
|
|
1609
|
+
let rawState = stringValue(values, "status")
|
|
1610
|
+
let streamId = stringValue(values, "streamId", "stream_id")
|
|
1611
|
+
let timestamp = intValue(values["timestamp"])
|
|
1612
|
+
let attempt = optionalIntValue(values, "attempt")
|
|
1613
|
+
let maxAttempts = optionalIntValue(values, "maxAttempts", "max_attempts") ?? 0
|
|
1614
|
+
|
|
1615
|
+
if hasAnyKey(values, "streaming") || hasAnyKey(values, "reconnecting") {
|
|
1616
|
+
let streaming = boolValue(values, "streaming") == true
|
|
1617
|
+
let reconnecting = boolValue(values, "reconnecting") == true
|
|
1618
|
+
let snapshotState: StreamState = reconnecting ? .reconnecting : (streaming ? .streaming : .stopped)
|
|
1619
|
+
self = .snapshot(
|
|
1620
|
+
state: snapshotState,
|
|
1621
|
+
streaming: streaming,
|
|
1622
|
+
reconnecting: reconnecting,
|
|
1623
|
+
streamId: streamId,
|
|
1624
|
+
attempt: attempt,
|
|
1625
|
+
timestamp: timestamp
|
|
1626
|
+
)
|
|
1627
|
+
return
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
guard let state = StreamState.from(rawState) else {
|
|
1631
|
+
self = .error(
|
|
1632
|
+
streamId: streamId,
|
|
1633
|
+
errorDetails: rawState.map { "Unknown stream status: \($0)" } ?? "Missing stream status",
|
|
1634
|
+
timestamp: timestamp
|
|
1635
|
+
)
|
|
1636
|
+
return
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
switch state {
|
|
1640
|
+
case .reconnecting:
|
|
1641
|
+
self = .reconnecting(
|
|
1642
|
+
streamId: streamId,
|
|
1643
|
+
attempt: attempt ?? 0,
|
|
1644
|
+
maxAttempts: maxAttempts,
|
|
1645
|
+
reason: stringValue(values, "reason") ?? "",
|
|
1646
|
+
timestamp: timestamp
|
|
1647
|
+
)
|
|
1648
|
+
case .reconnected:
|
|
1649
|
+
self = .reconnected(streamId: streamId, attempt: attempt ?? 0, timestamp: timestamp)
|
|
1650
|
+
case .reconnectFailed:
|
|
1651
|
+
self = .reconnectFailed(streamId: streamId, maxAttempts: maxAttempts, timestamp: timestamp)
|
|
1652
|
+
case .error:
|
|
1653
|
+
self = .error(
|
|
1654
|
+
streamId: streamId,
|
|
1655
|
+
errorDetails: stringValue(values, "errorDetails", "error_details", "details", "error", "errorMessage")
|
|
1656
|
+
?? (rawState == "error_not_streaming" ? "not_streaming" : "Unknown stream error"),
|
|
1657
|
+
timestamp: timestamp
|
|
1658
|
+
)
|
|
1659
|
+
default:
|
|
1660
|
+
self = .lifecycle(state: state, streamId: streamId, timestamp: timestamp)
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
public var kind: StreamStatusKind {
|
|
1665
|
+
switch self {
|
|
1666
|
+
case .lifecycle:
|
|
1667
|
+
.lifecycle
|
|
1668
|
+
case .reconnecting, .reconnected, .reconnectFailed:
|
|
1669
|
+
.reconnect
|
|
1670
|
+
case .error:
|
|
1671
|
+
.error
|
|
1672
|
+
case .snapshot:
|
|
1673
|
+
.snapshot
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
public var state: StreamState {
|
|
1678
|
+
switch self {
|
|
1679
|
+
case let .lifecycle(state, _, _):
|
|
1680
|
+
state
|
|
1681
|
+
case .reconnecting:
|
|
1682
|
+
.reconnecting
|
|
1683
|
+
case .reconnected:
|
|
1684
|
+
.reconnected
|
|
1685
|
+
case .reconnectFailed:
|
|
1686
|
+
.reconnectFailed
|
|
1687
|
+
case .error:
|
|
1688
|
+
.error
|
|
1689
|
+
case let .snapshot(state, _, _, _, _, _):
|
|
1690
|
+
state
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
public var streamId: String? {
|
|
1695
|
+
switch self {
|
|
1696
|
+
case let .lifecycle(_, streamId, _),
|
|
1697
|
+
let .reconnecting(streamId, _, _, _, _),
|
|
1698
|
+
let .reconnected(streamId, _, _),
|
|
1699
|
+
let .reconnectFailed(streamId, _, _),
|
|
1700
|
+
let .error(streamId, _, _),
|
|
1701
|
+
let .snapshot(_, _, _, streamId, _, _):
|
|
1702
|
+
streamId
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
public var timestamp: Int? {
|
|
1707
|
+
switch self {
|
|
1708
|
+
case let .lifecycle(_, _, timestamp),
|
|
1709
|
+
let .reconnecting(_, _, _, _, timestamp),
|
|
1710
|
+
let .reconnected(_, _, timestamp),
|
|
1711
|
+
let .reconnectFailed(_, _, timestamp),
|
|
1712
|
+
let .error(_, _, timestamp),
|
|
1713
|
+
let .snapshot(_, _, _, _, _, timestamp):
|
|
1714
|
+
timestamp
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
public var values: [String: Any] {
|
|
1719
|
+
var values: [String: Any] = [
|
|
1720
|
+
"kind": kind.rawValue,
|
|
1721
|
+
"status": state.rawValue,
|
|
1722
|
+
]
|
|
1723
|
+
if let streamId, !streamId.isEmpty {
|
|
1724
|
+
values["streamId"] = streamId
|
|
1725
|
+
}
|
|
1726
|
+
if let timestamp {
|
|
1727
|
+
values["timestamp"] = timestamp
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
switch self {
|
|
1731
|
+
case .lifecycle:
|
|
1732
|
+
break
|
|
1733
|
+
case let .reconnecting(_, attempt, maxAttempts, reason, _):
|
|
1734
|
+
values["attempt"] = attempt
|
|
1735
|
+
values["maxAttempts"] = maxAttempts
|
|
1736
|
+
values["reason"] = reason
|
|
1737
|
+
case let .reconnected(_, attempt, _):
|
|
1738
|
+
values["attempt"] = attempt
|
|
1739
|
+
case let .reconnectFailed(_, maxAttempts, _):
|
|
1740
|
+
values["maxAttempts"] = maxAttempts
|
|
1741
|
+
case let .error(_, errorDetails, _):
|
|
1742
|
+
values["errorDetails"] = errorDetails
|
|
1743
|
+
case let .snapshot(_, streaming, reconnecting, _, attempt, _):
|
|
1744
|
+
values["streaming"] = streaming
|
|
1745
|
+
values["reconnecting"] = reconnecting
|
|
1746
|
+
if let attempt {
|
|
1747
|
+
values["attempt"] = attempt
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
return values
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
public var description: String {
|
|
1755
|
+
"StreamStatus(kind: \(kind.rawValue), status: \(state.rawValue), streamId: \(streamId ?? "none"))"
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
public struct StreamStatusEvent: CustomStringConvertible {
|
|
1760
|
+
public let status: StreamStatus
|
|
1761
|
+
|
|
1762
|
+
public init(status: StreamStatus) {
|
|
1763
|
+
self.status = status
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
public init(values: [String: Any]) {
|
|
1767
|
+
self.status = StreamStatus(values: values)
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
public var state: StreamState {
|
|
1771
|
+
status.state
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
public var streamId: String? {
|
|
1775
|
+
status.streamId
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
public var values: [String: Any] {
|
|
1779
|
+
var values = status.values
|
|
1780
|
+
values["type"] = "stream_status"
|
|
1781
|
+
return values
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
public var description: String {
|
|
1785
|
+
"StreamStatusEvent(kind: \(status.kind.rawValue), status: \(state.rawValue), streamId: \(streamId ?? "none"))"
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
public struct KeepAliveAckEvent: CustomStringConvertible, Equatable {
|
|
1790
|
+
public let streamId: String
|
|
1791
|
+
public let ackId: String
|
|
1792
|
+
public let timestamp: Int?
|
|
1793
|
+
|
|
1794
|
+
public init(streamId: String, ackId: String, timestamp: Int? = nil) {
|
|
1795
|
+
self.streamId = streamId
|
|
1796
|
+
self.ackId = ackId
|
|
1797
|
+
self.timestamp = timestamp
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
public init(values: [String: Any]) {
|
|
1801
|
+
self.streamId = stringValue(values, "streamId", "stream_id") ?? ""
|
|
1802
|
+
self.ackId = stringValue(values, "ackId", "ack_id") ?? ""
|
|
1803
|
+
self.timestamp = intValue(values["timestamp"])
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
public var values: [String: Any] {
|
|
1807
|
+
var values: [String: Any] = [
|
|
1808
|
+
"type": "keep_alive_ack",
|
|
1809
|
+
"streamId": streamId,
|
|
1810
|
+
"ackId": ackId,
|
|
1811
|
+
]
|
|
1812
|
+
if let timestamp {
|
|
1813
|
+
values["timestamp"] = timestamp
|
|
1814
|
+
}
|
|
1815
|
+
return values
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
public var description: String {
|
|
1819
|
+
"KeepAliveAckEvent(streamId: \(streamId), ackId: \(ackId))"
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
public struct BluetoothError: Error, LocalizedError, CustomStringConvertible {
|
|
1824
|
+
public let code: String
|
|
1825
|
+
public let message: String
|
|
1826
|
+
|
|
1827
|
+
public init(code: String, message: String) {
|
|
1828
|
+
self.code = code
|
|
1829
|
+
self.message = message
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
public var errorDescription: String? {
|
|
1833
|
+
message
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
public var description: String {
|
|
1837
|
+
"\(code): \(message)"
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
private final class BluetoothAvailability: NSObject, CBCentralManagerDelegate {
|
|
1842
|
+
static let shared = BluetoothAvailability()
|
|
1843
|
+
|
|
1844
|
+
private var centralManager: CBCentralManager?
|
|
1845
|
+
private var state: CBManagerState = .unknown
|
|
1846
|
+
|
|
1847
|
+
override private init() {
|
|
1848
|
+
super.init()
|
|
1849
|
+
centralManager = CBCentralManager(
|
|
1850
|
+
delegate: self,
|
|
1851
|
+
queue: .main,
|
|
1852
|
+
options: [CBCentralManagerOptionShowPowerAlertKey: false]
|
|
1853
|
+
)
|
|
1854
|
+
state = centralManager?.state ?? .unknown
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
1858
|
+
state = central.state
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
func requirePoweredOn(operation: String) throws {
|
|
1862
|
+
if let current = centralManager?.state {
|
|
1863
|
+
state = current
|
|
1864
|
+
}
|
|
1865
|
+
switch state {
|
|
1866
|
+
case .poweredOn:
|
|
1867
|
+
return
|
|
1868
|
+
case .poweredOff:
|
|
1869
|
+
throw BluetoothError(
|
|
1870
|
+
code: "bluetooth_powered_off",
|
|
1871
|
+
message: "Turn on phone Bluetooth to \(operation)."
|
|
1872
|
+
)
|
|
1873
|
+
case .unauthorized:
|
|
1874
|
+
throw BluetoothError(
|
|
1875
|
+
code: "bluetooth_unauthorized",
|
|
1876
|
+
message: "Allow Bluetooth access to \(operation)."
|
|
1877
|
+
)
|
|
1878
|
+
case .unsupported:
|
|
1879
|
+
throw BluetoothError(
|
|
1880
|
+
code: "bluetooth_unsupported",
|
|
1881
|
+
message: "This phone does not support Bluetooth."
|
|
1882
|
+
)
|
|
1883
|
+
case .resetting, .unknown:
|
|
1884
|
+
throw BluetoothError(
|
|
1885
|
+
code: "bluetooth_not_ready",
|
|
1886
|
+
message: "Bluetooth is not ready yet. Try again."
|
|
1887
|
+
)
|
|
1888
|
+
@unknown default:
|
|
1889
|
+
throw BluetoothError(
|
|
1890
|
+
code: "bluetooth_unavailable",
|
|
1891
|
+
message: "Bluetooth is unavailable. Try again."
|
|
1892
|
+
)
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
public enum ScanStopReason {
|
|
1898
|
+
case completed
|
|
1899
|
+
case cancelled
|
|
1900
|
+
case error
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
public struct LocalTranscriptionEvent: CustomStringConvertible {
|
|
1904
|
+
public let text: String
|
|
1905
|
+
public let isFinal: Bool
|
|
1906
|
+
public let values: [String: Any]
|
|
1907
|
+
|
|
1908
|
+
public init(text: String, isFinal: Bool, values: [String: Any]) {
|
|
1909
|
+
self.text = text
|
|
1910
|
+
self.isFinal = isFinal
|
|
1911
|
+
self.values = values
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
public var description: String {
|
|
1915
|
+
"LocalTranscriptionEvent(text: \(text), isFinal: \(isFinal))"
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
public enum BluetoothEvent: CustomStringConvertible {
|
|
1920
|
+
case buttonPress(ButtonPressEvent)
|
|
1921
|
+
case touch(TouchEvent)
|
|
1922
|
+
case wifiStatus(WifiStatusEvent)
|
|
1923
|
+
case hotspotStatus(HotspotStatusEvent)
|
|
1924
|
+
case hotspotError(HotspotErrorEvent)
|
|
1925
|
+
case photoResponse(PhotoResponseEvent)
|
|
1926
|
+
case streamStatus(StreamStatusEvent)
|
|
1927
|
+
case keepAliveAck(KeepAliveAckEvent)
|
|
1928
|
+
case localTranscription(LocalTranscriptionEvent)
|
|
1929
|
+
case raw(name: String, values: [String: Any])
|
|
1930
|
+
|
|
1931
|
+
public var description: String {
|
|
1932
|
+
switch self {
|
|
1933
|
+
case let .buttonPress(event):
|
|
1934
|
+
event.description
|
|
1935
|
+
case let .touch(event):
|
|
1936
|
+
event.description
|
|
1937
|
+
case let .wifiStatus(event):
|
|
1938
|
+
event.description
|
|
1939
|
+
case let .hotspotStatus(event):
|
|
1940
|
+
event.description
|
|
1941
|
+
case let .hotspotError(event):
|
|
1942
|
+
event.description
|
|
1943
|
+
case let .photoResponse(event):
|
|
1944
|
+
event.description
|
|
1945
|
+
case let .streamStatus(event):
|
|
1946
|
+
event.description
|
|
1947
|
+
case let .keepAliveAck(event):
|
|
1948
|
+
event.description
|
|
1949
|
+
case let .localTranscription(event):
|
|
1950
|
+
event.description
|
|
1951
|
+
case let .raw(name, values):
|
|
1952
|
+
"\(name): \(values)"
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
@MainActor
|
|
1958
|
+
public protocol MentraBluetoothSDKDelegate: AnyObject {
|
|
1959
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateGlassesStatus status: GlassesStatusUpdate)
|
|
1960
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didUpdateBluetoothStatus status: BluetoothStatusUpdate)
|
|
1961
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didDiscover device: Device)
|
|
1962
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didStopScan reason: ScanStopReason)
|
|
1963
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceive event: BluetoothEvent)
|
|
1964
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceiveMicPcm frame: Data)
|
|
1965
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didReceiveMicLc3 frame: Data)
|
|
1966
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didChangeDefaultDevice device: Device?)
|
|
1967
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didLog message: String)
|
|
1968
|
+
func mentraBluetoothSDK(_ sdk: MentraBluetoothSDK, didFail error: BluetoothError)
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
@MainActor
|
|
1972
|
+
public extension MentraBluetoothSDKDelegate {
|
|
1973
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateGlassesStatus _: GlassesStatusUpdate) {}
|
|
1974
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didUpdateBluetoothStatus _: BluetoothStatusUpdate) {}
|
|
1975
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didDiscover _: Device) {}
|
|
1976
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didStopScan _: ScanStopReason) {}
|
|
1977
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceive _: BluetoothEvent) {}
|
|
1978
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceiveMicPcm _: Data) {}
|
|
1979
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didReceiveMicLc3 _: Data) {}
|
|
1980
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didChangeDefaultDevice _: Device?) {}
|
|
1981
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didLog _: String) {}
|
|
1982
|
+
func mentraBluetoothSDK(_: MentraBluetoothSDK, didFail _: BluetoothError) {}
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
@MainActor
|
|
1986
|
+
public final class MentraBluetoothSDK {
|
|
1987
|
+
public weak var delegate: MentraBluetoothSDKDelegate?
|
|
1988
|
+
|
|
1989
|
+
private let configuration: MentraBluetoothSDKConfiguration
|
|
1990
|
+
private var discoveredDeviceNames = Set<String>()
|
|
1991
|
+
private var bridgeEventSinkId: String?
|
|
1992
|
+
private var storeListenerId: String?
|
|
1993
|
+
private let defaultDeviceKeys: Set<String> = ["default_wearable", "device_name", "device_address"]
|
|
1994
|
+
private var suppressDefaultDeviceEvents = false
|
|
1995
|
+
private var defaultDeviceApplyGeneration = 0
|
|
1996
|
+
|
|
1997
|
+
public init(configuration: MentraBluetoothSDKConfiguration = .default) {
|
|
1998
|
+
self.configuration = configuration
|
|
1999
|
+
_ = BluetoothAvailability.shared
|
|
2000
|
+
bridgeEventSinkId = Bridge.addEventSink { [weak self] eventName, data in
|
|
2001
|
+
Task { @MainActor [weak self] in
|
|
2002
|
+
self?.dispatchBridgeEvent(eventName, data)
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
storeListenerId = DeviceStore.shared.store.addListener { [weak self] category, changes in
|
|
2006
|
+
Task { @MainActor [weak self] in
|
|
2007
|
+
self?.dispatchStoreUpdate(category, changes)
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
public var glassesStatus: GlassesStatus {
|
|
2013
|
+
GlassesStatus(values: DeviceStore.shared.store.getCategory("glasses"))
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
public var bluetoothStatus: BluetoothStatus {
|
|
2017
|
+
BluetoothStatus(values: DeviceStore.shared.store.getCategory(ObservableStore.bluetoothCategory))
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
public var defaultDevice: Device? {
|
|
2021
|
+
currentDefaultDevice()
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
public func getDefaultDevice() -> Device? {
|
|
2025
|
+
currentDefaultDevice()
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
public func setDefaultDevice(_ device: Device?) {
|
|
2029
|
+
guard let device else {
|
|
2030
|
+
clearDefaultDevice()
|
|
2031
|
+
return
|
|
2032
|
+
}
|
|
2033
|
+
defaultDeviceApplyGeneration += 1
|
|
2034
|
+
let generation = defaultDeviceApplyGeneration
|
|
2035
|
+
suppressDefaultDeviceEvents = true
|
|
2036
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "default_wearable", device.model.deviceType)
|
|
2037
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "device_name", device.name)
|
|
2038
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "device_address", device.identifier ?? "")
|
|
2039
|
+
finishDefaultDeviceApply(generation: generation)
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
public func clearDefaultDevice() {
|
|
2043
|
+
defaultDeviceApplyGeneration += 1
|
|
2044
|
+
let generation = defaultDeviceApplyGeneration
|
|
2045
|
+
suppressDefaultDeviceEvents = true
|
|
2046
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "default_wearable", "")
|
|
2047
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "device_name", "")
|
|
2048
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "device_address", "")
|
|
2049
|
+
finishDefaultDeviceApply(generation: generation)
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
public func startScan(model: DeviceModel) throws {
|
|
2053
|
+
if model != .simulated {
|
|
2054
|
+
try BluetoothAvailability.shared.requirePoweredOn(operation: "scan for glasses")
|
|
2055
|
+
}
|
|
2056
|
+
discoveredDeviceNames.removeAll()
|
|
2057
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "searching", true)
|
|
2058
|
+
DeviceManager.shared.findCompatibleDevices(model.deviceType)
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
public func stopScan() {
|
|
2062
|
+
DeviceManager.shared.stopScan()
|
|
2063
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "searching", false)
|
|
2064
|
+
delegate?.mentraBluetoothSDK(self, didStopScan: .cancelled)
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
public func connect(to device: Device, options: ConnectOptions = ConnectOptions()) throws {
|
|
2068
|
+
if device.model != .simulated {
|
|
2069
|
+
try BluetoothAvailability.shared.requirePoweredOn(operation: "connect to glasses")
|
|
2070
|
+
}
|
|
2071
|
+
let isController = ControllerTypes.ALL.contains(device.model.deviceType)
|
|
2072
|
+
if options.cancelExistingConnectionAttempt {
|
|
2073
|
+
if isController {
|
|
2074
|
+
DeviceManager.shared.disconnectController()
|
|
2075
|
+
} else {
|
|
2076
|
+
cancelConnectionAttempt()
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
if options.saveAsDefault && !isController {
|
|
2080
|
+
setDefaultDevice(device)
|
|
2081
|
+
}
|
|
2082
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "pending_wearable", device.model.deviceType)
|
|
2083
|
+
DeviceManager.shared.connectByName(device.name)
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
public func connectDefault(options: ConnectOptions = ConnectOptions()) throws {
|
|
2087
|
+
guard let device = currentDefaultDevice() else {
|
|
2088
|
+
throw BluetoothError(
|
|
2089
|
+
code: "default_device_missing",
|
|
2090
|
+
message: "Set a default glasses device before calling connectDefault."
|
|
2091
|
+
)
|
|
2092
|
+
}
|
|
2093
|
+
if device.model != .simulated {
|
|
2094
|
+
try BluetoothAvailability.shared.requirePoweredOn(operation: "connect to glasses")
|
|
2095
|
+
}
|
|
2096
|
+
if options.cancelExistingConnectionAttempt {
|
|
2097
|
+
cancelConnectionAttempt()
|
|
2098
|
+
}
|
|
2099
|
+
DeviceManager.shared.connectDefault()
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
public func cancelConnectionAttempt() {
|
|
2103
|
+
DeviceManager.shared.disconnect()
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
public func connectSimulated() {
|
|
2107
|
+
DeviceManager.shared.connectSimulated()
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
public func disconnect() {
|
|
2111
|
+
DeviceManager.shared.disconnect()
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
public func forget() {
|
|
2115
|
+
DeviceManager.shared.forget()
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
public func displayText(_ request: DisplayTextRequest) async throws {
|
|
2119
|
+
DeviceManager.shared.displayText(request.dictionary)
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
public func displayEvent(_ request: DisplayEventRequest) async throws {
|
|
2123
|
+
DeviceManager.shared.displayEvent(request.values)
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
public func clearDisplay() async throws {
|
|
2127
|
+
DeviceManager.shared.sgc?.clearDisplay()
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
public func showDashboard() {
|
|
2131
|
+
DeviceManager.shared.showDashboard()
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
public func setBrightness(_ level: Int, autoMode: Bool? = nil) async throws {
|
|
2135
|
+
if let autoMode {
|
|
2136
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "auto_brightness", autoMode)
|
|
2137
|
+
}
|
|
2138
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "brightness", level)
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
public func setAutoBrightness(enabled: Bool) async throws {
|
|
2142
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "auto_brightness", enabled)
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
public func setDashboardPosition(_ request: DashboardPositionRequest) async throws {
|
|
2146
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "dashboard_height", request.height)
|
|
2147
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "dashboard_depth", request.depth)
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
public func setDashboardMenu(_ items: [DashboardMenuItem]) async throws {
|
|
2151
|
+
DeviceStore.shared.apply(
|
|
2152
|
+
ObservableStore.bluetoothCategory,
|
|
2153
|
+
"menu_apps",
|
|
2154
|
+
items.map(\.dictionary)
|
|
2155
|
+
)
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
public func setHeadUpAngle(_ angleDegrees: Int) async throws {
|
|
2159
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "head_up_angle", angleDegrees)
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
public func setScreenDisabled(_ disabled: Bool) async throws {
|
|
2163
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "screen_disabled", disabled)
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
public func setGalleryMode(_ mode: GalleryMode) async throws {
|
|
2167
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "gallery_mode", mode == .auto)
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
public func setButtonPhotoSettings(_ settings: ButtonPhotoSettings) async throws {
|
|
2171
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_photo_size", settings.size.rawValue)
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
public func setButtonVideoRecordingSettings(_ settings: ButtonVideoRecordingSettings) async throws {
|
|
2175
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_width", settings.width)
|
|
2176
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_height", settings.height)
|
|
2177
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_video_fps", settings.fps)
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
public func setButtonCameraLed(enabled: Bool) async throws {
|
|
2181
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_camera_led", enabled)
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
public func setButtonMaxRecordingTime(minutes: Int) async throws {
|
|
2185
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "button_max_recording_time", minutes)
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
public func setCameraFov(_ fov: CameraFov) async throws {
|
|
2189
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "camera_fov", fov.value)
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
public func setMicState(_ config: MicConfiguration) {
|
|
2193
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "should_send_pcm", config.sendPcmData)
|
|
2194
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "should_send_lc3", config.sendLc3Data)
|
|
2195
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "should_send_transcript", config.sendTranscript)
|
|
2196
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "bypass_vad", config.bypassVad)
|
|
2197
|
+
DeviceManager.shared.setMicState()
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
public func setPreferredMic(_ preferredMic: MicPreference) {
|
|
2201
|
+
DeviceStore.shared.apply(ObservableStore.bluetoothCategory, "preferred_mic", preferredMic.rawValue)
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
public func setOwnAppAudioPlaying(_ playing: Bool) {
|
|
2205
|
+
PhoneAudioMonitor.getInstance().setOwnAppAudioPlaying(playing)
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
public func requestWifiScan() {
|
|
2209
|
+
DeviceManager.shared.requestWifiScan()
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
public func sendWifiCredentials(ssid: String, password: String) {
|
|
2213
|
+
DeviceManager.shared.sendWifiCredentials(ssid, password)
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
public func forgetWifiNetwork(ssid: String) {
|
|
2217
|
+
DeviceManager.shared.forgetWifiNetwork(ssid)
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
public func setHotspotState(enabled: Bool) {
|
|
2221
|
+
DeviceManager.shared.setHotspotState(enabled)
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
public func requestPhoto(_ request: PhotoRequest) {
|
|
2225
|
+
DeviceManager.shared.photoRequest(
|
|
2226
|
+
request.requestId,
|
|
2227
|
+
request.appId,
|
|
2228
|
+
request.size.rawValue,
|
|
2229
|
+
request.webhookUrl,
|
|
2230
|
+
request.authToken,
|
|
2231
|
+
request.compress?.rawValue,
|
|
2232
|
+
request.flash,
|
|
2233
|
+
request.sound
|
|
2234
|
+
)
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
public func queryGalleryStatus() {
|
|
2238
|
+
DeviceManager.shared.queryGalleryStatus()
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
public func startStream(_ request: StreamRequest) {
|
|
2242
|
+
DeviceManager.shared.startStream(request.values)
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
public func keepStreamAlive(_ request: StreamKeepAliveRequest) {
|
|
2246
|
+
DeviceManager.shared.keepStreamAlive(request.values)
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
public func rgbLedControl(_ request: RgbLedRequest) {
|
|
2250
|
+
DeviceManager.shared.rgbLedControl(
|
|
2251
|
+
requestId: request.requestId,
|
|
2252
|
+
packageName: request.packageName,
|
|
2253
|
+
action: request.action.rawValue,
|
|
2254
|
+
color: request.color?.rawValue,
|
|
2255
|
+
ontime: request.ontime,
|
|
2256
|
+
offtime: request.offtime,
|
|
2257
|
+
count: request.count
|
|
2258
|
+
)
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
public func stopStream() {
|
|
2262
|
+
DeviceManager.shared.stopStream()
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
public func startVideoRecording(_ request: VideoRecordingRequest) {
|
|
2266
|
+
DeviceManager.shared.startVideoRecording(
|
|
2267
|
+
request.requestId,
|
|
2268
|
+
request.save,
|
|
2269
|
+
request.flash,
|
|
2270
|
+
request.sound
|
|
2271
|
+
)
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
public func stopVideoRecording(requestId: String) {
|
|
2275
|
+
DeviceManager.shared.stopVideoRecording(requestId)
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
public func requestVersionInfo() {
|
|
2279
|
+
DeviceManager.shared.requestVersionInfo()
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
public func sendOtaStart() {
|
|
2283
|
+
DeviceManager.shared.sendOtaStart()
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
public func sendOtaQueryStatus() {
|
|
2287
|
+
DeviceManager.shared.sendOtaQueryStatus()
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
public func sendShutdown() {
|
|
2291
|
+
DeviceManager.shared.sendShutdown()
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
public func sendReboot() {
|
|
2295
|
+
DeviceManager.shared.sendReboot()
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
public func sendIncidentId(_ incidentId: String, apiBaseUrl: String? = nil) {
|
|
2299
|
+
DeviceManager.shared.sendIncidentId(incidentId, apiBaseUrl: apiBaseUrl)
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
public func invalidate() {
|
|
2303
|
+
if let bridgeEventSinkId {
|
|
2304
|
+
Bridge.removeEventSink(bridgeEventSinkId)
|
|
2305
|
+
self.bridgeEventSinkId = nil
|
|
2306
|
+
}
|
|
2307
|
+
if let storeListenerId {
|
|
2308
|
+
DeviceStore.shared.store.removeListener(storeListenerId)
|
|
2309
|
+
self.storeListenerId = nil
|
|
2310
|
+
}
|
|
2311
|
+
delegate = nil
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
private func dispatchStoreUpdate(_ category: String, _ changes: [String: Any]) {
|
|
2315
|
+
switch ObservableStore.normalizeCategory(category) {
|
|
2316
|
+
case "glasses":
|
|
2317
|
+
delegate?.mentraBluetoothSDK(self, didUpdateGlassesStatus: GlassesStatusUpdate(values: glassesStatusChanges(changes)))
|
|
2318
|
+
case ObservableStore.bluetoothCategory:
|
|
2319
|
+
delegate?.mentraBluetoothSDK(self, didUpdateBluetoothStatus: BluetoothStatusUpdate(values: changes))
|
|
2320
|
+
if !suppressDefaultDeviceEvents && changes.keys.contains(where: { defaultDeviceKeys.contains($0) }) {
|
|
2321
|
+
dispatchDefaultDeviceChanged()
|
|
2322
|
+
}
|
|
2323
|
+
dispatchDiscoveredDevices(changes["searchResults"])
|
|
2324
|
+
default:
|
|
2325
|
+
break
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
private func glassesStatusChanges(_ changes: [String: Any]) -> [String: Any] {
|
|
2330
|
+
var merged = changes
|
|
2331
|
+
|
|
2332
|
+
if changes.keys.contains(where: { ["wifiConnected", "wifiSsid", "wifiLocalIp"].contains($0) }) {
|
|
2333
|
+
merged["wifiConnected"] = DeviceStore.shared.get("glasses", "wifiConnected") as? Bool ?? false
|
|
2334
|
+
merged["wifiSsid"] = DeviceStore.shared.get("glasses", "wifiSsid") as? String ?? ""
|
|
2335
|
+
merged["wifiLocalIp"] = DeviceStore.shared.get("glasses", "wifiLocalIp") as? String ?? ""
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
if changes.keys.contains(where: { ["connected", "fullyBooted", "connectionState"].contains($0) }) {
|
|
2339
|
+
merged["connected"] = GlassesStore.shared.get("glasses", "connected") as? Bool ?? false
|
|
2340
|
+
merged["fullyBooted"] = GlassesStore.shared.get("glasses", "fullyBooted") as? Bool ?? false
|
|
2341
|
+
merged["connectionState"] = GlassesStore.shared.get("glasses", "connectionState") as? String ?? "DISCONNECTED"
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
if changes["signalStrengthUpdatedAt"] != nil, changes["signalStrength"] == nil {
|
|
2345
|
+
merged["signalStrength"] = DeviceStore.shared.get("glasses", "signalStrength") as? Int ?? -1
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
return merged
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
private func dispatchDefaultDeviceChanged() {
|
|
2352
|
+
delegate?.mentraBluetoothSDK(self, didChangeDefaultDevice: currentDefaultDevice())
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
private func finishDefaultDeviceApply(generation: Int) {
|
|
2356
|
+
Task { @MainActor [weak self] in
|
|
2357
|
+
guard let self, generation == self.defaultDeviceApplyGeneration else { return }
|
|
2358
|
+
self.suppressDefaultDeviceEvents = false
|
|
2359
|
+
self.dispatchDefaultDeviceChanged()
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
private func currentDefaultDevice() -> Device? {
|
|
2364
|
+
let core = DeviceStore.shared.store.getCategory(ObservableStore.bluetoothCategory)
|
|
2365
|
+
guard let model = core["default_wearable"] as? String, !model.isEmpty else { return nil }
|
|
2366
|
+
guard let name = core["device_name"] as? String, !name.isEmpty else { return nil }
|
|
2367
|
+
let identifier = (core["device_address"] as? String).flatMap { $0.isEmpty ? nil : $0 }
|
|
2368
|
+
return Device(
|
|
2369
|
+
model: DeviceModel.fromDeviceType(model),
|
|
2370
|
+
name: name,
|
|
2371
|
+
identifier: identifier
|
|
2372
|
+
)
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
2376
|
+
guard let results = rawSearchResults as? [[String: Any]] else { return }
|
|
2377
|
+
for result in results {
|
|
2378
|
+
guard let name = result["deviceName"] as? String ?? result["name"] as? String else { continue }
|
|
2379
|
+
guard discoveredDeviceNames.insert(name).inserted else { continue }
|
|
2380
|
+
guard let device = Device(values: result) else { continue }
|
|
2381
|
+
delegate?.mentraBluetoothSDK(self, didDiscover: device)
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
private func dispatchBridgeEvent(_ eventName: String, _ data: [String: Any]) {
|
|
2386
|
+
switch eventName {
|
|
2387
|
+
case "log":
|
|
2388
|
+
delegate?.mentraBluetoothSDK(self, didLog: data["message"] as? String ?? data.description)
|
|
2389
|
+
case "button_press":
|
|
2390
|
+
let event = ButtonPressEvent(
|
|
2391
|
+
buttonId: data["buttonId"] as? String ?? "",
|
|
2392
|
+
pressType: data["pressType"] as? String ?? "",
|
|
2393
|
+
timestamp: intValue(data["timestamp"])
|
|
2394
|
+
)
|
|
2395
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .buttonPress(event))
|
|
2396
|
+
case "touch_event":
|
|
2397
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .touch(TouchEvent(values: data)))
|
|
2398
|
+
case "mic_pcm":
|
|
2399
|
+
if let frame = data["pcm"] as? Data {
|
|
2400
|
+
delegate?.mentraBluetoothSDK(self, didReceiveMicPcm: frame)
|
|
2401
|
+
}
|
|
2402
|
+
case "mic_lc3":
|
|
2403
|
+
if let frame = data["lc3"] as? Data {
|
|
2404
|
+
delegate?.mentraBluetoothSDK(self, didReceiveMicLc3: frame)
|
|
2405
|
+
}
|
|
2406
|
+
case "local_transcription":
|
|
2407
|
+
let event = LocalTranscriptionEvent(
|
|
2408
|
+
text: data["text"] as? String ?? "",
|
|
2409
|
+
isFinal: data["isFinal"] as? Bool ?? false,
|
|
2410
|
+
values: data
|
|
2411
|
+
)
|
|
2412
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .localTranscription(event))
|
|
2413
|
+
case "hotspot_status_change":
|
|
2414
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .hotspotStatus(HotspotStatusEvent(values: data)))
|
|
2415
|
+
case "wifi_status_change":
|
|
2416
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .wifiStatus(WifiStatusEvent(values: data)))
|
|
2417
|
+
case "hotspot_error":
|
|
2418
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .hotspotError(HotspotErrorEvent(values: data)))
|
|
2419
|
+
case "photo_response":
|
|
2420
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .photoResponse(PhotoResponseEvent(values: data)))
|
|
2421
|
+
case "stream_status":
|
|
2422
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .streamStatus(StreamStatusEvent(values: data)))
|
|
2423
|
+
case "keep_alive_ack":
|
|
2424
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .keepAliveAck(KeepAliveAckEvent(values: data)))
|
|
2425
|
+
case "compatible_glasses_search_stop":
|
|
2426
|
+
delegate?.mentraBluetoothSDK(self, didStopScan: .completed)
|
|
2427
|
+
case "pair_failure":
|
|
2428
|
+
delegate?.mentraBluetoothSDK(
|
|
2429
|
+
self,
|
|
2430
|
+
didFail: BluetoothError(
|
|
2431
|
+
code: "pair_failure",
|
|
2432
|
+
message: data["error"] as? String ?? data.description
|
|
2433
|
+
)
|
|
2434
|
+
)
|
|
2435
|
+
default:
|
|
2436
|
+
delegate?.mentraBluetoothSDK(self, didReceive: .raw(name: eventName, values: data))
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|