@glassly/bluetooth-sdk 0.1.0-dev.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +563 -0
- package/android/build.gradle +450 -0
- package/android/gradle.properties.example +9 -0
- package/android/lc3Lib/CMakeLists.txt +6 -0
- package/android/lc3Lib/build.gradle +212 -0
- package/android/lc3Lib/proguard-rules.pro +33 -0
- package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
- package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
- package/android/lc3Lib/src/main/java/com/glassly/lc3Lib/Lc3Cpp.java +38 -0
- package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
- package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
- package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
- package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
- package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
- package/android/settings.gradle +4 -0
- package/android/silero/LICENSE.md +17 -0
- package/android/silero/build.gradle +50 -0
- package/android/silero/proguard-rules.pro +24 -0
- package/android/silero/src/androidTest/assets/hello.wav +0 -0
- package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
- package/android/silero/src/main/assets/LICENSE +21 -0
- package/android/silero/src/main/assets/silero_vad.onnx +0 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
- package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
- package/android/src/main/AndroidManifest.xml +42 -0
- package/android/src/main/assets/G2Firmware/README.md +11 -0
- package/android/src/main/assets/G2Firmware/g2-glassly-cfw-2.2.9.22.evenota.bin +0 -0
- package/android/src/main/assets/G2Firmware/g2-stock-2.2.9.22.evenota.bin +0 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +2321 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkAnalytics.kt +216 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDebug.kt +14 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDefaults.kt +7 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +1087 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +953 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +2472 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +359 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +9 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/LocalNetworkModule.kt +414 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ObservableStore.kt +88 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/OtaManifest.kt +191 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ReleaseChangelog.kt +61 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/audio/AudioModels.kt +144 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/camera/CameraModels.kt +486 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/ControllerManager.kt +158 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +1057 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/debug/BleTraceLogger.kt +165 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/events/BluetoothEvents.kt +178 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/internal/MapParsing.kt +140 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/net/LocalIpv4.kt +57 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/OtaServerModule.kt +120 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +622 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/PhotoReceiverModule.kt +156 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/requests/DisplayRequests.kt +56 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/Foreground.kt +272 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/PhoneMic.kt +1195 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Ar99.java +2653 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1.kt +3837 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +6457 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +1253 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +270 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +291 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +411 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/GlasslyBle.java +43189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Mach1.kt +1267 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +10742 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +2008 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Nimo.kt +2532 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +470 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Simulated.kt +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +525 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +669 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/RuntimeState.kt +201 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/WifiHotspotStatus.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/streaming/StreamModels.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +391 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/VadGateSpeechPolicy.kt +227 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +248 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/types/DeviceModels.kt +96 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AES.java +106 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AvifExifStripper.java +573 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleJsonCompact.java +496 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadService.java +677 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/Constants.kt +46 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/G1Text.kt +436 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleUploadService.java +77 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtils.java +827 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunkReassembler.java +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunker.java +325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +698 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitor.kt +348 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/StreamStatusEventTest.kt +29 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/camera/PhotoRequestTest.kt +141 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/EvenRleTest.kt +63 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +89 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +344 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +200 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocolTest.kt +131 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/AvifExifStripperTest.java +46 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BleJsonCompactTest.java +319 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +107 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
- package/android/src/test/resources/avif_with_exif.avif +0 -0
- package/app.plugin.js +3 -0
- package/build/BluetoothSdk.types.d.ts +1390 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -0
- package/build/BluetoothSdk.types.js +41 -0
- package/build/BluetoothSdk.types.js.map +1 -0
- package/build/_internal.d.ts +17 -0
- package/build/_internal.d.ts.map +1 -0
- package/build/_internal.js +16 -0
- package/build/_internal.js.map +1 -0
- package/build/_private/BluetoothSdkModule.d.ts +171 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
- package/build/_private/BluetoothSdkModule.js +300 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -0
- package/build/_private/LocalNetworkModule.d.ts +38 -0
- package/build/_private/LocalNetworkModule.d.ts.map +1 -0
- package/build/_private/LocalNetworkModule.js +3 -0
- package/build/_private/LocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts +4 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -0
- package/build/_private/cameraRequestPayload.js +32 -0
- package/build/_private/cameraRequestPayload.js.map +1 -0
- package/build/_private/photoRequestPayload.d.ts +6 -0
- package/build/_private/photoRequestPayload.d.ts.map +1 -0
- package/build/_private/photoRequestPayload.js +86 -0
- package/build/_private/photoRequestPayload.js.map +1 -0
- package/build/_private/sdkOtaManifest.d.ts +11 -0
- package/build/_private/sdkOtaManifest.d.ts.map +1 -0
- package/build/_private/sdkOtaManifest.js +15 -0
- package/build/_private/sdkOtaManifest.js.map +1 -0
- package/build/changelogs.d.ts +7 -0
- package/build/changelogs.d.ts.map +1 -0
- package/build/changelogs.js +46 -0
- package/build/changelogs.js.map +1 -0
- package/build/debug.d.ts +3 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +8 -0
- package/build/debug.js.map +1 -0
- package/build/generated/changelogCatalog.d.ts +12 -0
- package/build/generated/changelogCatalog.d.ts.map +1 -0
- package/build/generated/changelogCatalog.js +16 -0
- package/build/generated/changelogCatalog.js.map +1 -0
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +170 -0
- package/build/index.js.map +1 -0
- package/build/ota-server/OtaServer.types.d.ts +14 -0
- package/build/ota-server/OtaServer.types.d.ts.map +1 -0
- package/build/ota-server/OtaServer.types.js +2 -0
- package/build/ota-server/OtaServer.types.js.map +1 -0
- package/build/ota-server/OtaServerModule.d.ts +21 -0
- package/build/ota-server/OtaServerModule.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.js +3 -0
- package/build/ota-server/OtaServerModule.js.map +1 -0
- package/build/ota-server/OtaServerModule.web.d.ts +10 -0
- package/build/ota-server/OtaServerModule.web.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.web.js +12 -0
- package/build/ota-server/OtaServerModule.web.js.map +1 -0
- package/build/ota-server/index.d.ts +3 -0
- package/build/ota-server/index.d.ts.map +1 -0
- package/build/ota-server/index.js +3 -0
- package/build/ota-server/index.js.map +1 -0
- package/build/ota-transport/index.d.ts +67 -0
- package/build/ota-transport/index.d.ts.map +1 -0
- package/build/ota-transport/index.js +58 -0
- package/build/ota-transport/index.js.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts +18 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.js +2 -0
- package/build/photo-receiver/PhotoReceiver.types.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.js +3 -0
- package/build/photo-receiver/PhotoReceiverModule.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js +12 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js.map +1 -0
- package/build/photo-receiver/index.d.ts +3 -0
- package/build/photo-receiver/index.d.ts.map +1 -0
- package/build/photo-receiver/index.js +3 -0
- package/build/photo-receiver/index.js.map +1 -0
- package/build/react/index.d.ts +5 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +4 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useBluetooth.d.ts +100 -0
- package/build/react/useBluetooth.d.ts.map +1 -0
- package/build/react/useBluetooth.js +167 -0
- package/build/react/useBluetooth.js.map +1 -0
- package/build/react/useBluetoothEvent.d.ts +6 -0
- package/build/react/useBluetoothEvent.d.ts.map +1 -0
- package/build/react/useBluetoothEvent.js +21 -0
- package/build/react/useBluetoothEvent.js.map +1 -0
- package/build/react/useBluetoothScan.d.ts +22 -0
- package/build/react/useBluetoothScan.d.ts.map +1 -0
- package/build/react/useBluetoothScan.js +135 -0
- package/build/react/useBluetoothScan.js.map +1 -0
- package/build/react/useBluetoothStatus.d.ts +16 -0
- package/build/react/useBluetoothStatus.d.ts.map +1 -0
- package/build/react/useBluetoothStatus.js +137 -0
- package/build/react/useBluetoothStatus.js.map +1 -0
- package/build/react/useGlassesConnection.d.ts +29 -0
- package/build/react/useGlassesConnection.d.ts.map +1 -0
- package/build/react/useGlassesConnection.js +153 -0
- package/build/react/useGlassesConnection.js.map +1 -0
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/expo-module.config.json +14 -0
- package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
- package/ios/BluetoothSdkModule.swift +1028 -0
- package/ios/GlasslyBluetoothSDK.podspec +106 -0
- package/ios/LocalIPv4.swift +150 -0
- package/ios/LocalOtaServer.swift +309 -0
- package/ios/LocalPhotoUploadServer.swift +754 -0
- package/ios/OtaServerModule.swift +147 -0
- package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
- package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
- package/ios/Packages/CoreObjC/attdet.c +92 -0
- package/ios/Packages/CoreObjC/attdet.h +44 -0
- package/ios/Packages/CoreObjC/bits.c +375 -0
- package/ios/Packages/CoreObjC/bits.h +315 -0
- package/ios/Packages/CoreObjC/bwdet.c +129 -0
- package/ios/Packages/CoreObjC/bwdet.h +69 -0
- package/ios/Packages/CoreObjC/common.h +151 -0
- package/ios/Packages/CoreObjC/energy.c +70 -0
- package/ios/Packages/CoreObjC/energy.h +43 -0
- package/ios/Packages/CoreObjC/fastmath.h +158 -0
- package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/lc3.c +704 -0
- package/ios/Packages/CoreObjC/lc3.h +313 -0
- package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
- package/ios/Packages/CoreObjC/lc3_private.h +163 -0
- package/ios/Packages/CoreObjC/ltpf.c +905 -0
- package/ios/Packages/CoreObjC/ltpf.h +111 -0
- package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
- package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
- package/ios/Packages/CoreObjC/makefile.mk +35 -0
- package/ios/Packages/CoreObjC/mdct.c +469 -0
- package/ios/Packages/CoreObjC/mdct.h +57 -0
- package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
- package/ios/Packages/CoreObjC/meson.build +61 -0
- package/ios/Packages/CoreObjC/plc.c +61 -0
- package/ios/Packages/CoreObjC/plc.h +57 -0
- package/ios/Packages/CoreObjC/rnnoise.h +114 -0
- package/ios/Packages/CoreObjC/sns.c +880 -0
- package/ios/Packages/CoreObjC/sns.h +103 -0
- package/ios/Packages/CoreObjC/spec.c +907 -0
- package/ios/Packages/CoreObjC/spec.h +119 -0
- package/ios/Packages/CoreObjC/tables.c +3457 -0
- package/ios/Packages/CoreObjC/tables.h +94 -0
- package/ios/Packages/CoreObjC/tns.c +457 -0
- package/ios/Packages/CoreObjC/tns.h +99 -0
- package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
- package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +2383 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +48 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Packages/libbz2/module.modulemap +5 -0
- package/ios/Packages/libbz2/shim.h +1 -0
- package/ios/PhotoReceiverModule.swift +129 -0
- package/ios/Source/BluetoothSDK.swift +2299 -0
- package/ios/Source/BluetoothSDKDebug.swift +12 -0
- package/ios/Source/BluetoothSdkDefaults.swift +29 -0
- package/ios/Source/Bridge.swift +708 -0
- package/ios/Source/Bridging-Header.h +16 -0
- package/ios/Source/DeviceManager.swift +2054 -0
- package/ios/Source/DeviceStore.swift +350 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +8 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
- package/ios/Source/ObservableStore.swift +118 -0
- package/ios/Source/OtaManifest.swift +211 -0
- package/ios/Source/PrivacyInfo.xcprivacy +23 -0
- package/ios/Source/ReleaseChangelog.swift +65 -0
- package/ios/Source/Resources/G2Firmware/README.md +10 -0
- package/ios/Source/Resources/G2Firmware/g2-glassly-cfw-2.2.9.22.evenota.bin +0 -0
- package/ios/Source/Resources/G2Firmware/g2-stock-2.2.9.22.evenota.bin +0 -0
- package/ios/Source/audio/AudioModels.swift +178 -0
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +866 -0
- package/ios/Source/connection/ScanSession.swift +27 -0
- package/ios/Source/controllers/ControllerManager.swift +184 -0
- package/ios/Source/controllers/R1.swift +925 -0
- package/ios/Source/errors/BluetoothSdkError.swift +19 -0
- package/ios/Source/events/BluetoothEvents.swift +342 -0
- package/ios/Source/internal/BleTraceLogger.swift +192 -0
- package/ios/Source/internal/BluetoothAvailability.swift +78 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +199 -0
- package/ios/Source/internal/ValueParsing.swift +99 -0
- package/ios/Source/requests/DisplayRequests.swift +82 -0
- package/ios/Source/services/PhoneMic.swift +662 -0
- package/ios/Source/sgcs/Ar99.swift +2451 -0
- package/ios/Source/sgcs/G1.swift +2554 -0
- package/ios/Source/sgcs/G2.swift +7142 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +43 -0
- package/ios/Source/sgcs/G2CfwRenderer.swift +1295 -0
- package/ios/Source/sgcs/G2CfwTextureCache.swift +233 -0
- package/ios/Source/sgcs/G2FlashProtocol.swift +312 -0
- package/ios/Source/sgcs/G2Flasher.swift +773 -0
- package/ios/Source/sgcs/Mach1.swift +541 -0
- package/ios/Source/sgcs/MentraLive.swift +7011 -0
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
- package/ios/Source/sgcs/MentraNex.swift +3220 -0
- package/ios/Source/sgcs/Nimo.swift +1886 -0
- package/ios/Source/sgcs/SGCManager.swift +621 -0
- package/ios/Source/sgcs/Simulated.swift +274 -0
- package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
- package/ios/Source/sgcs/glassly_ble.pb.swift +4354 -0
- package/ios/Source/speakerid/SpeakerIdEngine.swift +573 -0
- package/ios/Source/status/DeviceStatus.swift +981 -0
- package/ios/Source/status/RuntimeState.swift +359 -0
- package/ios/Source/status/WifiHotspotStatus.swift +345 -0
- package/ios/Source/streaming/StreamModels.swift +693 -0
- package/ios/Source/stt/STTTools.swift +191 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +466 -0
- package/ios/Source/tts/TTSTools.swift +167 -0
- package/ios/Source/types/DeviceModels.swift +177 -0
- package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
- package/ios/Source/utils/BinaryCodec.swift +61 -0
- package/ios/Source/utils/BleJsonCompact.swift +405 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/Constants.swift +78 -0
- package/ios/Source/utils/Enums.swift +95 -0
- package/ios/Source/utils/G1Text.swift +2096 -0
- package/ios/Source/utils/G2NotifyFile.swift +116 -0
- package/ios/Source/utils/JSCExperiment.swift +241 -0
- package/ios/Source/utils/MemoryMonitor.swift +44 -0
- package/ios/Source/utils/MessageChunkReassembler.swift +190 -0
- package/ios/Source/utils/MessageChunker.swift +294 -0
- package/ios/Source/utils/Models.swift +135 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +230 -0
- package/ios/Source/utils/TarBz2Extractor.swift +257 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +78 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +372 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +162 -0
- package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +182 -0
- package/ios/Tests/BluetoothSDKTests/G2NotifyFileTests.swift +91 -0
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
- package/ios/Tests/ReleaseChangelogTests.swift +13 -0
- package/package.json +129 -0
- package/plugin/build/index.d.ts +10 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.d.ts +3 -0
- package/plugin/build/withAndroid.js +190 -0
- package/plugin/build/withIos.d.ts +5 -0
- package/plugin/build/withIos.js +108 -0
- package/scripts/inject-ios-sdk-version.mjs +65 -0
- package/scripts/public-ota-api.test.mjs +37 -0
- package/scripts/verify-release-package.mjs +46 -0
- package/scripts/write-release-metadata.mjs +154 -0
- package/scripts/write-release-metadata.test.mjs +37 -0
- package/src/BluetoothSdk.types.ts +1659 -0
- package/src/_internal.ts +16 -0
- package/src/_private/BluetoothSdkModule.ts +683 -0
- package/src/_private/LocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +34 -0
- package/src/_private/photoRequestPayload.ts +90 -0
- package/src/_private/sdkOtaManifest.ts +18 -0
- package/src/changelogs.ts +46 -0
- package/src/debug.ts +9 -0
- package/src/generated/changelogCatalog.ts +17 -0
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +320 -0
- package/src/ota-server/OtaServer.types.ts +10 -0
- package/src/ota-server/OtaServerModule.ts +23 -0
- package/src/ota-server/OtaServerModule.web.ts +17 -0
- package/src/ota-server/index.ts +2 -0
- package/src/ota-transport/index.ts +116 -0
- package/src/photo-receiver/PhotoReceiver.types.ts +20 -0
- package/src/photo-receiver/PhotoReceiverModule.ts +11 -0
- package/src/photo-receiver/PhotoReceiverModule.web.ts +17 -0
- package/src/photo-receiver/index.ts +2 -0
- package/src/react/index.ts +21 -0
- package/src/react/useBluetooth.ts +309 -0
- package/src/react/useBluetoothEvent.ts +35 -0
- package/src/react/useBluetoothScan.ts +173 -0
- package/src/react/useBluetoothStatus.ts +180 -0
- package/src/react/useGlassesConnection.ts +202 -0
- package/src/types/index.ts +12 -0
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
//
|
|
2
|
+
// R1.swift
|
|
3
|
+
// Glassly_Manager
|
|
4
|
+
//
|
|
5
|
+
// Even Realities R1 Smart Ring controller
|
|
6
|
+
// Protocol reverse-engineered from BTSnoop captures and firmware analysis
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Combine
|
|
10
|
+
import CoreBluetooth
|
|
11
|
+
import Foundation
|
|
12
|
+
|
|
13
|
+
// MARK: - R1 BLE Constants
|
|
14
|
+
|
|
15
|
+
private enum R1BLE {
|
|
16
|
+
/// Ring custom service
|
|
17
|
+
static let SERVICE_UUID = CBUUID(string: "BAE80001-4F05-4503-8E65-3AF1F7329D1F")
|
|
18
|
+
|
|
19
|
+
// Channel 1
|
|
20
|
+
static let WRITE_CHAR_1 = CBUUID(string: "BAE80010-4F05-4503-8E65-3AF1F7329D1F")
|
|
21
|
+
static let NOTIFY_CHAR_1 = CBUUID(string: "BAE80011-4F05-4503-8E65-3AF1F7329D1F")
|
|
22
|
+
|
|
23
|
+
// Channel 2
|
|
24
|
+
static let WRITE_CHAR_2 = CBUUID(string: "BAE80012-4F05-4503-8E65-3AF1F7329D1F")
|
|
25
|
+
static let NOTIFY_CHAR_2 = CBUUID(string: "BAE80013-4F05-4503-8E65-3AF1F7329D1F")
|
|
26
|
+
|
|
27
|
+
// Standard battery service (ring may expose this)
|
|
28
|
+
static let BATTERY_SERVICE = CBUUID(string: "180F")
|
|
29
|
+
static let BATTERY_LEVEL_CHAR = CBUUID(string: "2A19")
|
|
30
|
+
|
|
31
|
+
/// Name filters for scanning
|
|
32
|
+
static let NAME_FILTERS = ["EVEN R1", "BCL60"]
|
|
33
|
+
|
|
34
|
+
/// Gesture protocol marker
|
|
35
|
+
static let GESTURE_MARKER: UInt8 = 0xFF
|
|
36
|
+
|
|
37
|
+
static let SCAN_TIMEOUT: TimeInterval = 15.0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// MARK: - EUS transport encoder (phone → ring, channel 2)
|
|
41
|
+
|
|
42
|
+
/// Recovered firmware contract (openCFW r1/ research): every phone→ring packet
|
|
43
|
+
/// is a 12-byte "model" header
|
|
44
|
+
/// [version, module, moduleVersion, serial LE16, status, command, subcommand,
|
|
45
|
+
/// totalLength LE16, crc16 LE16]
|
|
46
|
+
/// followed by the payload, where the phone-direction CRC-16 (CCITT rounds,
|
|
47
|
+
/// init 0xFFFF) runs over the compact bytes [0,1,2,3,5,6,7,8,0x00] and then
|
|
48
|
+
/// continues over the payload bytes. The model is carried on channel 2 inside
|
|
49
|
+
/// a fragment train [sequence, crc32 LE32, ≤239 bytes]; the CRC-32 (poly
|
|
50
|
+
/// 0x1EDC6F41, MSB-first, zero seed, no final xor) covers the whole model and
|
|
51
|
+
/// sequence 0 terminates the train — all our packets fit a single fragment.
|
|
52
|
+
private enum R1Eus {
|
|
53
|
+
static let VERSION: UInt8 = 100
|
|
54
|
+
static let MODULE_SYSTEM: UInt8 = 0x01
|
|
55
|
+
static let MODULE_VERSION: UInt8 = 100
|
|
56
|
+
/// status bit 0x02 marks a SET; advStart requires it
|
|
57
|
+
static let STATUS_SET: UInt8 = 0x02
|
|
58
|
+
|
|
59
|
+
/// system subcommand 0x08: payload [0x01] claims the phone role; the ring
|
|
60
|
+
/// requests link encryption/bonding in response
|
|
61
|
+
static let SUBCMD_PAIR_AUTH: UInt8 = 0x08
|
|
62
|
+
/// system subcommand 0x0A: 12-byte payload = two 6-byte glasses arm MACs;
|
|
63
|
+
/// the ring stores both targets and fast-advertises for them
|
|
64
|
+
static let SUBCMD_ADV_START: UInt8 = 0x0A
|
|
65
|
+
|
|
66
|
+
private static func crc16Step(_ crc: UInt16, _ byte: UInt8) -> UInt16 {
|
|
67
|
+
var c = (crc >> 8) | (crc << 8)
|
|
68
|
+
c ^= UInt16(byte)
|
|
69
|
+
c ^= (c & 0x00FF) >> 4
|
|
70
|
+
c ^= c << 12
|
|
71
|
+
c ^= (c & 0x00FF) << 5
|
|
72
|
+
return c
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private static func phoneCrc16(header: [UInt8], payload: Data) -> UInt16 {
|
|
76
|
+
var crc: UInt16 = 0xFFFF
|
|
77
|
+
// compact header: bytes 0-3 and 5-8, then a zero placeholder
|
|
78
|
+
for b in [header[0], header[1], header[2], header[3], header[5], header[6], header[7], header[8], 0] {
|
|
79
|
+
crc = crc16Step(crc, b)
|
|
80
|
+
}
|
|
81
|
+
for b in payload {
|
|
82
|
+
crc = crc16Step(crc, b)
|
|
83
|
+
}
|
|
84
|
+
return crc
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private static func crc32(_ data: Data) -> UInt32 {
|
|
88
|
+
var crc: UInt32 = 0
|
|
89
|
+
for byte in data {
|
|
90
|
+
crc ^= UInt32(byte) << 24
|
|
91
|
+
for _ in 0 ..< 8 {
|
|
92
|
+
crc = (crc & 0x8000_0000) != 0 ? (crc << 1) ^ 0x1EDC_6F41 : crc << 1
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return crc
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Build one single-fragment system-module command packet.
|
|
99
|
+
static func frame(subcommand: UInt8, status: UInt8, serial: UInt16, payload: Data) -> Data {
|
|
100
|
+
let length = UInt16(12 + payload.count)
|
|
101
|
+
var header: [UInt8] = [
|
|
102
|
+
VERSION, MODULE_SYSTEM, MODULE_VERSION,
|
|
103
|
+
UInt8(serial & 0xFF), UInt8(serial >> 8),
|
|
104
|
+
status, 0x00, subcommand,
|
|
105
|
+
UInt8(length & 0xFF), UInt8(length >> 8),
|
|
106
|
+
0, 0,
|
|
107
|
+
]
|
|
108
|
+
let crc16 = phoneCrc16(header: header, payload: payload)
|
|
109
|
+
header[10] = UInt8(crc16 & 0xFF)
|
|
110
|
+
header[11] = UInt8(crc16 >> 8)
|
|
111
|
+
var model = Data(header)
|
|
112
|
+
model.append(payload)
|
|
113
|
+
|
|
114
|
+
let outer = crc32(model)
|
|
115
|
+
var packet = Data([0x00]) // sequence 0: single-fragment train
|
|
116
|
+
packet.append(UInt8(outer & 0xFF))
|
|
117
|
+
packet.append(UInt8((outer >> 8) & 0xFF))
|
|
118
|
+
packet.append(UInt8((outer >> 16) & 0xFF))
|
|
119
|
+
packet.append(UInt8((outer >> 24) & 0xFF))
|
|
120
|
+
packet.append(model)
|
|
121
|
+
return packet
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// MARK: - R1 Gesture Types
|
|
126
|
+
|
|
127
|
+
private enum R1Gesture: String {
|
|
128
|
+
case hold
|
|
129
|
+
case singleTap = "single_tap"
|
|
130
|
+
case doubleTap = "double_tap"
|
|
131
|
+
case swipeUp = "swipe_up"
|
|
132
|
+
case swipeDown = "swipe_down"
|
|
133
|
+
|
|
134
|
+
/// Parse gesture from notification data: [0xFF, type, param]
|
|
135
|
+
static func parse(from data: Data) -> R1Gesture? {
|
|
136
|
+
guard data.count >= 3, data[0] == R1BLE.GESTURE_MARKER else { return nil }
|
|
137
|
+
switch data[1] {
|
|
138
|
+
case 0x03:
|
|
139
|
+
return .hold
|
|
140
|
+
case 0x04:
|
|
141
|
+
return data[2] == 0x01 ? .singleTap : data[2] == 0x02 ? .doubleTap : nil
|
|
142
|
+
case 0x05:
|
|
143
|
+
return data[2] < 0x80 ? .swipeUp : .swipeDown
|
|
144
|
+
default:
|
|
145
|
+
return nil
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// MARK: - R1 Controller
|
|
151
|
+
|
|
152
|
+
@MainActor
|
|
153
|
+
class R1: NSObject, ControllerManager {
|
|
154
|
+
var type = ControllerTypes.R1
|
|
155
|
+
let hasMic = false // R1 ring has no microphone
|
|
156
|
+
|
|
157
|
+
// Connection state
|
|
158
|
+
private var centralManager: CBCentralManager?
|
|
159
|
+
private var ringPeripheral: CBPeripheral?
|
|
160
|
+
private var isDisconnecting = false
|
|
161
|
+
|
|
162
|
+
// BLE characteristics
|
|
163
|
+
private var writeChar1: CBCharacteristic?
|
|
164
|
+
private var notifyChar1: CBCharacteristic?
|
|
165
|
+
private var writeChar2: CBCharacteristic?
|
|
166
|
+
private var notifyChar2: CBCharacteristic?
|
|
167
|
+
private var batteryLevelChar: CBCharacteristic?
|
|
168
|
+
private var notifySubscriptionCount = 0
|
|
169
|
+
private var initSequenceRun = false
|
|
170
|
+
private var eusSerial: UInt16 = 0
|
|
171
|
+
|
|
172
|
+
/// Device search
|
|
173
|
+
var DEVICE_SEARCH_ID = "NOT_SET"
|
|
174
|
+
|
|
175
|
+
/// persisted state for ease of reconnection / background connection:
|
|
176
|
+
/// we could store these elsewhere to be like other settings / state, but in practice they will only ever be set and used here
|
|
177
|
+
/// Stored UUID for background reconnection
|
|
178
|
+
private var ringUUID: UUID? {
|
|
179
|
+
get { UserDefaults.standard.string(forKey: "r1_ringUUID").flatMap { UUID(uuidString: $0) } }
|
|
180
|
+
set {
|
|
181
|
+
if let v = newValue {
|
|
182
|
+
UserDefaults.standard.set(v.uuidString, forKey: "r1_ringUUID")
|
|
183
|
+
} else {
|
|
184
|
+
UserDefaults.standard.removeObject(forKey: "r1_ringUUID")
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// maps peripheral.name to 6-byte ring MAC address:
|
|
190
|
+
private var ringMacAddressMap: [String: Data] {
|
|
191
|
+
get {
|
|
192
|
+
UserDefaults.standard.dictionary(forKey: "r1_ringMacAddressMap") as? [String: Data]
|
|
193
|
+
?? [:]
|
|
194
|
+
}
|
|
195
|
+
set { UserDefaults.standard.set(newValue, forKey: "r1_ringMacAddressMap") }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private var ringMacAddress: String? {
|
|
199
|
+
get { UserDefaults.standard.string(forKey: "r1_ringMacAddress") }
|
|
200
|
+
set { UserDefaults.standard.set(newValue, forKey: "r1_ringMacAddress") }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// Reconnection
|
|
204
|
+
private let reconnectionManager = R1ReconnectionManager()
|
|
205
|
+
|
|
206
|
+
/// Battery
|
|
207
|
+
@Published private var _batteryLevel: Int = -1 {
|
|
208
|
+
didSet {
|
|
209
|
+
if _batteryLevel != oldValue && _batteryLevel >= 0 {
|
|
210
|
+
DeviceStore.shared.apply("glasses", "controllerBatteryLevel", _batteryLevel)
|
|
211
|
+
// Bridge.sendBatteryStatus(level: _batteryLevel, charging: isCharging)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private var isCharging = false
|
|
217
|
+
|
|
218
|
+
/// Heartbeat
|
|
219
|
+
private var heartbeatTimer: Timer?
|
|
220
|
+
|
|
221
|
+
static let _bluetoothQueue = DispatchQueue(label: "BluetoothR1", qos: .userInitiated)
|
|
222
|
+
|
|
223
|
+
// MARK: - Init
|
|
224
|
+
|
|
225
|
+
override init() {
|
|
226
|
+
super.init()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
deinit {
|
|
230
|
+
centralManager?.delegate = nil
|
|
231
|
+
ringPeripheral?.delegate = nil
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// MARK: - BLE Scanning
|
|
235
|
+
|
|
236
|
+
@discardableResult
|
|
237
|
+
private func startScan() -> Bool {
|
|
238
|
+
Bridge.log("R1: startScan()")
|
|
239
|
+
if centralManager == nil {
|
|
240
|
+
centralManager = CBCentralManager(
|
|
241
|
+
delegate: self, queue: R1._bluetoothQueue,
|
|
242
|
+
options: [CBCentralManagerOptionShowPowerAlertKey: 0]
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
isDisconnecting = false
|
|
247
|
+
guard centralManager!.state == .poweredOn else {
|
|
248
|
+
Bridge.log("R1: Bluetooth not powered on")
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Try UUID-based reconnection first
|
|
253
|
+
if connectByUUID() {
|
|
254
|
+
return true
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
centralManager!.scanForPeripherals(
|
|
258
|
+
withServices: nil,
|
|
259
|
+
options: [
|
|
260
|
+
CBCentralManagerScanOptionAllowDuplicatesKey: false,
|
|
261
|
+
]
|
|
262
|
+
)
|
|
263
|
+
return true
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
func stopScan() {
|
|
267
|
+
centralManager?.stopScan()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private func connectByUUID() -> Bool {
|
|
271
|
+
// don't do this if we don't have a search id set:
|
|
272
|
+
if DEVICE_SEARCH_ID == "NOT_SET" || DEVICE_SEARCH_ID.isEmpty {
|
|
273
|
+
Bridge.log("R1: 🔵 No DEVICE_SEARCH_ID set, skipping connect by UUID")
|
|
274
|
+
return false
|
|
275
|
+
}
|
|
276
|
+
guard let uuid = ringUUID else { return false }
|
|
277
|
+
guard let ringMac = ringMacAddress else { return false }
|
|
278
|
+
guard let peripheral = centralManager?.retrievePeripherals(withIdentifiers: [uuid]).first
|
|
279
|
+
else { return false }
|
|
280
|
+
|
|
281
|
+
ringPeripheral = peripheral
|
|
282
|
+
peripheral.delegate = self
|
|
283
|
+
centralManager?.connect(peripheral, options: nil)
|
|
284
|
+
Bridge.log("R1: Reconnecting by UUID to \(peripheral.name ?? "ring")")
|
|
285
|
+
return true
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private func matchesNameFilter(_ name: String?) -> Bool {
|
|
289
|
+
guard let name = name else { return false }
|
|
290
|
+
return R1BLE.NAME_FILTERS.contains(where: { name.contains($0) })
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/// Extract a device identifier from the ring name (e.g. "EVEN R1_CEC5BA" -> "CEC5BA")
|
|
294
|
+
private func extractRingId(_ name: String) -> String? {
|
|
295
|
+
if let range = name.range(of: "R1_") {
|
|
296
|
+
let id = String(name[range.upperBound...])
|
|
297
|
+
return id.isEmpty ? nil : id
|
|
298
|
+
}
|
|
299
|
+
return nil
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// MARK: - EUS commands
|
|
303
|
+
|
|
304
|
+
private func nextEusSerial() -> UInt16 {
|
|
305
|
+
eusSerial &+= 1
|
|
306
|
+
if eusSerial == 0 { eusSerial = 1 }
|
|
307
|
+
return eusSerial
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// EUS system commands go to WRITE_CHAR_2 only — channel 2 is the framed
|
|
311
|
+
/// transport; channel 1 speaks the separate legacy protocol.
|
|
312
|
+
private func sendEusCommand(_ subcommand: UInt8, payload: Data, log: String) {
|
|
313
|
+
guard let wc = writeChar2 else {
|
|
314
|
+
Bridge.log("R1: \(log): no channel-2 write characteristic")
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
let packet = R1Eus.frame(
|
|
318
|
+
subcommand: subcommand, status: R1Eus.STATUS_SET,
|
|
319
|
+
serial: nextEusSerial(), payload: payload
|
|
320
|
+
)
|
|
321
|
+
ringPeripheral?.writeValue(packet, for: wc, type: .withoutResponse)
|
|
322
|
+
Bridge.log("R1: \(log) sent (\(packet.count) bytes)")
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// MARK: - Init Sequence
|
|
326
|
+
|
|
327
|
+
/// Runs after notify subscriptions are confirmed: claim the phone role,
|
|
328
|
+
/// then mark the phone link ready.
|
|
329
|
+
private func runInitSequence() {
|
|
330
|
+
guard !initSequenceRun else { return }
|
|
331
|
+
initSequenceRun = true
|
|
332
|
+
|
|
333
|
+
Bridge.log("R1: Running init sequence")
|
|
334
|
+
|
|
335
|
+
guard writeChar2 != nil else {
|
|
336
|
+
Bridge.log("R1: No channel-2 write characteristic, skipping init")
|
|
337
|
+
markConnected()
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
sendEusCommand(R1Eus.SUBCMD_PAIR_AUTH, payload: Data([0x01]), log: "pairAuth")
|
|
342
|
+
|
|
343
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
|
344
|
+
self?.markConnected()
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/// Tells the ring which glasses may connect to it: EUS advStart whose
|
|
349
|
+
/// 12-byte payload is the two glasses arm MACs. The ring stores both
|
|
350
|
+
/// targets, fast-advertises while a glasses role is open, and accepts a
|
|
351
|
+
/// connection from either arm. With only one arm MAC known it is sent for
|
|
352
|
+
/// both targets — the ring matches against either slot.
|
|
353
|
+
private func connectToGlasses() {
|
|
354
|
+
func stored(_ key: String) -> String? {
|
|
355
|
+
guard let value = DeviceStore.shared.get("glasses", key) as? String, !value.isEmpty else { return nil }
|
|
356
|
+
return value
|
|
357
|
+
}
|
|
358
|
+
let left = stored("leftMacAddress")
|
|
359
|
+
?? stored("bluetoothMacAddress")
|
|
360
|
+
?? UserDefaults.standard.string(forKey: "glasses_btMacAddress")
|
|
361
|
+
let right = stored("rightMacAddress")
|
|
362
|
+
?? UserDefaults.standard.string(forKey: "glasses_btMacAddressRight")
|
|
363
|
+
|
|
364
|
+
guard let left, let leftBytes = parseMac(left) else {
|
|
365
|
+
Bridge.log("R1: connectToGlasses: no glasses MAC")
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
let rightBytes = right.flatMap(parseMac) ?? leftBytes
|
|
369
|
+
|
|
370
|
+
// Cache so we can re-send even before the glasses are next scanned.
|
|
371
|
+
UserDefaults.standard.set(left, forKey: "glasses_btMacAddress")
|
|
372
|
+
if let right {
|
|
373
|
+
UserDefaults.standard.set(right, forKey: "glasses_btMacAddressRight")
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
var payload = leftBytes
|
|
377
|
+
payload.append(rightBytes)
|
|
378
|
+
sendEusCommand(R1Eus.SUBCMD_ADV_START, payload: payload, log: "advStart")
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// Parse a MAC string like "AA:BB:CC:DD:EE:FF" or "AABBCCDDEEFF" into 6 raw bytes.
|
|
382
|
+
private func parseMac(_ s: String) -> Data? {
|
|
383
|
+
let cleaned = s.replacingOccurrences(of: ":", with: "")
|
|
384
|
+
.replacingOccurrences(of: "-", with: "")
|
|
385
|
+
guard cleaned.count == 12 else { return nil }
|
|
386
|
+
var out = Data(); out.reserveCapacity(6)
|
|
387
|
+
var idx = cleaned.startIndex
|
|
388
|
+
for _ in 0 ..< 6 {
|
|
389
|
+
let next = cleaned.index(idx, offsetBy: 2)
|
|
390
|
+
guard let byte = UInt8(cleaned[idx ..< next], radix: 16) else { return nil }
|
|
391
|
+
out.append(byte)
|
|
392
|
+
idx = next
|
|
393
|
+
}
|
|
394
|
+
return out
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private func markConnected() {
|
|
398
|
+
Task { await reconnectionManager.stop() }
|
|
399
|
+
Bridge.log("R1: Ring connected")
|
|
400
|
+
|
|
401
|
+
if let name = ringPeripheral?.name, let id = extractRingId(name) {
|
|
402
|
+
DeviceStore.shared.apply("bluetooth", "controller_device_name", id)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
guard let mac = ringMacAddress else {
|
|
406
|
+
Bridge.log("R1: No ring MAC address found")
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
DeviceStore.shared.apply("glasses", "controllerMacAddress", mac)
|
|
410
|
+
|
|
411
|
+
DeviceStore.shared.apply("glasses", "controllerConnected", true)
|
|
412
|
+
// The phone link owns these flags: a ready phone link IS pairing
|
|
413
|
+
// success (fires handleControllerReady). The glasses link is layered
|
|
414
|
+
// on afterwards via advStart + RING_CONNECT_INFO.
|
|
415
|
+
DeviceStore.shared.apply("glasses", "controllerFullyBooted", true)
|
|
416
|
+
|
|
417
|
+
// tell the ring to connect to the glasses if we have it's mac address:
|
|
418
|
+
connectToGlasses()
|
|
419
|
+
|
|
420
|
+
// after a second, connect the glasses to the controller if needed:
|
|
421
|
+
Task {
|
|
422
|
+
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
|
423
|
+
await DeviceManager.shared.sgc?.connectController()
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
startHeartbeat()
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// MARK: - Heartbeat
|
|
430
|
+
|
|
431
|
+
private func startHeartbeat() {
|
|
432
|
+
heartbeatTimer?.invalidate()
|
|
433
|
+
// Simple keepalive: re-read battery every 30 seconds
|
|
434
|
+
heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: true) {
|
|
435
|
+
[weak self] _ in
|
|
436
|
+
DispatchQueue.main.async {
|
|
437
|
+
guard let self = self, DeviceStore.shared.get("glasses", "controllerConnected") as? Bool ?? false else { return }
|
|
438
|
+
// Read battery if we have the standard battery char
|
|
439
|
+
if let char = self.batteryLevelChar {
|
|
440
|
+
self.ringPeripheral?.readValue(for: char)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private func stopHeartbeat() {
|
|
447
|
+
heartbeatTimer?.invalidate()
|
|
448
|
+
heartbeatTimer = nil
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// MARK: - Incoming Data Handling
|
|
452
|
+
|
|
453
|
+
private func handleNotification(from characteristic: CBCharacteristic, data: Data) {
|
|
454
|
+
let hex = data.map { String(format: "%02X", $0) }.joined(separator: " ")
|
|
455
|
+
Bridge.log("R1: \(String(characteristic.uuid.uuidString.suffix(4))) -> \(hex)")
|
|
456
|
+
|
|
457
|
+
let timestamp = Int64(Date().timeIntervalSince1970 * 1000)
|
|
458
|
+
|
|
459
|
+
// Gesture: [0xFF, type, param]
|
|
460
|
+
if data.count >= 3, data[0] == R1BLE.GESTURE_MARKER {
|
|
461
|
+
if let gesture = R1Gesture.parse(from: data) {
|
|
462
|
+
Bridge.log("R1: Gesture: \(gesture.rawValue)")
|
|
463
|
+
Bridge.sendTouchEvent(
|
|
464
|
+
deviceModel: ControllerTypes.R1,
|
|
465
|
+
gestureName: gesture.rawValue,
|
|
466
|
+
timestamp: timestamp
|
|
467
|
+
)
|
|
468
|
+
} else {
|
|
469
|
+
Bridge.log(
|
|
470
|
+
"R1: Unknown gesture type=0x\(String(format: "%02X", data[1])) param=0x\(String(format: "%02X", data[2]))"
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
return
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Battery: 2 bytes, first byte is percentage
|
|
477
|
+
if data.count == 2, data[0] <= 100 {
|
|
478
|
+
_batteryLevel = Int(data[0])
|
|
479
|
+
Bridge.log("R1: Battery: \(data[0])%")
|
|
480
|
+
return
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// State: single byte (0x01=ready, 0x00=menu)
|
|
484
|
+
if data.count == 1 {
|
|
485
|
+
let state = data[0] == 0x01 ? "ready" : data[0] == 0x00 ? "menu" : "unknown(\(data[0]))"
|
|
486
|
+
Bridge.log("R1: State: \(state)")
|
|
487
|
+
return
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Longer data: check for embedded gesture marker
|
|
491
|
+
if data.count > 3, let ffIndex = data.firstIndex(of: R1BLE.GESTURE_MARKER),
|
|
492
|
+
ffIndex + 2 < data.count
|
|
493
|
+
{
|
|
494
|
+
let gestureData = Data(data[ffIndex ... ffIndex + 2])
|
|
495
|
+
if let gesture = R1Gesture.parse(from: gestureData) {
|
|
496
|
+
Bridge.log("R1: Embedded gesture: \(gesture.rawValue)")
|
|
497
|
+
Bridge.sendTouchEvent(
|
|
498
|
+
deviceModel: ControllerTypes.R1,
|
|
499
|
+
gestureName: gesture.rawValue,
|
|
500
|
+
timestamp: timestamp
|
|
501
|
+
)
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// MARK: - Connection State Reset
|
|
508
|
+
|
|
509
|
+
private func resetConnectionState() {
|
|
510
|
+
ringPeripheral = nil
|
|
511
|
+
writeChar1 = nil
|
|
512
|
+
notifyChar1 = nil
|
|
513
|
+
writeChar2 = nil
|
|
514
|
+
notifyChar2 = nil
|
|
515
|
+
batteryLevelChar = nil
|
|
516
|
+
notifySubscriptionCount = 0
|
|
517
|
+
initSequenceRun = false
|
|
518
|
+
// ringMacAddress/ringUUID persist across disconnects so reconnection
|
|
519
|
+
// can find the ring again; only forget() clears them.
|
|
520
|
+
DeviceStore.shared.apply("glasses", "controllerConnected", false)
|
|
521
|
+
DeviceStore.shared.apply("glasses", "controllerFullyBooted", false)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// MARK: - Reconnection
|
|
525
|
+
|
|
526
|
+
private func startReconnectionTimer() {
|
|
527
|
+
Task {
|
|
528
|
+
await reconnectionManager.start { [weak self] in
|
|
529
|
+
guard let self else { return false }
|
|
530
|
+
if await MainActor.run(body: { DeviceStore.shared.get("glasses", "controllerConnected") as? Bool ?? false }) {
|
|
531
|
+
return true // already connected
|
|
532
|
+
}
|
|
533
|
+
Bridge.log("R1: Attempting reconnection...")
|
|
534
|
+
await MainActor.run { self.startScan() }
|
|
535
|
+
return false // keep trying
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// MARK: - ControllerManager Protocol
|
|
541
|
+
|
|
542
|
+
func findCompatibleDevices() {
|
|
543
|
+
Bridge.log("R1: findCompatibleDevices()")
|
|
544
|
+
DEVICE_SEARCH_ID = "NOT_SET"
|
|
545
|
+
startScan()
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
func connectById(_ id: String) {
|
|
549
|
+
Bridge.log("R1: connectById(\(id))")
|
|
550
|
+
DEVICE_SEARCH_ID = id
|
|
551
|
+
startScan()
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
func disconnect() {
|
|
555
|
+
Bridge.log("R1: disconnect()")
|
|
556
|
+
isDisconnecting = true
|
|
557
|
+
stopHeartbeat()
|
|
558
|
+
Task { await reconnectionManager.stop() }
|
|
559
|
+
|
|
560
|
+
if let peripheral = ringPeripheral {
|
|
561
|
+
centralManager?.cancelPeripheralConnection(peripheral)
|
|
562
|
+
}
|
|
563
|
+
resetConnectionState()
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
func forget() {
|
|
567
|
+
disconnect()
|
|
568
|
+
ringUUID = nil
|
|
569
|
+
ringMacAddress = nil
|
|
570
|
+
DEVICE_SEARCH_ID = "NOT_SET"
|
|
571
|
+
centralManager?.delegate = nil
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
func cleanup() {
|
|
575
|
+
disconnect()
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
func getConnectedBluetoothName() -> String? {
|
|
579
|
+
return ringPeripheral?.name
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
func ping() {
|
|
583
|
+
if let char = batteryLevelChar {
|
|
584
|
+
ringPeripheral?.readValue(for: char)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
func getBatteryStatus() {
|
|
589
|
+
if let char = batteryLevelChar {
|
|
590
|
+
ringPeripheral?.readValue(for: char)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// MARK: - No-op implementations (ring has no display/camera/wifi/mic)
|
|
595
|
+
|
|
596
|
+
func sendIncidentId(_: String, apiBaseUrl _: String?) {}
|
|
597
|
+
func setMicEnabled(_: Bool) {}
|
|
598
|
+
func sortMicRanking(list: [String]) -> [String] {
|
|
599
|
+
return list
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
func sendJson(_: [String: Any], wakeUp _: Bool, requireAck _: Bool) {}
|
|
603
|
+
func requestPhoto(_: PhotoRequest) {}
|
|
604
|
+
func startVideoRecording(requestId _: String, save _: Bool, sound _: Bool) {}
|
|
605
|
+
func stopVideoRecording(requestId _: String) {}
|
|
606
|
+
func startStream(_: [String: Any]) {}
|
|
607
|
+
func stopStream() {}
|
|
608
|
+
func sendStreamKeepAlive(_: [String: Any]) {}
|
|
609
|
+
func sendButtonPhotoSettings() {}
|
|
610
|
+
func sendButtonVideoRecordingSettings() {}
|
|
611
|
+
func sendButtonMaxRecordingTime() {}
|
|
612
|
+
func setBrightness(_: Int, autoMode _: Bool) {}
|
|
613
|
+
func clearDisplay() {}
|
|
614
|
+
func sendTextWall(_: String) {}
|
|
615
|
+
func sendDoubleTextWall(_: String, _: String) {}
|
|
616
|
+
func displayBitmap(base64ImageData _: String, x _: Int32? = nil, y _: Int32? = nil, width _: Int32? = nil, height _: Int32? = nil) async -> Bool {
|
|
617
|
+
return false
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
func showDashboard() {}
|
|
621
|
+
func setDashboardPosition(_: Int, _: Int) {}
|
|
622
|
+
func setHeadUpAngle(_: Int) {}
|
|
623
|
+
func setSilentMode(_: Bool) {}
|
|
624
|
+
func exit() {}
|
|
625
|
+
func sendShutdown() {
|
|
626
|
+
disconnect()
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
func sendReboot() {}
|
|
630
|
+
func sendRgbLedControl(
|
|
631
|
+
requestId _: String, packageName _: String?, action _: String, color _: String?, onDurationMs _: Int,
|
|
632
|
+
offDurationMs _: Int, count _: Int
|
|
633
|
+
) {}
|
|
634
|
+
func requestWifiScan() {}
|
|
635
|
+
func sendWifiCredentials(_: String, _: String) {}
|
|
636
|
+
func forgetWifiNetwork(_: String) {}
|
|
637
|
+
func sendHotspotState(_: Bool) {}
|
|
638
|
+
func sendOtaStart(otaVersionUrl: String?) {}
|
|
639
|
+
func sendOtaQueryStatus() {}
|
|
640
|
+
func sendUserEmailToGlasses(_: String) {}
|
|
641
|
+
func queryGalleryStatus() {}
|
|
642
|
+
func sendGalleryMode() {}
|
|
643
|
+
func requestVersionInfo() {}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// MARK: - CBCentralManagerDelegate
|
|
647
|
+
|
|
648
|
+
extension R1: CBCentralManagerDelegate {
|
|
649
|
+
nonisolated func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
650
|
+
let state = central.state
|
|
651
|
+
DispatchQueue.main.async { [weak self] in
|
|
652
|
+
guard let self = self else { return }
|
|
653
|
+
Bridge.log("R1: Bluetooth state: \(state.rawValue)")
|
|
654
|
+
if state == .poweredOn {
|
|
655
|
+
_ = self.startScan()
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
nonisolated func centralManager(
|
|
661
|
+
_ central: CBCentralManager,
|
|
662
|
+
didDiscover peripheral: CBPeripheral,
|
|
663
|
+
advertisementData: [String: Any],
|
|
664
|
+
rssi RSSI: NSNumber
|
|
665
|
+
) {
|
|
666
|
+
let name = peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String
|
|
667
|
+
let mfgData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
|
668
|
+
|
|
669
|
+
DispatchQueue.main.async { [weak self] in
|
|
670
|
+
guard let self = self else { return }
|
|
671
|
+
guard self.matchesNameFilter(name) else { return }
|
|
672
|
+
|
|
673
|
+
Bridge.log(
|
|
674
|
+
"R1: Discovered: \(name ?? "?") (RSSI: \(RSSI)) mfgData: \(mfgData?.map { String(format: "%02X", $0) }.joined(separator: " ") ?? "none")"
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
// Extract ring MAC from manufacturer data if available and store to a map name:mac
|
|
678
|
+
if let mfgData = mfgData {
|
|
679
|
+
Bridge.log(
|
|
680
|
+
"R1: mfgData: \(mfgData.map { String(format: "%02X", $0) }.joined(separator: " "))"
|
|
681
|
+
)
|
|
682
|
+
if mfgData.count >= 6 {
|
|
683
|
+
self.ringMacAddressMap[name ?? ""] = Data(mfgData.suffix(6))
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Emit discovered device
|
|
688
|
+
if let name = name, let id = self.extractRingId(name) {
|
|
689
|
+
Bridge.sendDiscoveredDevice(ControllerTypes.R1, id)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// If scan-only mode, don't auto-connect
|
|
693
|
+
guard self.DEVICE_SEARCH_ID != "NOT_SET" else { return }
|
|
694
|
+
|
|
695
|
+
// If search ID is specific, check it matches the ring name/id
|
|
696
|
+
if let name = name, let id = self.extractRingId(name),
|
|
697
|
+
self.DEVICE_SEARCH_ID != id && !name.contains(self.DEVICE_SEARCH_ID)
|
|
698
|
+
{
|
|
699
|
+
return
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if self.ringPeripheral == nil {
|
|
703
|
+
self.ringPeripheral = peripheral
|
|
704
|
+
peripheral.delegate = self
|
|
705
|
+
central.connect(peripheral, options: nil)
|
|
706
|
+
self.stopScan()
|
|
707
|
+
Bridge.log("R1: Connecting to \(name ?? "ring")")
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
nonisolated func centralManager(
|
|
713
|
+
_: CBCentralManager, didConnect peripheral: CBPeripheral
|
|
714
|
+
) {
|
|
715
|
+
DispatchQueue.main.async { [weak self] in
|
|
716
|
+
guard let self = self else { return }
|
|
717
|
+
Bridge.log("R1: Connected to \(peripheral.name ?? "ring")")
|
|
718
|
+
|
|
719
|
+
self.ringUUID = peripheral.identifier
|
|
720
|
+
|
|
721
|
+
guard let name = peripheral.name, let mac = self.ringMacAddressMap[name] else {
|
|
722
|
+
Bridge.log("R1: No MAC stored in map found for \(peripheral.name ?? "ring")")
|
|
723
|
+
// stop the scan, disconnect, remove the uuid, and try again as we need the mac address to connect:
|
|
724
|
+
self.disconnect()
|
|
725
|
+
self.ringUUID = nil
|
|
726
|
+
// we are still searching!:
|
|
727
|
+
DeviceStore.shared.apply("glasses", "controllerConnected", false)
|
|
728
|
+
DeviceStore.shared.apply("glasses", "controllerFullyBooted", false)
|
|
729
|
+
DeviceStore.shared.apply("glasses", "controllerSearching", true)
|
|
730
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
|
731
|
+
self.startScan()
|
|
732
|
+
}
|
|
733
|
+
return
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
self.ringMacAddress = mac.map { String(format: "%02X", $0) }.joined(separator: ":")
|
|
737
|
+
|
|
738
|
+
// Discover all services
|
|
739
|
+
peripheral.discoverServices(nil)
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
nonisolated func centralManager(
|
|
744
|
+
_: CBCentralManager, didFailToConnect _: CBPeripheral, error: Error?
|
|
745
|
+
) {
|
|
746
|
+
DispatchQueue.main.async { [weak self] in
|
|
747
|
+
guard let self = self else { return }
|
|
748
|
+
Bridge.log("R1: Failed to connect: \(error?.localizedDescription ?? "unknown")")
|
|
749
|
+
self.resetConnectionState()
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
nonisolated func centralManager(
|
|
754
|
+
_: CBCentralManager, didDisconnectPeripheral _: CBPeripheral, error: Error?
|
|
755
|
+
) {
|
|
756
|
+
DispatchQueue.main.async { [weak self] in
|
|
757
|
+
guard let self = self else { return }
|
|
758
|
+
Bridge.log("R1: Disconnected: \(error?.localizedDescription ?? "clean")")
|
|
759
|
+
|
|
760
|
+
self.stopHeartbeat()
|
|
761
|
+
|
|
762
|
+
if self.isDisconnecting { return }
|
|
763
|
+
|
|
764
|
+
self.resetConnectionState()
|
|
765
|
+
self.startReconnectionTimer()
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// MARK: - CBPeripheralDelegate
|
|
771
|
+
|
|
772
|
+
extension R1: CBPeripheralDelegate {
|
|
773
|
+
nonisolated func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) {
|
|
774
|
+
guard let services = peripheral.services else { return }
|
|
775
|
+
for service in services {
|
|
776
|
+
// Discover ALL characteristics on every service
|
|
777
|
+
peripheral.discoverCharacteristics(nil, for: service)
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
nonisolated func peripheral(
|
|
782
|
+
_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error _: Error?
|
|
783
|
+
) {
|
|
784
|
+
guard let characteristics = service.characteristics else { return }
|
|
785
|
+
|
|
786
|
+
DispatchQueue.main.async { [weak self] in
|
|
787
|
+
guard let self = self else { return }
|
|
788
|
+
|
|
789
|
+
for char in characteristics {
|
|
790
|
+
Bridge.log("R1: char discovered: \(char.uuid)")
|
|
791
|
+
let props = char.properties
|
|
792
|
+
var propStr: [String] = []
|
|
793
|
+
if props.contains(.read) { propStr.append("read") }
|
|
794
|
+
if props.contains(.write) { propStr.append("write") }
|
|
795
|
+
if props.contains(.writeWithoutResponse) { propStr.append("writeNoResp") }
|
|
796
|
+
if props.contains(.notify) { propStr.append("notify") }
|
|
797
|
+
if props.contains(.indicate) { propStr.append("indicate") }
|
|
798
|
+
Bridge.log("R1: char \(char.uuid) props=[\(propStr.joined(separator: ","))]")
|
|
799
|
+
|
|
800
|
+
// Store known characteristics
|
|
801
|
+
switch char.uuid {
|
|
802
|
+
case R1BLE.WRITE_CHAR_1:
|
|
803
|
+
self.writeChar1 = char
|
|
804
|
+
case R1BLE.NOTIFY_CHAR_1:
|
|
805
|
+
self.notifyChar1 = char
|
|
806
|
+
case R1BLE.WRITE_CHAR_2:
|
|
807
|
+
self.writeChar2 = char
|
|
808
|
+
case R1BLE.NOTIFY_CHAR_2:
|
|
809
|
+
self.notifyChar2 = char
|
|
810
|
+
case R1BLE.BATTERY_LEVEL_CHAR:
|
|
811
|
+
self.batteryLevelChar = char
|
|
812
|
+
default:
|
|
813
|
+
break
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Subscribe to any notify/indicate characteristic
|
|
817
|
+
if props.contains(.notify) || props.contains(.indicate) {
|
|
818
|
+
peripheral.setNotifyValue(true, for: char)
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Read any readable characteristic (triggers pairing if encrypted)
|
|
822
|
+
if props.contains(.read) {
|
|
823
|
+
peripheral.readValue(for: char)
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
nonisolated func peripheral(
|
|
830
|
+
_: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic,
|
|
831
|
+
error: Error?
|
|
832
|
+
) {
|
|
833
|
+
Bridge.log("R1: didUpdateNotificationStateFor: \(characteristic.uuid)")
|
|
834
|
+
DispatchQueue.main.async { [weak self] in
|
|
835
|
+
guard let self = self else { return }
|
|
836
|
+
|
|
837
|
+
if let error = error {
|
|
838
|
+
Bridge.log(
|
|
839
|
+
"R1: Notify error on \(characteristic.uuid): \(error.localizedDescription)"
|
|
840
|
+
)
|
|
841
|
+
return
|
|
842
|
+
}
|
|
843
|
+
Bridge.log("R1: Notify enabled on \(characteristic.uuid)")
|
|
844
|
+
|
|
845
|
+
self.notifySubscriptionCount += 1
|
|
846
|
+
|
|
847
|
+
// Run init after subscribing to at least 2 notify characteristics
|
|
848
|
+
if self.notifySubscriptionCount >= 2 && !self.initSequenceRun {
|
|
849
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
|
850
|
+
self?.runInitSequence()
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
nonisolated func peripheral(
|
|
857
|
+
_: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
|
|
858
|
+
error: Error?
|
|
859
|
+
) {
|
|
860
|
+
Bridge.log("R1: didUpdateValueFor1: \(characteristic.uuid)")
|
|
861
|
+
guard let data = characteristic.value, !data.isEmpty, error == nil else { return }
|
|
862
|
+
Bridge.log("R1: didUpdateValueFor: \(characteristic.uuid) data: \(data.hexEncodedString())")
|
|
863
|
+
|
|
864
|
+
DispatchQueue.main.async { [weak self] in
|
|
865
|
+
guard let self = self else { return }
|
|
866
|
+
|
|
867
|
+
// Standard battery service
|
|
868
|
+
if characteristic.uuid == R1BLE.BATTERY_LEVEL_CHAR {
|
|
869
|
+
self._batteryLevel = Int(data[0])
|
|
870
|
+
Bridge.log("R1: Battery (std): \(data[0])%")
|
|
871
|
+
return
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
self.handleNotification(from: characteristic, data: data)
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
nonisolated func peripheral(
|
|
879
|
+
_: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?
|
|
880
|
+
) {
|
|
881
|
+
Bridge.log("R1: didWriteValueFor: \(characteristic.uuid)")
|
|
882
|
+
if let error = error {
|
|
883
|
+
DispatchQueue.main.async {
|
|
884
|
+
Bridge.log(
|
|
885
|
+
"R1: Write error on \(characteristic.uuid): \(error.localizedDescription)"
|
|
886
|
+
)
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// MARK: - R1 Reconnection Manager
|
|
893
|
+
|
|
894
|
+
actor R1ReconnectionManager {
|
|
895
|
+
private var task: Task<Void, Never>?
|
|
896
|
+
private let intervalSeconds: TimeInterval
|
|
897
|
+
private var attempts = 0
|
|
898
|
+
private let maxAttempts: Int
|
|
899
|
+
|
|
900
|
+
init(intervalSeconds: TimeInterval = 30, maxAttempts: Int = -1) {
|
|
901
|
+
self.intervalSeconds = intervalSeconds
|
|
902
|
+
self.maxAttempts = maxAttempts
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
func start(onAttempt: @escaping @Sendable () async -> Bool) {
|
|
906
|
+
stop()
|
|
907
|
+
attempts = 0
|
|
908
|
+
task = Task {
|
|
909
|
+
while !Task.isCancelled {
|
|
910
|
+
try? await Task.sleep(nanoseconds: UInt64(intervalSeconds * 1_000_000_000))
|
|
911
|
+
if Task.isCancelled { break }
|
|
912
|
+
attempts += 1
|
|
913
|
+
if maxAttempts > 0, attempts > maxAttempts { break }
|
|
914
|
+
let done = await onAttempt()
|
|
915
|
+
if done { break }
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
func stop() {
|
|
921
|
+
task?.cancel()
|
|
922
|
+
task = nil
|
|
923
|
+
attempts = 0
|
|
924
|
+
}
|
|
925
|
+
}
|