diff --git a/.github/workflows/run_android.yml b/.github/workflows/run_android.yml index 9945618ec..fa61cae75 100644 --- a/.github/workflows/run_android.yml +++ b/.github/workflows/run_android.yml @@ -14,10 +14,15 @@ jobs: env: ANDROID_NDK_VERSION: 25.1.8937393 + ANDROID_API_LEVEL: android-31 steps: - name: Clone Repository uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11.0.3 - name: Install Ninja run: | sudo -H apt-get update -y @@ -30,7 +35,7 @@ jobs: cmakeVersion: '3.21.4' - name: Install NDK run: | - echo "y" | sudo -H ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;${ANDROID_NDK_VERSION}" --sdk_root=${ANDROID_SDK_ROOT} + echo "y" | sudo -H ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;${ANDROID_NDK_VERSION}" "platforms;${ANDROID_API_LEVEL}" --sdk_root=${ANDROID_SDK_ROOT} env: DEBIAN_FRONTEND: noninteractive # build for all ABIs @@ -47,7 +52,7 @@ jobs: for ABI in arm64-v8a armeabi-v7a x86 x86_64; do mkdir $GITHUB_WORKSPACE/libftdi/build-$ABI cd $GITHUB_WORKSPACE/libftdi/build-$ABI - cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake -DANDROID_ABI=$ABI -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=android-24 -DEXAMPLES=OFF -DFTDI_EEPROM=OFF -DLIBUSB_LIBRARIES=$GITHUB_WORKSPACE/libusb/android/libs/$ABI/libusb1.0.so -DLIBUSB_INCLUDE_DIR=$GITHUB_WORKSPACE/libusb/libusb -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-$ABI .. + cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake -DANDROID_ABI=$ABI -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=${ANDROID_API_LEVEL} -DEXAMPLES=OFF -DFTDI_EEPROM=OFF -DLIBUSB_LIBRARIES=$GITHUB_WORKSPACE/libusb/android/libs/$ABI/libusb1.0.so -DLIBUSB_INCLUDE_DIR=$GITHUB_WORKSPACE/libusb/libusb -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-$ABI .. ninja ninja install done @@ -56,20 +61,16 @@ jobs: for ABI in arm64-v8a armeabi-v7a x86 x86_64; do mkdir $GITHUB_WORKSPACE/build-$ABI cd $GITHUB_WORKSPACE/build-$ABI - cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake -DANDROID_ABI=$ABI -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=android-24 -DCMAKE_FIND_ROOT_PATH=$GITHUB_WORKSPACE/install-$ABI -DUSE_LIBFTDI=ON .. + cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake -DANDROID_ABI=$ABI -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=${ANDROID_API_LEVEL} -DCMAKE_FIND_ROOT_PATH=$GITHUB_WORKSPACE/install-$ABI -DUSE_LIBFTDI=ON .. ninja done - - name: Prepare Zip - run: | - cd $GITHUB_WORKSPACE/tools - zip -r jniLibs.zip jniLibs + - name: Build Android AAR + run: python3 $GITHUB_WORKSPACE/tools/build_android_aar.py --android-sdk-root ${ANDROID_SDK_ROOT} --android-api-level ${ANDROID_API_LEVEL} --output $GITHUB_WORKSPACE/tools/brainflow-android.aar - name: Install AWS CLI run: sudo -H python3 -m pip install awscli==1.35.24 - name: Upload To AWS if: ${{ github.event_name == 'push' && github.repository == 'brainflow-dev/brainflow' }} - run: | - cd $GITHUB_WORKSPACE/tools - aws s3 cp jniLibs.zip s3://brainflow/$GITHUB_SHA/ + run: aws s3 cp $GITHUB_WORKSPACE/tools/brainflow-android.aar s3://brainflow/$GITHUB_SHA/brainflow-android.aar env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.gitignore b/.gitignore index 728e34fb5..8a2beb362 100644 --- a/.gitignore +++ b/.gitignore @@ -367,6 +367,11 @@ rust_package/brainflow/lib/ rust_package/brainflow/inc/ src/ml/train/data/ src/ml/train/data/*.onnx +tools/brainflow-android.aar +build_android_aar/ +tools/simpleble-bridge.jar +tools/simpleble-bridge-classes/ +tools/simpleble-bridge-sources.txt # CMake & GNU Make CMakeCache.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e4807f7b..358a2f5ae 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,10 @@ option (BUILD_ONNX "BUILD_ONNX" OFF) option (BUILD_TESTS "BUILD_TESTS" OFF) option (BUILD_PERIPHERY "BUILD_PERIPHERY" OFF) +if (ANDROID) + set (BUILD_BLE ON CACHE BOOL "BUILD_BLE" FORCE) +endif (ANDROID) + include (${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros.cmake) configure_msvc_runtime () @@ -76,4 +80,4 @@ install ( EXPORT ${TARGETS_EXPORT_NAME} NAMESPACE brainflow:: DESTINATION ${CONFIG_INSTALL_DIR} -) \ No newline at end of file +) diff --git a/csharp_package/brainflow/brainflow/board_controller_library.cs b/csharp_package/brainflow/brainflow/board_controller_library.cs index 9f4257f3b..7f87b8ce9 100644 --- a/csharp_package/brainflow/brainflow/board_controller_library.cs +++ b/csharp_package/brainflow/brainflow/board_controller_library.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace brainflow @@ -123,7 +123,7 @@ public enum BoardIds BIOLISTENER_BOARD = 64, IRONBCI_32_BOARD = 65, NEUROPAWN_KNIGHT_BOARD_IMU = 66, - MUSE_S_ANTHENA_BOARD = 67 + MUSE_S_ATHENA_BOARD = 67 }; diff --git a/docs/BuildBrainFlow.rst b/docs/BuildBrainFlow.rst index c7ca532cb..2832c68f3 100644 --- a/docs/BuildBrainFlow.rst +++ b/docs/BuildBrainFlow.rst @@ -253,29 +253,38 @@ To check supported boards for Android visit :ref:`supported-boards-label` Installation instructions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Create Java project in Android Studio, Kotlin is not supported -- Download *jniLibs.zip* from `Release page `_ -- Unpack *jniLibs.zip* and copy it's content to *project/app/src/main/jniLibs* -- Download *brainflow-jar-with-dependencies.jar* from `Release page `_ or from `Github package `_ -- Copy *brainflow-jar-with-dependencies.jar* to *project/app/libs folder* +- Create an Android project in Android Studio +- Download *brainflow-android.aar* from `Release page `_ +- Copy *brainflow-android.aar* to *project/app/libs* +- Add it to your app dependencies :: + + dependencies { + implementation files('libs/brainflow-android.aar') + } Now you can use BrainFlow SDK in your Android application! Note: Android Studio inline compiler may show red errors but it should be compiled fine with Gradle. To fix inline compiler you can use *File > Sync Project with Gradle Files* or click at *File > Invalidate Cache/Restart > Invalidate and Restart* -Prebuild libraries for *jniLibs.zip* are complied using: +Prebuilt libraries inside *brainflow-android.aar* are compiled using: - Android NDK 25.1.8937393 -- *-DANDROID_NATIVE_API_LEVEL=android-24* +- *-DANDROID_NATIVE_API_LEVEL=android-31* + +Prebuilt Android libraries require API 31 or newer. BLE support is always enabled for Android builds and the SimpleBLE bridge is packaged into the AAR. .. compound:: - For some API calls you need to provide additional permissions via manifest file of your application :: + The AAR declares Bluetooth permissions via its manifest, but Android 12+ still requires runtime approval for :code:`BLUETOOTH_SCAN` and :code:`BLUETOOTH_CONNECT` before creating a BrainFlow BLE board. For network or storage boards, or if you do not use manifest merging, add the required permissions to your application manifest manually :: + + + + Compilation using Android NDK @@ -293,6 +302,7 @@ Compilation instructions: - You can also try *MinGW Makefiles* instead *Ninja*, but it's not tested and may not work - Build C++ code using cmake and *Ninja* for **all ABIs** - Compiled libraries will be in *tools/jniLibs* folder +- Build the AAR using *tools/build_android_aar.py* .. compound:: @@ -300,13 +310,19 @@ Compilation instructions: # to prepare project(choose ABIs which you need) # for arm64-v8a - cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-24 -DANDROID_ABI=arm64-v8a .. + cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-31 -DANDROID_ABI=arm64-v8a .. # for armeabi-v7a - cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-24 -DANDROID_ABI=armeabi-v7a .. + cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-31 -DANDROID_ABI=armeabi-v7a .. # for x86_64 - cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-24 -DANDROID_ABI=x86_64 .. + cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-31 -DANDROID_ABI=x86_64 .. # for x86 - cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-24 -DANDROID_ABI=x86 .. + cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=D:\workspace\android-ndk-r25b\build\cmake\android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-31 -DANDROID_ABI=x86 .. # to build(should be run for each ABI from previous step** cmake --build . --target install --config Release -j 2 --parallel 2 + + # build Java wrapper, SimpleBLE bridge jar, and package single Android artifact + python tools/build_android_aar.py + + # or build native libraries and package a local AAR in one command + python tools/build_android_aar.py --build-native --abis arm64-v8a --allow-missing-abis diff --git a/docs/SupportedBoards.rst b/docs/SupportedBoards.rst index d4959973a..368e72754 100644 --- a/docs/SupportedBoards.rst +++ b/docs/SupportedBoards.rst @@ -751,7 +751,7 @@ Supported platforms: Muse ------ -Muse startup commands can be selected with :code:`BrainFlowInputParams.other_info`. Use a shorthand such as :code:`p21` or a key-value form such as :code:`preset=p21`. If :code:`other_info` is empty, BrainFlow uses the default command listed in the device section. :code:`low_latency` is only supported for MuseS Anthena. +Muse startup commands can be selected with :code:`BrainFlowInputParams.other_info`. Use a shorthand such as :code:`p21` or a key-value form such as :code:`preset=p21`. If :code:`other_info` is empty, BrainFlow uses the default command listed in the device section. :code:`low_latency` is only supported for MuseS Athena. Muse S @@ -821,7 +821,7 @@ Available :ref:`presets-label`: - :code:`BrainFlowPresets.ANCILLARY_PRESET`, it contains PPG data, to enable it use :code:`board.config_board("p61")` -MuseS Anthena +MuseS Athena ~~~~~~~~~~~~~~ .. image:: https://live.staticflickr.com/65535/55236436914_6e442f3192.jpg @@ -840,10 +840,10 @@ MuseS Anthena To create such board you need to specify the following board ID and fields of BrainFlowInputParams object: -- :code:`BoardIds.MUSE_S_ANTHENA_BOARD` +- :code:`BoardIds.MUSE_S_ATHENA_BOARD` - *optional:* :code:`mac_address`, mac address of the device to connect - *optional:* :code:`serial_number`, device name, can be printed on the Muse device or discovered via mobile apps -- *optional:* :code:`other_info`, MuseS Anthena startup options +- *optional:* :code:`other_info`, MuseS Athena startup options Initialization Example: @@ -851,7 +851,7 @@ Initialization Example: params = BrainFlowInputParams() params.other_info = "preset=p1041;low_latency=true" - board = BoardShim(BoardIds.MUSE_S_ANTHENA_BOARD, params) + board = BoardShim(BoardIds.MUSE_S_ATHENA_BOARD, params) Supported platforms: @@ -917,7 +917,7 @@ Available :ref:`presets-label`: - :code:`BrainFlowPresets.DEFAULT_PRESET`, it contains EEG data, sampling rate is 256 Hz. For 4-channel Muse presets BrainFlow exposes :code:`TP9`, :code:`AF7`, :code:`AF8`, and :code:`TP10` as EEG channels. For 8-channel Muse presets the additional Muse EEG values are exposed as other channels. - :code:`BrainFlowPresets.AUXILIARY_PRESET`, it contains Accelerometer and Gyro data, sampling rate is 52 Hz. -- :code:`BrainFlowPresets.ANCILLARY_PRESET`, it contains optics and battery data. Optics sampling rate is 64 Hz. MuseS Anthena uses optics data for PPG, and BrainFlow exposes this data as optical channels instead of PPG channels. Depending on selected Muse preset, the stream contains 4, 8, or 16 optical channels. +- :code:`BrainFlowPresets.ANCILLARY_PRESET`, it contains optics and battery data. Optics sampling rate is 64 Hz. MuseS Athena uses optics data for PPG, and BrainFlow exposes this data as optical channels instead of PPG channels. Depending on selected Muse preset, the stream contains 4, 8, or 16 optical channels. Muse 2 diff --git a/java_package/brainflow/pom.xml b/java_package/brainflow/pom.xml index 7096e629e..29499ccdc 100644 --- a/java_package/brainflow/pom.xml +++ b/java_package/brainflow/pom.xml @@ -7,6 +7,11 @@ java binding for brainflow library https://brainflow.org/ + + 11 + 11 + + MIT @@ -101,8 +106,8 @@ maven-compiler-plugin 3.8.0 - 11 - 11 + ${maven.compiler.source} + ${maven.compiler.target} @@ -145,6 +150,26 @@ + + + + android + + + + org.apache.maven.plugins + maven-compiler-plugin + + + module-info.java + + + + + + + + net.java.dev.jna diff --git a/java_package/brainflow/src/main/java/brainflow/BoardIds.java b/java_package/brainflow/src/main/java/brainflow/BoardIds.java index e5de61fdc..bd3d3df2a 100644 --- a/java_package/brainflow/src/main/java/brainflow/BoardIds.java +++ b/java_package/brainflow/src/main/java/brainflow/BoardIds.java @@ -73,7 +73,7 @@ public enum BoardIds BIOLISTENER_BOARD(64), IRONBCI_32_BOARD(65), NEUROPAWN_KNIGHT_BOARD_IMU(66), - MUSE_S_ANTHENA_BOARD(67); + MUSE_S_ATHENA_BOARD(67); private final int board_id; private static final Map bi_map = new HashMap (); diff --git a/java_package/brainflow/src/main/java/brainflow/BoardShim.java b/java_package/brainflow/src/main/java/brainflow/BoardShim.java index c6b059ad9..3ac97fd14 100644 --- a/java_package/brainflow/src/main/java/brainflow/BoardShim.java +++ b/java_package/brainflow/src/main/java/brainflow/BoardShim.java @@ -159,8 +159,14 @@ int get_current_board_data (int num_samples, int preset, double[] data_buf, int[ if (is_os_android) { - // for android you need to put these files manually to jniLibs folder, unpacking - // doesnt work + // Android native libraries are loaded from the app or BrainFlow AAR. + try + { + System.loadLibrary ("simpleble-c"); + } catch (UnsatisfiedLinkError e) + { + // Android packages built without BLE do not ship SimpleBLE. + } lib_name = "BoardController"; // no lib prefix and no extension for android } else { diff --git a/java_package/brainflow/src/main/java/brainflow/MLModel.java b/java_package/brainflow/src/main/java/brainflow/MLModel.java index dd43212cc..f34a3fea1 100644 --- a/java_package/brainflow/src/main/java/brainflow/MLModel.java +++ b/java_package/brainflow/src/main/java/brainflow/MLModel.java @@ -74,8 +74,7 @@ private interface DllInterface extends Library if (is_os_android) { - // for android you need to put these files manually to jniLibs folder, unpacking - // doesnt work + // Android native libraries are loaded from the app or BrainFlow AAR. lib_name = "MLModule"; // no lib prefix and no extension for android } else { diff --git a/julia_package/brainflow/src/board_shim.jl b/julia_package/brainflow/src/board_shim.jl index c0e3d43f8..0583a1629 100644 --- a/julia_package/brainflow/src/board_shim.jl +++ b/julia_package/brainflow/src/board_shim.jl @@ -68,7 +68,7 @@ export BrainFlowInputParams BIOLISTENER_BOARD = 64 IRONBCI_32_BOARD = 65 NEUROPAWN_KNIGHT_BOARD_IMU = 66 - MUSE_S_ANTHENA_BOARD = 67 + MUSE_S_ATHENA_BOARD = 67 end diff --git a/matlab_package/brainflow/BoardIds.m b/matlab_package/brainflow/BoardIds.m index 79c3988c4..9b5db2d2f 100644 --- a/matlab_package/brainflow/BoardIds.m +++ b/matlab_package/brainflow/BoardIds.m @@ -66,6 +66,6 @@ BIOLISTENER_BOARD(64) IRONBCI_32_BOARD(65) NEUROPAWN_KNIGHT_BOARD_IMU(66) - MUSE_S_ANTHENA_BOARD(67) + MUSE_S_ATHENA_BOARD(67) end end diff --git a/nodejs_package/brainflow/brainflow.types.ts b/nodejs_package/brainflow/brainflow.types.ts index e41c3e275..7eb1cf54f 100644 --- a/nodejs_package/brainflow/brainflow.types.ts +++ b/nodejs_package/brainflow/brainflow.types.ts @@ -76,7 +76,7 @@ export enum BoardIds { BIOLISTENER_BOARD = 64, IRONBCI_32_BOARD = 65, NEUROPAWN_KNIGHT_BOARD_IMU = 66, - MUSE_S_ANTHENA_BOARD = 67 + MUSE_S_ATHENA_BOARD = 67 } export enum IpProtocolTypes { diff --git a/python_package/brainflow/board_shim.py b/python_package/brainflow/board_shim.py index 82113be56..657dc173c 100644 --- a/python_package/brainflow/board_shim.py +++ b/python_package/brainflow/board_shim.py @@ -81,7 +81,7 @@ class BoardIds(enum.IntEnum): BIOLISTENER_BOARD = 64 #: IRONBCI_32_BOARD = 65 #: NEUROPAWN_KNIGHT_BOARD_IMU = 66 #: - MUSE_S_ANTHENA_BOARD = 67 #: + MUSE_S_ATHENA_BOARD = 67 #: class IpProtocolTypes(enum.IntEnum): diff --git a/python_package/examples/tests/muse_anthena_analyze_optics_pulse.py b/python_package/examples/tests/muse_athena_analyze_optics_pulse.py similarity index 99% rename from python_package/examples/tests/muse_anthena_analyze_optics_pulse.py rename to python_package/examples/tests/muse_athena_analyze_optics_pulse.py index 970c74f30..887c72cf6 100644 --- a/python_package/examples/tests/muse_anthena_analyze_optics_pulse.py +++ b/python_package/examples/tests/muse_athena_analyze_optics_pulse.py @@ -202,7 +202,7 @@ def save_spectrum_plot(freqs, power, spectrum_bpm, output_file): def main(): parser = argparse.ArgumentParser() - parser.add_argument('--input-file', type=str, required=False, default='muse_anthena_optics_recording.csv') + parser.add_argument('--input-file', type=str, required=False, default='muse_athena_optics_recording.csv') parser.add_argument('--output-prefix', type=str, required=False, default='') parser.add_argument('--discard-seconds', type=float, required=False, default=5.0) parser.add_argument('--low-cut', type=float, required=False, default=0.7) @@ -212,7 +212,7 @@ def main(): args = parser.parse_args() data = DataFilter.read_file(args.input_file) - board_id = BoardIds.MUSE_S_ANTHENA_BOARD.value + board_id = BoardIds.MUSE_S_ATHENA_BOARD.value preset = BrainFlowPresets.ANCILLARY_PRESET optical_channels = BoardShim.get_optical_channels(board_id, preset) timestamp_channel = BoardShim.get_timestamp_channel(board_id, preset) diff --git a/python_package/examples/tests/muse_athena_eeg_band_powers.py b/python_package/examples/tests/muse_athena_eeg_band_powers.py new file mode 100644 index 000000000..662672f53 --- /dev/null +++ b/python_package/examples/tests/muse_athena_eeg_band_powers.py @@ -0,0 +1,179 @@ +import argparse +import csv +import json +import time +from pathlib import Path + +import numpy as np + +from brainflow.board_shim import ( + BoardIds, + BoardShim, + BrainFlowInputParams, + BrainFlowPresets, + LogLevels, +) +from brainflow.data_filter import DataFilter, DetrendOperations, WindowOperations + + +BANDS = [ + ('delta', 2.0, 4.0), + ('theta', 4.0, 8.0), + ('alpha', 8.0, 13.0), + ('beta', 13.0, 30.0), + ('gamma', 30.0, 45.0), +] + + +def write_eeg_csv(path, eeg_data, eeg_channels): + header = [f'eeg_row_{channel}' for channel in eeg_channels] + np.savetxt(path, eeg_data.T, delimiter=',', header=','.join(header), comments='') + + +def calc_channel_band_powers(eeg_data, sampling_rate): + nfft = DataFilter.get_nearest_power_of_two(sampling_rate) + per_channel = [] + for channel_index in range(eeg_data.shape[0]): + channel_data = np.copy(eeg_data[channel_index]) + DataFilter.detrend(channel_data, DetrendOperations.LINEAR.value) + psd = DataFilter.get_psd_welch( + channel_data, + nfft, + nfft // 2, + sampling_rate, + WindowOperations.BLACKMAN_HARRIS.value, + ) + values = { + name: DataFilter.get_band_power(psd, start, stop) + for name, start, stop in BANDS + } + per_channel.append(values) + return per_channel + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--timeout', type=int, required=False, default=30) + parser.add_argument('--mac-address', type=str, required=False, default='') + parser.add_argument('--serial-number', type=str, required=False, default='') + parser.add_argument('--duration', type=float, required=False, default=30.0) + parser.add_argument('--buffer-size', type=int, required=False, default=450000) + parser.add_argument('--output-prefix', type=str, required=False, default='muse_athena_eeg') + parser.add_argument('--no-filter', action='store_true') + args = parser.parse_args() + + BoardShim.enable_dev_board_logger() + board_id = BoardIds.MUSE_S_ATHENA_BOARD + preset = BrainFlowPresets.DEFAULT_PRESET + + params = BrainFlowInputParams() + params.timeout = args.timeout + params.mac_address = args.mac_address + params.serial_number = args.serial_number + + sampling_rate = BoardShim.get_sampling_rate(board_id, preset) + eeg_channels = BoardShim.get_eeg_channels(board_id, preset) + board_descr = BoardShim.get_board_descr(board_id, preset) + + prefix = Path(args.output_prefix) + full_data_file = prefix.with_name(f'{prefix.name}_full_default.csv') + eeg_data_file = prefix.with_name(f'{prefix.name}_eeg_only.csv') + streamer_file = prefix.with_name(f'{prefix.name}_streamer_default.csv') + summary_json_file = prefix.with_name(f'{prefix.name}_band_powers.json') + per_channel_csv_file = prefix.with_name(f'{prefix.name}_band_powers_by_channel.csv') + + print(f'board_id={int(board_id)} preset={int(preset)}') + print(f'sampling_rate={sampling_rate}') + print(f'eeg_channels={eeg_channels}') + print(f'eeg_names={board_descr.get("eeg_names", "")}') + print(f'duration={args.duration}') + + board = BoardShim(board_id, params) + prepared = False + streaming = False + try: + board.prepare_session() + prepared = True + board.add_streamer(f'file://{streamer_file}:w', preset) + board.start_stream(args.buffer_size) + streaming = True + BoardShim.log_message(LogLevels.LEVEL_INFO.value, 'start sleeping in the main thread') + time.sleep(args.duration) + data = board.get_board_data(preset=preset) + finally: + if streaming: + board.stop_stream() + if prepared: + board.release_session() + + if data.shape[1] < sampling_rate: + raise RuntimeError(f'not enough data for band powers: {data.shape[1]} samples') + + eeg_data = data[eeg_channels, :] + DataFilter.write_file(data, str(full_data_file), 'w') + write_eeg_csv(eeg_data_file, eeg_data, eeg_channels) + + avg_bands, stddev_bands = DataFilter.get_avg_band_powers( + data, + eeg_channels, + sampling_rate, + not args.no_filter, + ) + per_channel = calc_channel_band_powers(eeg_data, sampling_rate) + + summary = { + 'board_id': int(board_id), + 'preset': int(preset), + 'sampling_rate': int(sampling_rate), + 'samples': int(data.shape[1]), + 'duration_seconds': float(data.shape[1] / sampling_rate), + 'eeg_channels': [int(channel) for channel in eeg_channels], + 'eeg_names': board_descr.get('eeg_names', ''), + 'apply_filter': not args.no_filter, + 'avg_band_powers': { + BANDS[i][0]: float(avg_bands[i]) + for i in range(len(BANDS)) + }, + 'stddev_band_powers': { + BANDS[i][0]: float(stddev_bands[i]) + for i in range(len(BANDS)) + }, + 'files': { + 'full_default': str(full_data_file), + 'eeg_only': str(eeg_data_file), + 'streamer_default': str(streamer_file), + 'summary_json': str(summary_json_file), + 'per_channel_csv': str(per_channel_csv_file), + }, + } + + with summary_json_file.open('w', encoding='utf-8') as f: + json.dump(summary, f, indent=2) + + with per_channel_csv_file.open('w', newline='', encoding='utf-8') as f: + fieldnames = ['channel_index', 'board_row'] + [band[0] for band in BANDS] + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + for channel_index, powers in enumerate(per_channel): + row = { + 'channel_index': channel_index, + 'board_row': eeg_channels[channel_index], + } + row.update(powers) + writer.writerow(row) + + print(f'data_shape={data.shape}') + print(f'full_default_file={full_data_file}') + print(f'eeg_only_file={eeg_data_file}') + print(f'streamer_file={streamer_file}') + print(f'summary_json_file={summary_json_file}') + print(f'per_channel_csv_file={per_channel_csv_file}') + print('avg_band_powers=' + json.dumps(summary['avg_band_powers'], sort_keys=True)) + print('stddev_band_powers=' + json.dumps(summary['stddev_band_powers'], sort_keys=True)) + for channel_index, powers in enumerate(per_channel): + print(f'channel_{channel_index}_row_{eeg_channels[channel_index]}=' + + json.dumps(powers, sort_keys=True)) + + +if __name__ == '__main__': + main() diff --git a/python_package/examples/tests/muse_anthena_eeg_p21.py b/python_package/examples/tests/muse_athena_eeg_p21.py similarity index 95% rename from python_package/examples/tests/muse_anthena_eeg_p21.py rename to python_package/examples/tests/muse_athena_eeg_p21.py index 471e96960..77cdf481c 100644 --- a/python_package/examples/tests/muse_anthena_eeg_p21.py +++ b/python_package/examples/tests/muse_athena_eeg_p21.py @@ -20,7 +20,7 @@ def main(): params.timeout = args.timeout params.other_info = 'preset=p21;low_latency=true' - board_id = BoardIds.MUSE_S_ANTHENA_BOARD.value + board_id = BoardIds.MUSE_S_ATHENA_BOARD.value board = BoardShim(board_id, params) try: diff --git a/python_package/examples/tests/muse_athena_ppg_plotjuggler.py b/python_package/examples/tests/muse_athena_ppg_plotjuggler.py new file mode 100644 index 000000000..4471826a3 --- /dev/null +++ b/python_package/examples/tests/muse_athena_ppg_plotjuggler.py @@ -0,0 +1,72 @@ +import argparse +import time + +from brainflow.board_shim import ( + BoardIds, + BoardShim, + BrainFlowInputParams, + BrainFlowPresets, +) + + +def main(): + BoardShim.enable_dev_board_logger() + + parser = argparse.ArgumentParser() + parser.add_argument('--timeout', type=int, help='timeout for device discovery or connection', + required=False, default=0) + parser.add_argument('--mac-address', type=str, help='mac address', required=False, default='') + parser.add_argument('--serial-number', type=str, help='device name', required=False, default='') + parser.add_argument('--other-info', type=str, help='other info', required=False, default='') + parser.add_argument('--plotjuggler-ip', type=str, help='PlotJuggler UDP host', + required=False, default='127.0.0.1') + parser.add_argument('--plotjuggler-port', type=int, help='PlotJuggler UDP port', + required=False, default=9870) + parser.add_argument('--buffer-size', type=int, help='BrainFlow ring buffer size', + required=False, default=450000) + parser.add_argument('--duration', type=float, + help='stream duration in seconds; omit or set 0 to stream until Ctrl+C', + required=False, default=0.0) + args = parser.parse_args() + + params = BrainFlowInputParams() + params.timeout = args.timeout + params.mac_address = args.mac_address + params.serial_number = args.serial_number + params.other_info = args.other_info + + board_id = BoardIds.MUSE_S_ATHENA_BOARD + preset = BrainFlowPresets.DEFAULT_PRESET + streamer = f'plotjuggler_udp://{args.plotjuggler_ip}:{args.plotjuggler_port}' + + board = BoardShim(board_id, params) + streaming = False + try: + eeg_channels = BoardShim.get_eeg_channels(board_id, preset) + eeg_names = BoardShim.get_eeg_names(board_id) + sampling_rate = BoardShim.get_sampling_rate(board_id, preset) + print(f'Muse S Athena EEG channels: {eeg_channels}') + print(f'Muse S Athena EEG names: {eeg_names}') + print(f'Muse S Athena EEG sampling rate: {sampling_rate} Hz') + print(f'Streaming {preset.name} to {streamer}') + + board.prepare_session() + board.add_streamer(streamer, preset) + board.start_stream(args.buffer_size) + streaming = True + + if args.duration > 0: + time.sleep(args.duration) + else: + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + finally: + if streaming: + board.stop_stream() + board.release_session() + + +if __name__ == '__main__': + main() diff --git a/python_package/examples/tests/muse_anthena_record_optics.py b/python_package/examples/tests/muse_athena_record_optics.py similarity index 96% rename from python_package/examples/tests/muse_anthena_record_optics.py rename to python_package/examples/tests/muse_athena_record_optics.py index a215bbbba..2a29fc708 100644 --- a/python_package/examples/tests/muse_anthena_record_optics.py +++ b/python_package/examples/tests/muse_athena_record_optics.py @@ -40,7 +40,7 @@ def main(): parser.add_argument('--serial-number', type=str, required=False, default='') parser.add_argument('--timeout', type=int, required=False, default=0) parser.add_argument('--other-info', type=str, required=False, default='preset=p1035;low_latency=true') - parser.add_argument('--output-file', type=str, required=False, default='muse_anthena_optics_recording.csv') + parser.add_argument('--output-file', type=str, required=False, default='muse_athena_optics_recording.csv') args = parser.parse_args() params = BrainFlowInputParams() @@ -49,7 +49,7 @@ def main(): params.timeout = args.timeout params.other_info = args.other_info - board_id = BoardIds.MUSE_S_ANTHENA_BOARD.value + board_id = BoardIds.MUSE_S_ATHENA_BOARD.value preset = BrainFlowPresets.ANCILLARY_PRESET optical_channels = BoardShim.get_optical_channels(board_id, preset) timestamp_channel = BoardShim.get_timestamp_channel(board_id, preset) diff --git a/python_package/examples/tests/muse_anthena_save_all.py b/python_package/examples/tests/muse_athena_save_all.py similarity index 95% rename from python_package/examples/tests/muse_anthena_save_all.py rename to python_package/examples/tests/muse_athena_save_all.py index d3a86b94f..2a28b503c 100644 --- a/python_package/examples/tests/muse_anthena_save_all.py +++ b/python_package/examples/tests/muse_athena_save_all.py @@ -13,7 +13,7 @@ def main(): parser.add_argument('--mac-address', type=str, required=False, default='') parser.add_argument('--serial-number', type=str, required=False, default='') parser.add_argument('--timeout', type=int, required=False, default=0) - parser.add_argument('--output-prefix', type=str, required=False, default='muse_anthena') + parser.add_argument('--output-prefix', type=str, required=False, default='muse_athena') args = parser.parse_args() params = BrainFlowInputParams() @@ -22,7 +22,7 @@ def main(): params.timeout = args.timeout params.other_info = 'preset=p1041;low_latency=true' - board_id = BoardIds.MUSE_S_ANTHENA_BOARD.value + board_id = BoardIds.MUSE_S_ATHENA_BOARD.value board = BoardShim(board_id, params) try: diff --git a/rust_package/brainflow/src/ffi/constants.rs b/rust_package/brainflow/src/ffi/constants.rs index 070c6f9bc..7e11cf125 100644 --- a/rust_package/brainflow/src/ffi/constants.rs +++ b/rust_package/brainflow/src/ffi/constants.rs @@ -32,7 +32,7 @@ impl BoardIds { pub const FIRST: BoardIds = BoardIds::PlaybackFileBoard; } impl BoardIds { - pub const LAST: BoardIds = BoardIds::MuseSAnthenaBoard; + pub const LAST: BoardIds = BoardIds::MuseSAthenaBoard; } #[repr(i32)] #[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)] @@ -102,7 +102,7 @@ pub enum BoardIds { BiolistenerBoard = 64, Ironbci32Board = 65, NeuropawnKnightBoardImu = 66, - MuseSAnthenaBoard = 67, + MuseSAthenaBoard = 67, } #[repr(i32)] #[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)] diff --git a/src/board_controller/board_controller.cpp b/src/board_controller/board_controller.cpp index be4ccc41f..8c59fac41 100644 --- a/src/board_controller/board_controller.cpp +++ b/src/board_controller/board_controller.cpp @@ -47,7 +47,7 @@ #include "knight.h" #include "knight_imu.h" #include "muse.h" -#include "muse_anthena.h" +#include "muse_athena.h" #include "muse_bled.h" #include "notion_osc.h" #include "ntl_wifi.h" @@ -58,6 +58,10 @@ #include "synthetic_board.h" #include "unicorn_board.h" +#if defined(__ANDROID__) && defined(BRAINFLOW_BUILD_BLE) +#include "simplecble/android.h" +#endif + using json = nlohmann::json; @@ -238,8 +242,8 @@ int prepare_session (int board_id, const char *json_brainflow_input_params) case BoardIds::MUSE_S_BOARD: board = std::shared_ptr (new Muse (board_id, params)); break; - case BoardIds::MUSE_S_ANTHENA_BOARD: - board = std::shared_ptr (new MuseAnthena (board_id, params)); + case BoardIds::MUSE_S_ATHENA_BOARD: + board = std::shared_ptr (new MuseAthena (board_id, params)); break; case BoardIds::BRAINALIVE_BOARD: board = std::shared_ptr (new BrainAlive (params)); @@ -510,6 +514,16 @@ int set_log_file_board_controller (const char *log_file) int java_set_jnienv (JNIEnv *java_jnienv) { Board::java_jnienv = java_jnienv; +#if defined(__ANDROID__) && defined(BRAINFLOW_BUILD_BLE) + if (java_jnienv != NULL) + { + JavaVM *java_vm = NULL; + if ((java_jnienv->GetJavaVM (&java_vm) == JNI_OK) && (java_vm != NULL)) + { + simpleble_android_set_jvm (java_vm); + } + } +#endif return (int)BrainFlowExitCodes::STATUS_OK; } diff --git a/src/board_controller/brainflow_boards.cpp b/src/board_controller/brainflow_boards.cpp index 388a9a175..754e1501a 100644 --- a/src/board_controller/brainflow_boards.cpp +++ b/src/board_controller/brainflow_boards.cpp @@ -1166,7 +1166,7 @@ BrainFlowBoards::BrainFlowBoards() }; brainflow_boards_json["boards"]["67"]["default"] = { - {"name", "MuseAnthena"}, + {"name", "MuseAthena"}, {"sampling_rate", 256}, {"timestamp_channel", 9}, {"marker_channel", 10}, @@ -1178,7 +1178,7 @@ BrainFlowBoards::BrainFlowBoards() }; brainflow_boards_json["boards"]["67"]["auxiliary"] = { - {"name", "MuseAnthenaAux"}, + {"name", "MuseAthenaAux"}, {"sampling_rate", 52}, {"timestamp_channel", 7}, {"marker_channel", 8}, @@ -1189,7 +1189,7 @@ BrainFlowBoards::BrainFlowBoards() }; brainflow_boards_json["boards"]["67"]["ancillary"] = { - {"name", "MuseAnthenaAnc"}, + {"name", "MuseAthenaAnc"}, {"sampling_rate", 64}, {"timestamp_channel", 18}, {"marker_channel", 19}, diff --git a/src/board_controller/build.cmake b/src/board_controller/build.cmake index 1bbfb7e2a..578a9465d 100644 --- a/src/board_controller/build.cmake +++ b/src/board_controller/build.cmake @@ -77,7 +77,7 @@ SET (BOARD_CONTROLLER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/enophone/enophone.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/ble_lib_board.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/muse/muse.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/muse/muse_anthena.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/muse/muse_athena.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/brainalive/brainalive.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/emotibit/emotibit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/board_controller/ntl/ntl_wifi.cpp @@ -160,6 +160,21 @@ target_include_directories ( target_compile_definitions(${BOARD_CONTROLLER_NAME} PRIVATE NOMINMAX BRAINFLOW_VERSION=${BRAINFLOW_VERSION}) +if (BUILD_BLE) + target_compile_definitions (${BOARD_CONTROLLER_NAME} PRIVATE BRAINFLOW_BUILD_BLE) + if (ANDROID) + target_compile_definitions (${BOARD_CONTROLLER_NAME} PRIVATE STATIC_SIMPLEBLE) + target_include_directories ( + ${BOARD_CONTROLLER_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/SimpleBLE/simpleble/include + ${CMAKE_BINARY_DIR}/simpleble/export + ${CMAKE_BINARY_DIR}/third_party/SimpleBLE/simplecble/export + ) + add_dependencies (${BOARD_CONTROLLER_NAME} simplecble) + target_link_libraries (${BOARD_CONTROLLER_NAME} PRIVATE "$") + endif (ANDROID) +endif (BUILD_BLE) + set_target_properties (${BOARD_CONTROLLER_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/compiled @@ -230,8 +245,14 @@ endif (UNIX AND NOT ANDROID) if (ANDROID) add_custom_command (TARGET ${BOARD_CONTROLLER_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_SOURCE_DIR}/tools/jniLibs/${ANDROID_ABI}" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/compiled/${BOARD_CONTROLLER_COMPILED_NAME}" "${CMAKE_CURRENT_SOURCE_DIR}/tools/jniLibs/${ANDROID_ABI}/${BOARD_CONTROLLER_COMPILED_NAME}" ) + if (BUILD_BLE) + add_custom_command (TARGET ${BOARD_CONTROLLER_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$" "${CMAKE_CURRENT_SOURCE_DIR}/tools/jniLibs/${ANDROID_ABI}/$" + ) + endif (BUILD_BLE) if (LibFTDI1_FOUND) add_custom_command (TARGET ${BOARD_CONTROLLER_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_SOURCE_DIR}/java_package/android/src/main/libs/${ANDROID_ABI}/" diff --git a/src/board_controller/inc/board_controller.h b/src/board_controller/inc/board_controller.h index f50f8f147..5e136a36d 100644 --- a/src/board_controller/inc/board_controller.h +++ b/src/board_controller/inc/board_controller.h @@ -3,6 +3,12 @@ #include "board_info_getter.h" // include it here for matlab #include "shared_export.h" +#if defined(__ANDROID__) +#include +#else +typedef const struct JNINativeInterface *JNIEnv; // A handle to use Java's JNI +#endif + #ifdef __cplusplus extern "C" { @@ -46,7 +52,6 @@ extern "C" int log_level, char *message); // platform types and methods - typedef const struct JNINativeInterface *JNIEnv; // A handle to use Java's JNI SHARED_EXPORT int CALLING_CONVENTION java_set_jnienv (JNIEnv *java_jnienv); SHARED_EXPORT int CALLING_CONVENTION get_version_board_controller ( char *version, int *num_chars, int max_chars); diff --git a/src/board_controller/muse/inc/muse_anthena_constants.h b/src/board_controller/muse/inc/muse_anthena_constants.h deleted file mode 100644 index c004b82b1..000000000 --- a/src/board_controller/muse/inc/muse_anthena_constants.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - - -// info about services and chars -#define MUSE_ANTHENA_SERVICE_UUID 0xFE8D - -#define MUSE_ANTHENA_GATT_ATTR_STREAM_TOGGLE "273e0001-4c4d-454d-96be-f03bac821358" -#define MUSE_ANTHENA_GATT_DATA_1 "273e0013-4c4d-454d-96be-f03bac821358" -#define MUSE_ANTHENA_GATT_DATA_2 "273e0014-4c4d-454d-96be-f03bac821358" - -// info for equations -#define MUSE_ANTHENA_ACCELEROMETER_SCALE_FACTOR 0.00006103515625 -#define MUSE_ANTHENA_GYRO_SCALE_FACTOR -0.007476806640625 -#define MUSE_ANTHENA_EEG_SCALE_FACTOR 0.40293040293040294 -#define MUSE_ANTHENA_OPTICS_SCALE_FACTOR 1.0 -#define MUSE_ANTHENA_BATTERY_PERCENT_SCALE_FACTOR (1.0 / 512.0) diff --git a/src/board_controller/muse/inc/muse_anthena.h b/src/board_controller/muse/inc/muse_athena.h similarity index 94% rename from src/board_controller/muse/inc/muse_anthena.h rename to src/board_controller/muse/inc/muse_athena.h index b9c2ce797..fc79b87f6 100644 --- a/src/board_controller/muse/inc/muse_anthena.h +++ b/src/board_controller/muse/inc/muse_athena.h @@ -9,10 +9,10 @@ #include #include "ble_lib_board.h" -#include "muse_anthena_types.h" +#include "muse_athena_types.h" -class MuseAnthena : public BLELibBoard +class MuseAthena : public BLELibBoard { protected: @@ -65,8 +65,8 @@ class MuseAnthena : public BLELibBoard int sample_index, int n_samples, double sampling_rate); public: - MuseAnthena (int board_id, struct BrainFlowInputParams params); - ~MuseAnthena () override; + MuseAthena (int board_id, struct BrainFlowInputParams params); + ~MuseAthena () override; int prepare_session () override; int start_stream (int buffer_size, const char *streamer_params) override; diff --git a/src/board_controller/muse/inc/muse_athena_constants.h b/src/board_controller/muse/inc/muse_athena_constants.h new file mode 100644 index 000000000..6ab674720 --- /dev/null +++ b/src/board_controller/muse/inc/muse_athena_constants.h @@ -0,0 +1,16 @@ +#pragma once + + +// info about services and chars +#define MUSE_ATHENA_SERVICE_UUID 0xFE8D + +#define MUSE_ATHENA_GATT_ATTR_STREAM_TOGGLE "273e0001-4c4d-454d-96be-f03bac821358" +#define MUSE_ATHENA_GATT_DATA_1 "273e0013-4c4d-454d-96be-f03bac821358" +#define MUSE_ATHENA_GATT_DATA_2 "273e0014-4c4d-454d-96be-f03bac821358" + +// info for equations +#define MUSE_ATHENA_ACCELEROMETER_SCALE_FACTOR 0.00006103515625 +#define MUSE_ATHENA_GYRO_SCALE_FACTOR -0.007476806640625 +#define MUSE_ATHENA_EEG_SCALE_FACTOR 0.40293040293040294 +#define MUSE_ATHENA_OPTICS_SCALE_FACTOR 1.0 +#define MUSE_ATHENA_BATTERY_PERCENT_SCALE_FACTOR (1.0 / 512.0) diff --git a/src/board_controller/muse/inc/muse_anthena_types.h b/src/board_controller/muse/inc/muse_athena_types.h similarity index 100% rename from src/board_controller/muse/inc/muse_anthena_types.h rename to src/board_controller/muse/inc/muse_athena_types.h diff --git a/src/board_controller/muse/inc/muse_options.h b/src/board_controller/muse/inc/muse_options.h index 880b250b2..ded68a6d2 100644 --- a/src/board_controller/muse/inc/muse_options.h +++ b/src/board_controller/muse/inc/muse_options.h @@ -13,7 +13,7 @@ namespace MuseOptions enum class PresetFamily { Legacy, - Anthena + Athena }; inline std::string trim_string (const std::string &value) @@ -51,7 +51,7 @@ namespace MuseOptions return false; } - inline bool is_valid_anthena_preset (const std::string &preset) + inline bool is_valid_athena_preset (const std::string &preset) { static const char *valid_presets[] = {"p20", "p21", "p50", "p51", "p60", "p61", "p1034", "p1035", "p1041", "p1042", "p1043", "p1044", "p1045", "p1046", "p4129"}; @@ -92,9 +92,9 @@ namespace MuseOptions inline bool is_valid_preset (int board_id, PresetFamily family, const std::string &preset) { - if (family == PresetFamily::Anthena) + if (family == PresetFamily::Athena) { - return is_valid_anthena_preset (preset); + return is_valid_athena_preset (preset); } return is_valid_legacy_preset (board_id, preset); } diff --git a/src/board_controller/muse/muse.cpp b/src/board_controller/muse/muse.cpp index 5839b5f43..ccce43c09 100644 --- a/src/board_controller/muse/muse.cpp +++ b/src/board_controller/muse/muse.cpp @@ -162,6 +162,7 @@ int Muse::prepare_session () res = (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR; } simpleble_adapter_scan_stop (muse_adapter); + simpleble_adapter_set_callback_on_scan_found (muse_adapter, NULL, NULL); if (res == (int)BrainFlowExitCodes::STATUS_OK) { // for safety diff --git a/src/board_controller/muse/muse_anthena.cpp b/src/board_controller/muse/muse_athena.cpp similarity index 88% rename from src/board_controller/muse/muse_anthena.cpp rename to src/board_controller/muse/muse_athena.cpp index 28c1c9aff..5baac69e2 100644 --- a/src/board_controller/muse/muse_anthena.cpp +++ b/src/board_controller/muse/muse_athena.cpp @@ -1,4 +1,4 @@ -#include "muse_anthena.h" +#include "muse_athena.h" #include #include @@ -9,12 +9,11 @@ #include #include "custom_cast.h" -#include "muse_anthena_constants.h" +#include "muse_athena_constants.h" #include "muse_options.h" #include "timestamp.h" - -MuseAnthena::SensorConfig::SensorConfig () +MuseAthena::SensorConfig::SensorConfig () : type (SensorType::UNKNOWN) , n_channels (0) , n_samples (0) @@ -24,7 +23,7 @@ MuseAnthena::SensorConfig::SensorConfig () { } -MuseAnthena::SensorConfig::SensorConfig (SensorType type, int n_channels, int n_samples, +MuseAthena::SensorConfig::SensorConfig (SensorType type, int n_channels, int n_samples, double sampling_rate, size_t data_len, bool variable_length) : type (type) , n_channels (n_channels) @@ -36,7 +35,7 @@ MuseAnthena::SensorConfig::SensorConfig (SensorType type, int n_channels, int n_ } -bool MuseAnthena::get_sensor_config (uint8_t tag, SensorConfig &config) +bool MuseAthena::get_sensor_config (uint8_t tag, SensorConfig &config) { switch (tag) { @@ -60,7 +59,7 @@ bool MuseAnthena::get_sensor_config (uint8_t tag, SensorConfig &config) return true; case 0x53: // DRL/REF: 2 channels, 6 samples at 32 Hz. BrainFlow does not expose it for - // Muse Anthena, but the fixed length is needed to skip the block correctly. + // Muse Athena, but the fixed length is needed to skip the block correctly. config = SensorConfig (SensorType::UNKNOWN, 2, 6, 32.0, 24); return true; case 0x88: @@ -74,7 +73,7 @@ bool MuseAnthena::get_sensor_config (uint8_t tag, SensorConfig &config) } } -int MuseAnthena::get_optics_canonical_index (uint8_t tag, int channel) +int MuseAthena::get_optics_canonical_index (uint8_t tag, int channel) { int num_channels = 0; if (tag == 0x34) @@ -97,36 +96,36 @@ int MuseAnthena::get_optics_canonical_index (uint8_t tag, int channel) return -1; } -std::string MuseAnthena::trim_string (const std::string &value) +std::string MuseAthena::trim_string (const std::string &value) { return MuseOptions::trim_string (value); } -std::string MuseAnthena::to_lower (const std::string &value) +std::string MuseAthena::to_lower (const std::string &value) { return MuseOptions::to_lower (value); } -bool MuseAnthena::is_valid_muse_preset (const std::string &preset) +bool MuseAthena::is_valid_muse_preset (const std::string &preset) { - return MuseOptions::is_valid_anthena_preset (preset); + return MuseOptions::is_valid_athena_preset (preset); } -bool MuseAnthena::parse_bool_option (const std::string &value, bool &parsed) +bool MuseAthena::parse_bool_option (const std::string &value, bool &parsed) { return MuseOptions::parse_bool_option (value, parsed); } -int MuseAnthena::parse_muse_options () +int MuseAthena::parse_muse_options () { muse_preset = "p1041"; enable_low_latency = true; std::string parse_error; if (!MuseOptions::parse_preset_options (params.other_info, board_id, - MuseOptions::PresetFamily::Anthena, true, muse_preset, enable_low_latency, parse_error)) + MuseOptions::PresetFamily::Athena, true, muse_preset, enable_low_latency, parse_error)) { - safe_logger (spdlog::level::err, "Invalid MuseAnthena other_info: {}", parse_error); + safe_logger (spdlog::level::err, "Invalid MuseAthena other_info: {}", parse_error); return (int)BrainFlowExitCodes::INVALID_ARGUMENTS_ERROR; } @@ -134,26 +133,25 @@ int MuseAnthena::parse_muse_options () } -void anthena_adapter_on_scan_found ( +void athena_adapter_on_scan_found ( simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, void *board) { - ((MuseAnthena *)(board))->adapter_on_scan_found (adapter, peripheral); + ((MuseAthena *)(board))->adapter_on_scan_found (adapter, peripheral); } -void anthena_peripheral_on_data (simpleble_peripheral_t peripheral, simpleble_uuid_t service, +void athena_peripheral_on_data (simpleble_peripheral_t peripheral, simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t *data, size_t size, void *board) { - ((MuseAnthena *)(board))->peripheral_on_data (peripheral, service, characteristic, data, size); + ((MuseAthena *)(board))->peripheral_on_data (peripheral, service, characteristic, data, size); } -void anthena_peripheral_on_status (simpleble_peripheral_t peripheral, simpleble_uuid_t service, +void athena_peripheral_on_status (simpleble_peripheral_t peripheral, simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t *data, size_t size, void *board) { - ((MuseAnthena *)(board)) - ->peripheral_on_status (peripheral, service, characteristic, data, size); + ((MuseAthena *)(board))->peripheral_on_status (peripheral, service, characteristic, data, size); } -MuseAnthena::MuseAnthena (int board_id, struct BrainFlowInputParams params) +MuseAthena::MuseAthena (int board_id, struct BrainFlowInputParams params) : BLELibBoard (board_id, params) { initialized = false; @@ -166,13 +164,13 @@ MuseAnthena::MuseAnthena (int board_id, struct BrainFlowInputParams params) enable_low_latency = true; } -MuseAnthena::~MuseAnthena () +MuseAthena::~MuseAthena () { skip_logs = true; release_session (); } -int MuseAnthena::prepare_session () +int MuseAthena::prepare_session () { if (initialized) { @@ -184,7 +182,7 @@ int MuseAnthena::prepare_session () { return res; } - safe_logger (spdlog::level::info, "Use MuseAnthena preset {} and low_latency {}", muse_preset, + safe_logger (spdlog::level::info, "Use MuseAthena preset {} and low_latency {}", muse_preset, enable_low_latency); if (params.timeout < 1) { @@ -213,7 +211,7 @@ int MuseAnthena::prepare_session () } simpleble_adapter_set_callback_on_scan_found ( - muse_adapter, ::anthena_adapter_on_scan_found, (void *)this); + muse_adapter, ::athena_adapter_on_scan_found, (void *)this); if (!simpleble_adapter_is_bluetooth_enabled ()) { @@ -227,14 +225,15 @@ int MuseAnthena::prepare_session () auto sec = std::chrono::seconds (1); if (cv.wait_for (lk, params.timeout * sec, [this] { return this->muse_peripheral != NULL; })) { - safe_logger (spdlog::level::info, "Found MuseAnthena device"); + safe_logger (spdlog::level::info, "Found MuseAthena device"); } else { - safe_logger (spdlog::level::err, "Failed to find MuseAnthena Device"); + safe_logger (spdlog::level::err, "Failed to find MuseAthena Device"); res = (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR; } simpleble_adapter_scan_stop (muse_adapter); + simpleble_adapter_set_callback_on_scan_found (muse_adapter, NULL, NULL); if (res == (int)BrainFlowExitCodes::STATUS_OK) { // for safety @@ -242,14 +241,14 @@ int MuseAnthena::prepare_session () { if (simpleble_peripheral_connect (muse_peripheral) == SIMPLEBLE_SUCCESS) { - safe_logger (spdlog::level::info, "Connected to MuseAnthena Device"); + safe_logger (spdlog::level::info, "Connected to MuseAthena Device"); res = (int)BrainFlowExitCodes::STATUS_OK; break; } else { safe_logger ( - spdlog::level::warn, "Failed to connect to MuseAnthena Device: {}/3", i); + spdlog::level::warn, "Failed to connect to MuseAthena Device: {}/3", i); res = (int)BrainFlowExitCodes::BOARD_NOT_READY_ERROR; std::this_thread::sleep_for (std::chrono::seconds (1)); } @@ -281,14 +280,14 @@ int MuseAnthena::prepare_session () service.characteristics[j].uuid.value); if (strcmp (service.characteristics[j].uuid.value, - MUSE_ANTHENA_GATT_ATTR_STREAM_TOGGLE) == 0) + MUSE_ATHENA_GATT_ATTR_STREAM_TOGGLE) == 0) { control_characteristics = std::pair ( service.uuid, service.characteristics[j].uuid); control_characteristics_found = true; safe_logger (spdlog::level::info, "found control characteristic"); if (simpleble_peripheral_notify (muse_peripheral, service.uuid, - service.characteristics[j].uuid, ::anthena_peripheral_on_status, + service.characteristics[j].uuid, ::athena_peripheral_on_status, (void *)this) == SIMPLEBLE_SUCCESS) { notified_characteristics.push_back ( @@ -302,14 +301,14 @@ int MuseAnthena::prepare_session () } } - if ((strcmp (service.characteristics[j].uuid.value, MUSE_ANTHENA_GATT_DATA_1) == + if ((strcmp (service.characteristics[j].uuid.value, MUSE_ATHENA_GATT_DATA_1) == 0) || - (strcmp (service.characteristics[j].uuid.value, MUSE_ANTHENA_GATT_DATA_2) == 0)) + (strcmp (service.characteristics[j].uuid.value, MUSE_ATHENA_GATT_DATA_2) == 0)) { // Athena multiplexes EEG, IMU, optics, and battery packets across data // characteristics; use one parser intentionally and route by packet tag. if (simpleble_peripheral_notify (muse_peripheral, service.uuid, - service.characteristics[j].uuid, ::anthena_peripheral_on_data, + service.characteristics[j].uuid, ::athena_peripheral_on_data, (void *)this) == SIMPLEBLE_SUCCESS) { notified_characteristics.push_back ( @@ -370,7 +369,7 @@ int MuseAnthena::prepare_session () return res; } -int MuseAnthena::start_stream (int buffer_size, const char *streamer_params) +int MuseAthena::start_stream (int buffer_size, const char *streamer_params) { if (!initialized) { @@ -399,7 +398,7 @@ int MuseAnthena::start_stream (int buffer_size, const char *streamer_params) int l1_res = config_board ("L1"); if (l1_res != (int)BrainFlowExitCodes::STATUS_OK) { - safe_logger (spdlog::level::warn, "Failed to enable MuseAnthena low latency mode"); + safe_logger (spdlog::level::warn, "Failed to enable MuseAthena low latency mode"); } } if (res == (int)BrainFlowExitCodes::STATUS_OK) @@ -408,7 +407,7 @@ int MuseAnthena::start_stream (int buffer_size, const char *streamer_params) int status_res = config_board ("s"); if (status_res != (int)BrainFlowExitCodes::STATUS_OK) { - safe_logger (spdlog::level::warn, "Failed to request MuseAnthena status after start"); + safe_logger (spdlog::level::warn, "Failed to request MuseAthena status after start"); } std::this_thread::sleep_for (std::chrono::milliseconds (200)); } @@ -420,7 +419,7 @@ int MuseAnthena::start_stream (int buffer_size, const char *streamer_params) return res; } -int MuseAnthena::stop_stream () +int MuseAthena::stop_stream () { if (muse_peripheral == NULL) { @@ -438,7 +437,7 @@ int MuseAnthena::stop_stream () (!is_connected)) { safe_logger (spdlog::level::warn, - "MuseAnthena device is already disconnected during stop_stream"); + "MuseAthena device is already disconnected during stop_stream"); res = (int)BrainFlowExitCodes::STATUS_OK; } } @@ -452,7 +451,7 @@ int MuseAnthena::stop_stream () return res; } -int MuseAnthena::release_session () +int MuseAthena::release_session () { if (initialized) { @@ -511,12 +510,12 @@ int MuseAnthena::release_session () return (int)BrainFlowExitCodes::STATUS_OK; } -int MuseAnthena::config_board (std::string config, std::string &response) +int MuseAthena::config_board (std::string config, std::string &response) { return config_board (config); } -int MuseAnthena::config_board (std::string config) +int MuseAthena::config_board (std::string config) { if (!initialized) { @@ -546,7 +545,7 @@ int MuseAnthena::config_board (std::string config) return (int)BrainFlowExitCodes::STATUS_OK; } -void MuseAnthena::adapter_on_scan_found ( +void MuseAthena::adapter_on_scan_found ( simpleble_adapter_t adapter, simpleble_peripheral_t peripheral) { (void)adapter; @@ -571,7 +570,9 @@ void MuseAnthena::adapter_on_scan_found ( } else { - if (strncmp (peripheral_identified, "MuseS", 5) == 0) + if ((strncmp (peripheral_identified, "MuseS", 5) == 0) || + (strncmp (peripheral_identified, "MuseAthena", 10) == 0) || + (strncmp (peripheral_identified, "Muse Athena", 11) == 0)) { found = true; } @@ -597,7 +598,7 @@ void MuseAnthena::adapter_on_scan_found ( } } -void MuseAnthena::peripheral_on_data (simpleble_peripheral_t peripheral, simpleble_uuid_t service, +void MuseAthena::peripheral_on_data (simpleble_peripheral_t peripheral, simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t *data, size_t size) { (void)peripheral; @@ -606,7 +607,7 @@ void MuseAnthena::peripheral_on_data (simpleble_peripheral_t peripheral, simpleb handle_data_notification (data, size); } -void MuseAnthena::peripheral_on_status (simpleble_peripheral_t peripheral, simpleble_uuid_t service, +void MuseAthena::peripheral_on_status (simpleble_peripheral_t peripheral, simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t *data, size_t size) { (void)peripheral; @@ -615,7 +616,7 @@ void MuseAnthena::peripheral_on_status (simpleble_peripheral_t peripheral, simpl safe_logger (spdlog::level::debug, "Status packet: {}", bytes_to_string (data, size)); } -void MuseAnthena::handle_data_notification (const uint8_t *data, size_t size) +void MuseAthena::handle_data_notification (const uint8_t *data, size_t size) { std::lock_guard callback_guard (callback_lock); @@ -704,7 +705,7 @@ void MuseAnthena::handle_data_notification (const uint8_t *data, size_t size) } } -void MuseAnthena::parse_sensor_payload ( +void MuseAthena::parse_sensor_payload ( uint8_t tag, uint32_t package_num, double host_timestamp, const uint8_t *data, size_t size) { SensorConfig config; @@ -726,7 +727,7 @@ void MuseAnthena::parse_sensor_payload ( { last_battery = (double)cast_16bit_to_uint16_little_endian ((const unsigned char *)data) * - MUSE_ANTHENA_BATTERY_PERCENT_SCALE_FACTOR; + MUSE_ATHENA_BATTERY_PERCENT_SCALE_FACTOR; } return; } @@ -757,7 +758,7 @@ void MuseAnthena::parse_sensor_payload ( if ((size_t)channel < eeg_channels.size ()) { package[(size_t)eeg_channels[(size_t)channel]] = - (double)raw * MUSE_ANTHENA_EEG_SCALE_FACTOR; + (double)raw * MUSE_ATHENA_EEG_SCALE_FACTOR; } else { @@ -765,7 +766,7 @@ void MuseAnthena::parse_sensor_payload ( if (other_channel < other_channels.size ()) { package[(size_t)other_channels[other_channel]] = - (double)raw * MUSE_ANTHENA_EEG_SCALE_FACTOR; + (double)raw * MUSE_ATHENA_EEG_SCALE_FACTOR; } } } @@ -796,7 +797,7 @@ void MuseAnthena::parse_sensor_payload ( if ((size_t)channel < accel_channels.size ()) { package[(size_t)accel_channels[(size_t)channel]] = - (double)raw * MUSE_ANTHENA_ACCELEROMETER_SCALE_FACTOR; + (double)raw * MUSE_ATHENA_ACCELEROMETER_SCALE_FACTOR; } } for (int channel = 0; channel < 3; channel++) @@ -806,7 +807,7 @@ void MuseAnthena::parse_sensor_payload ( if ((size_t)channel < gyro_channels.size ()) { package[(size_t)gyro_channels[(size_t)channel]] = - (double)raw * MUSE_ANTHENA_GYRO_SCALE_FACTOR; + (double)raw * MUSE_ATHENA_GYRO_SCALE_FACTOR; } } package[(size_t)timestamp_channel] = get_sample_timestamp ( @@ -838,7 +839,7 @@ void MuseAnthena::parse_sensor_payload ( int canonical_index = get_optics_canonical_index (tag, channel); if ((canonical_index >= 0) && (canonical_index < 16)) { - double value = (double)raw * MUSE_ANTHENA_OPTICS_SCALE_FACTOR; + double value = (double)raw * MUSE_ATHENA_OPTICS_SCALE_FACTOR; if ((size_t)canonical_index < optical_channels.size ()) { package[(size_t)optical_channels[(size_t)canonical_index]] = value; @@ -854,14 +855,14 @@ void MuseAnthena::parse_sensor_payload ( } } -void MuseAnthena::reset_timestamps () +void MuseAthena::reset_timestamps () { last_eeg_timestamp = -1.0; last_aux_timestamp = -1.0; last_anc_timestamp = -1.0; } -double MuseAnthena::get_sample_timestamp (double last_timestamp, double current_timestamp, +double MuseAthena::get_sample_timestamp (double last_timestamp, double current_timestamp, int sample_index, int n_samples, double sampling_rate) { if ((n_samples <= 0) || (sampling_rate <= 0.0)) @@ -889,7 +890,7 @@ double MuseAnthena::get_sample_timestamp (double last_timestamp, double current_ return last_timestamp + step * (double)(sample_index + 1); } -std::string MuseAnthena::bytes_to_string (const uint8_t *data, size_t size) +std::string MuseAthena::bytes_to_string (const uint8_t *data, size_t size) { std::ostringstream oss; for (size_t i = 0; i < size; i++) diff --git a/src/utils/inc/brainflow_constants.h b/src/utils/inc/brainflow_constants.h index 93aecb3ca..4a7f11e47 100644 --- a/src/utils/inc/brainflow_constants.h +++ b/src/utils/inc/brainflow_constants.h @@ -95,10 +95,10 @@ enum class BoardIds : int BIOLISTENER_BOARD = 64, IRONBCI_32_BOARD = 65, NEUROPAWN_KNIGHT_BOARD_IMU = 66, - MUSE_S_ANTHENA_BOARD = 67, + MUSE_S_ATHENA_BOARD = 67, // use it to iterate FIRST = PLAYBACK_FILE_BOARD, - LAST = MUSE_S_ANTHENA_BOARD + LAST = MUSE_S_ATHENA_BOARD }; enum class IpProtocolTypes : int diff --git a/third_party/SimpleBLE/brainflow.patch b/third_party/SimpleBLE/brainflow.patch index da205db9a..085efcb2f 100644 --- a/third_party/SimpleBLE/brainflow.patch +++ b/third_party/SimpleBLE/brainflow.patch @@ -258,3 +258,377 @@ index d1b3590..4a964dd 100644 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/simplecble) if(SIMPLECBLE_TEST) +diff --git a/simpleble/src/backends/android/AdapterAndroid.cpp b/simpleble/src/backends/android/AdapterAndroid.cpp +index 1db187fd..37511b79 100644 +--- a/simpleble/src/backends/android/AdapterAndroid.cpp ++++ b/simpleble/src/backends/android/AdapterAndroid.cpp +@@ -20,0 +21,4 @@ AdapterAndroid::AdapterAndroid() { ++ if (!this->scanning_) { ++ return; ++ } ++ +@@ -71 +74,0 @@ void AdapterAndroid::scan_start() { +- _btScanner.startScan(_btScanCallback); +@@ -72,0 +76 @@ void AdapterAndroid::scan_start() { ++ _btScanner.startScan(_btScanCallback); +@@ -77 +80,0 @@ void AdapterAndroid::scan_stop() { +- _btScanner.stopScan(_btScanCallback); +@@ -78,0 +82 @@ void AdapterAndroid::scan_stop() { ++ _btScanner.stopScan(_btScanCallback); +diff --git a/simpleble/src/backends/android/PeripheralAndroid.cpp b/simpleble/src/backends/android/PeripheralAndroid.cpp +index 1e130581..93d1dc67 100644 +--- a/simpleble/src/backends/android/PeripheralAndroid.cpp ++++ b/simpleble/src/backends/android/PeripheralAndroid.cpp +@@ -9 +8,0 @@ +-#include +@@ -10,0 +10 @@ ++#include +@@ -18,0 +19,4 @@ using namespace std::chrono_literals; ++namespace { ++constexpr int ANDROID_REQUESTED_MTU = 517; ++} ++ +@@ -26,2 +30,4 @@ PeripheralAndroid::PeripheralAndroid(Android::BluetoothDevice device) : _device( +- // If a connection has been established, request service discovery. +- _gatt.discoverServices(); ++ if (!_gatt.requestMtu(ANDROID_REQUESTED_MTU)) { ++ SIMPLEBLE_LOG_WARN("Failed to request Android GATT MTU"); ++ _gatt.discoverServices(); ++ } +@@ -35,0 +42,2 @@ PeripheralAndroid::PeripheralAndroid(Android::BluetoothDevice device) : _device( ++ _btGattCallback.set_callback_onMtuChanged([this]() { _gatt.discoverServices(); }); ++ +diff --git a/simpleble/src/backends/android/bridge/BluetoothGattCallback.cpp b/simpleble/src/backends/android/bridge/BluetoothGattCallback.cpp +index 75f7b426..f9de8e58 100644 +--- a/simpleble/src/backends/android/bridge/BluetoothGattCallback.cpp ++++ b/simpleble/src/backends/android/bridge/BluetoothGattCallback.cpp +@@ -4 +3,0 @@ +-#include +@@ -5,0 +5 @@ ++#include +@@ -14 +14,3 @@ jmethodID BluetoothGattCallback::_constructor = nullptr; +-kvn::safe_map, BluetoothGattCallback*, SimpleJNI::ObjectComparator> BluetoothGattCallback::_map; ++kvn::safe_map, BluetoothGattCallback*, ++ SimpleJNI::ObjectComparator> ++ BluetoothGattCallback::_map; +@@ -18,4 +20,5 @@ const SimpleJNI::JNIDescriptor BluetoothGattCallback::descriptor{ +- "org/simpleble/android/bridge/BluetoothGattCallback", // Java class name +- &_cls, // Where to store the jclass +- { // Methods to preload +- {"", "()V", &_constructor} // Constructor ++ "org/simpleble/android/bridge/BluetoothGattCallback", // Java class name ++ &_cls, // Where to store the jclass ++ { ++ // Methods to preload ++ {"", "()V", &_constructor} // Constructor +@@ -38 +41,2 @@ BluetoothGattCallback::BluetoothGattCallback() : connected(false), services_disc +- throw std::runtime_error("BluetoothGattCallback JNI resources not preloaded. Ensure SimpleJNI::Registrar::preload() is called."); ++ throw std::runtime_error( ++ "BluetoothGattCallback JNI resources not preloaded. Ensure SimpleJNI::Registrar::preload() is called."); +@@ -71,2 +75,11 @@ void BluetoothGattCallback::set_callback_onServicesDiscovered(std::function characteristic, +- std::function)> callback) { ++void BluetoothGattCallback::set_callback_onMtuChanged(std::function callback) { ++ if (callback) { ++ _callback_onMtuChanged.load(callback); ++ } else { ++ _callback_onMtuChanged.unload(); ++ } ++} ++ ++void BluetoothGattCallback::set_callback_onCharacteristicChanged( ++ SimpleJNI::Object characteristic, ++ std::function)> callback) { +@@ -80 +93,2 @@ void BluetoothGattCallback::set_callback_onCharacteristicChanged(SimpleJNI::Obje +-void BluetoothGattCallback::clear_callback_onCharacteristicChanged(SimpleJNI::Object characteristic) { ++void BluetoothGattCallback::clear_callback_onCharacteristicChanged( ++ SimpleJNI::Object characteristic) { +@@ -84 +98,2 @@ void BluetoothGattCallback::clear_callback_onCharacteristicChanged(SimpleJNI::Ob +-void BluetoothGattCallback::set_flag_characteristicWritePending(SimpleJNI::Object characteristic) { ++void BluetoothGattCallback::set_flag_characteristicWritePending( ++ SimpleJNI::Object characteristic) { +@@ -91 +106,2 @@ void BluetoothGattCallback::set_flag_characteristicWritePending(SimpleJNI::Objec +-void BluetoothGattCallback::clear_flag_characteristicWritePending(SimpleJNI::Object characteristic) { ++void BluetoothGattCallback::clear_flag_characteristicWritePending( ++ SimpleJNI::Object characteristic) { +@@ -100 +116,2 @@ void BluetoothGattCallback::clear_flag_characteristicWritePending(SimpleJNI::Obj +-void BluetoothGattCallback::wait_flag_characteristicWritePending(SimpleJNI::Object characteristic) { ++void BluetoothGattCallback::wait_flag_characteristicWritePending( ++ SimpleJNI::Object characteristic) { +@@ -111 +128,2 @@ void BluetoothGattCallback::wait_flag_characteristicWritePending(SimpleJNI::Obje +-void BluetoothGattCallback::set_flag_characteristicReadPending(SimpleJNI::Object characteristic) { ++void BluetoothGattCallback::set_flag_characteristicReadPending( ++ SimpleJNI::Object characteristic) { +@@ -118,2 +136,2 @@ void BluetoothGattCallback::set_flag_characteristicReadPending(SimpleJNI::Object +-void BluetoothGattCallback::clear_flag_characteristicReadPending(SimpleJNI::Object characteristic, +- std::vector value) { ++void BluetoothGattCallback::clear_flag_characteristicReadPending( ++ SimpleJNI::Object characteristic, std::vector value) { +@@ -129 +147,2 @@ void BluetoothGattCallback::clear_flag_characteristicReadPending(SimpleJNI::Obje +-std::vector BluetoothGattCallback::wait_flag_characteristicReadPending(SimpleJNI::Object characteristic) { ++std::vector BluetoothGattCallback::wait_flag_characteristicReadPending( ++ SimpleJNI::Object characteristic) { +@@ -142 +161,2 @@ std::vector BluetoothGattCallback::wait_flag_characteristicReadPending( +-void BluetoothGattCallback::set_flag_descriptorWritePending(SimpleJNI::Object descriptor) { ++void BluetoothGattCallback::set_flag_descriptorWritePending( ++ SimpleJNI::Object descriptor) { +@@ -149 +169,2 @@ void BluetoothGattCallback::set_flag_descriptorWritePending(SimpleJNI::Object descriptor) { ++void BluetoothGattCallback::clear_flag_descriptorWritePending( ++ SimpleJNI::Object descriptor) { +@@ -158 +179,2 @@ void BluetoothGattCallback::clear_flag_descriptorWritePending(SimpleJNI::Object< +-void BluetoothGattCallback::wait_flag_descriptorWritePending(SimpleJNI::Object descriptor) { ++void BluetoothGattCallback::wait_flag_descriptorWritePending( ++ SimpleJNI::Object descriptor) { +@@ -169 +191,2 @@ void BluetoothGattCallback::wait_flag_descriptorWritePending(SimpleJNI::Object descriptor) { ++void BluetoothGattCallback::set_flag_descriptorReadPending( ++ SimpleJNI::Object descriptor) { +@@ -176 +199,2 @@ void BluetoothGattCallback::set_flag_descriptorReadPending(SimpleJNI::Object descriptor, std::vector value) { ++void BluetoothGattCallback::clear_flag_descriptorReadPending( ++ SimpleJNI::Object descriptor, std::vector value) { +@@ -186 +210,2 @@ void BluetoothGattCallback::clear_flag_descriptorReadPending(SimpleJNI::Object BluetoothGattCallback::wait_flag_descriptorReadPending(SimpleJNI::Object descriptor) { ++std::vector BluetoothGattCallback::wait_flag_descriptorReadPending( ++ SimpleJNI::Object descriptor) { +@@ -201 +226,2 @@ std::vector BluetoothGattCallback::wait_flag_descriptorReadPending(Simp +-void BluetoothGattCallback::jni_onConnectionStateChangeCallback(SimpleJNI::Object thiz_obj, jint status, jint new_state) { ++void BluetoothGattCallback::jni_onConnectionStateChangeCallback( ++ SimpleJNI::Object thiz_obj, jint status, jint new_state) { +@@ -211 +237,2 @@ void BluetoothGattCallback::jni_onConnectionStateChangeCallback(SimpleJNI::Objec +-void BluetoothGattCallback::jni_onServicesDiscoveredCallback(SimpleJNI::Object thiz_obj, jint status) { ++void BluetoothGattCallback::jni_onServicesDiscoveredCallback(SimpleJNI::Object thiz_obj, ++ jint status) { +@@ -228,2 +255,4 @@ void BluetoothGattCallback::jni_onServiceChangedCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object characteristic_obj, +- SimpleJNI::ByteArray value) { ++void BluetoothGattCallback::jni_onCharacteristicChangedCallback( ++ SimpleJNI::Object thiz_obj, ++ SimpleJNI::Object characteristic_obj, ++ SimpleJNI::ByteArray value) { +@@ -240,2 +269,4 @@ void BluetoothGattCallback::jni_onCharacteristicChangedCallback(SimpleJNI::Objec +-void BluetoothGattCallback::jni_onCharacteristicReadCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object characteristic_obj, +- SimpleJNI::ByteArray value, jint status) { ++void BluetoothGattCallback::jni_onCharacteristicReadCallback( ++ SimpleJNI::Object thiz_obj, ++ SimpleJNI::Object characteristic_obj, ++ SimpleJNI::ByteArray value, jint status) { +@@ -249,2 +280,3 @@ void BluetoothGattCallback::jni_onCharacteristicReadCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object characteristic_obj, +- jint status) { ++void BluetoothGattCallback::jni_onCharacteristicWriteCallback( ++ SimpleJNI::Object thiz_obj, ++ SimpleJNI::Object characteristic_obj, jint status) { +@@ -258,2 +290,4 @@ void BluetoothGattCallback::jni_onCharacteristicWriteCallback(SimpleJNI::Object< +-void BluetoothGattCallback::jni_onDescriptorReadCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object descriptor_obj, +- SimpleJNI::ByteArray value, jint status) { ++void BluetoothGattCallback::jni_onDescriptorReadCallback( ++ SimpleJNI::Object thiz_obj, ++ SimpleJNI::Object descriptor_obj, SimpleJNI::ByteArray value, ++ jint status) { +@@ -267,2 +301,3 @@ void BluetoothGattCallback::jni_onDescriptorReadCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object descriptor_obj, +- jint status) { ++void BluetoothGattCallback::jni_onDescriptorWriteCallback( ++ SimpleJNI::Object thiz_obj, ++ SimpleJNI::Object descriptor_obj, jint status) { +@@ -276,2 +311,3 @@ void BluetoothGattCallback::jni_onDescriptorWriteCallback(SimpleJNI::Object thiz_obj, jint mtu, jint status) { +- auto msg = "onMtuChangedCallback"; ++void BluetoothGattCallback::jni_onMtuChangedCallback(SimpleJNI::Object thiz_obj, ++ jint mtu, jint status) { ++ auto msg = fmt::format("onMtuChangedCallback mtu: {} status: {}", mtu, status); +@@ -281,0 +318 @@ void BluetoothGattCallback::jni_onMtuChangedCallback(SimpleJNI::Object_callback_onMtuChanged); +@@ -284 +321,2 @@ void BluetoothGattCallback::jni_onMtuChangedCallback(SimpleJNI::Object thiz_obj, jint tx_phy, jint rx_phy, jint status) { ++void BluetoothGattCallback::jni_onPhyReadCallback(SimpleJNI::Object thiz_obj, ++ jint tx_phy, jint rx_phy, jint status) { +@@ -291 +329,2 @@ void BluetoothGattCallback::jni_onPhyReadCallback(SimpleJNI::Object thiz_obj, jint tx_phy, jint rx_phy, jint status) { ++void BluetoothGattCallback::jni_onPhyUpdateCallback(SimpleJNI::Object thiz_obj, ++ jint tx_phy, jint rx_phy, jint status) { +@@ -298 +337,2 @@ void BluetoothGattCallback::jni_onPhyUpdateCallback(SimpleJNI::Object thiz_obj, jint rssi, jint status) { ++void BluetoothGattCallback::jni_onReadRemoteRssiCallback(SimpleJNI::Object thiz_obj, ++ jint rssi, jint status) { +@@ -305 +345,2 @@ void BluetoothGattCallback::jni_onReadRemoteRssiCallback(SimpleJNI::Object thiz_obj, jint status) { ++void BluetoothGattCallback::jni_onReliableWriteCompletedCallback( ++ SimpleJNI::Object thiz_obj, jint status) { +@@ -432 +473 @@ JNIEXPORT void JNICALL Java_org_simpleble_android_bridge_BluetoothGattCallback_o +-} // extern "C" +\ No newline at end of file ++} // extern "C" +diff --git a/simpleble/src/backends/android/bridge/BluetoothGattCallback.h b/simpleble/src/backends/android/bridge/BluetoothGattCallback.h +index 9431d149..c08017fc 100644 +--- a/simpleble/src/backends/android/bridge/BluetoothGattCallback.h ++++ b/simpleble/src/backends/android/bridge/BluetoothGattCallback.h +@@ -23,0 +24 @@ class BluetoothGattCallback { ++ void set_callback_onMtuChanged(std::function callback); +@@ -34,2 +35,4 @@ class BluetoothGattCallback { +- void clear_flag_characteristicReadPending(SimpleJNI::Object characteristic, std::vector value); +- std::vector wait_flag_characteristicReadPending(SimpleJNI::Object characteristic); ++ void clear_flag_characteristicReadPending(SimpleJNI::Object characteristic, ++ std::vector value); ++ std::vector wait_flag_characteristicReadPending( ++ SimpleJNI::Object characteristic); +@@ -42 +45,2 @@ class BluetoothGattCallback { +- void clear_flag_descriptorReadPending(SimpleJNI::Object descriptor, std::vector value); ++ void clear_flag_descriptorReadPending(SimpleJNI::Object descriptor, ++ std::vector value); +@@ -80 +84,3 @@ class BluetoothGattCallback { +- static kvn::safe_map, BluetoothGattCallback*, SimpleJNI::ObjectComparator> _map; ++ static kvn::safe_map, BluetoothGattCallback*, ++ SimpleJNI::ObjectComparator> ++ _map; +@@ -85,0 +92 @@ class BluetoothGattCallback { ++ kvn::safe_callback _callback_onMtuChanged; +@@ -87 +94,2 @@ class BluetoothGattCallback { +- kvn::safe_map, kvn::safe_callback)>, SimpleJNI::ObjectComparator> ++ kvn::safe_map, kvn::safe_callback)>, ++ SimpleJNI::ObjectComparator> +@@ -90,4 +98,12 @@ class BluetoothGattCallback { +- kvn::safe_map, FlagData, SimpleJNI::ObjectComparator> _flag_characteristicWritePending; +- kvn::safe_map, FlagData, SimpleJNI::ObjectComparator> _flag_characteristicReadPending; +- kvn::safe_map, FlagData, SimpleJNI::ObjectComparator> _flag_descriptorWritePending; +- kvn::safe_map, FlagData, SimpleJNI::ObjectComparator> _flag_descriptorReadPending; ++ kvn::safe_map, FlagData, ++ SimpleJNI::ObjectComparator> ++ _flag_characteristicWritePending; ++ kvn::safe_map, FlagData, ++ SimpleJNI::ObjectComparator> ++ _flag_characteristicReadPending; ++ kvn::safe_map, FlagData, ++ SimpleJNI::ObjectComparator> ++ _flag_descriptorWritePending; ++ kvn::safe_map, FlagData, ++ SimpleJNI::ObjectComparator> ++ _flag_descriptorReadPending; +@@ -102 +118 @@ class BluetoothGattCallback { +-} // namespace SimpleBLE +\ No newline at end of file ++} // namespace SimpleBLE +diff --git a/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.cpp b/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.cpp +index e12c9d8b..65064618 100644 +--- a/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.cpp ++++ b/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.cpp +@@ -21,0 +22 @@ jmethodID BluetoothGatt::_method_requestConnectionPriority = nullptr; ++jmethodID BluetoothGatt::_method_requestMtu = nullptr; +@@ -24,15 +25,16 @@ const SimpleJNI::JNIDescriptor BluetoothGatt::descriptor{ +- "android/bluetooth/BluetoothGatt", // Java class name +- &_cls, // Where to store the jclass +- { // Methods to preload +- {"close", "()V", &_method_close}, +- {"connect", "()Z", &_method_connect}, +- {"disconnect", "()V", &_method_disconnect}, +- {"discoverServices", "()Z", &_method_discoverServices}, +- {"getServices", "()Ljava/util/List;", &_method_getServices}, +- {"readCharacteristic", "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", &_method_readCharacteristic}, +- {"readDescriptor", "(Landroid/bluetooth/BluetoothGattDescriptor;)Z", &_method_readDescriptor}, +- {"setCharacteristicNotification", "(Landroid/bluetooth/BluetoothGattCharacteristic;Z)Z", &_method_setCharacteristicNotification}, +- {"writeCharacteristic", "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", &_method_writeCharacteristic}, +- {"writeDescriptor", "(Landroid/bluetooth/BluetoothGattDescriptor;)Z", &_method_writeDescriptor}, +- {"requestConnectionPriority", "(I)Z", &_method_requestConnectionPriority} +- }}; ++ "android/bluetooth/BluetoothGatt", // Java class name ++ &_cls, // Where to store the jclass ++ { // Methods to preload ++ {"close", "()V", &_method_close}, ++ {"connect", "()Z", &_method_connect}, ++ {"disconnect", "()V", &_method_disconnect}, ++ {"discoverServices", "()Z", &_method_discoverServices}, ++ {"getServices", "()Ljava/util/List;", &_method_getServices}, ++ {"readCharacteristic", "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", &_method_readCharacteristic}, ++ {"readDescriptor", "(Landroid/bluetooth/BluetoothGattDescriptor;)Z", &_method_readDescriptor}, ++ {"setCharacteristicNotification", "(Landroid/bluetooth/BluetoothGattCharacteristic;Z)Z", ++ &_method_setCharacteristicNotification}, ++ {"writeCharacteristic", "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", &_method_writeCharacteristic}, ++ {"writeDescriptor", "(Landroid/bluetooth/BluetoothGattDescriptor;)Z", &_method_writeDescriptor}, ++ {"requestConnectionPriority", "(I)Z", &_method_requestConnectionPriority}, ++ {"requestMtu", "(I)Z", &_method_requestMtu}}}; +@@ -113,0 +116,5 @@ bool BluetoothGatt::requestConnectionPriority(int connectionPriority) { ++bool BluetoothGatt::requestMtu(int mtu) { ++ if (!_obj) throw std::runtime_error("BluetoothGatt is not initialized"); ++ return _obj.call_boolean_method(_method_requestMtu, mtu); ++} ++ +diff --git a/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.h b/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.h +index 131f5bea..6bac0d57 100644 +--- a/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.h ++++ b/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.h +@@ -15 +14,0 @@ class BluetoothGatt { +- +@@ -45 +44 @@ class BluetoothGatt { +- // bool requestMtu(int mtu); ++ bool requestMtu(int mtu); +@@ -68,0 +68 @@ class BluetoothGatt { ++ static jmethodID _method_requestMtu; +diff --git a/simplecble/src/adapter.cpp b/simplecble/src/adapter.cpp +index b6a73d91..3cc478e3 100644 +--- a/simplecble/src/adapter.cpp ++++ b/simplecble/src/adapter.cpp +@@ -375 +375 @@ simpleble_err_t simpleble_adapter_set_callback_on_scan_found( +- if (handle == nullptr || callback == nullptr) { ++ if (handle == nullptr) { +@@ -380,0 +381,4 @@ simpleble_err_t simpleble_adapter_set_callback_on_scan_found( ++ if (callback == nullptr) { ++ adapter->set_callback_on_scan_found(nullptr); ++ return SIMPLEBLE_SUCCESS; ++ } +@@ -389 +393 @@ simpleble_err_t simpleble_adapter_set_callback_on_scan_found( +-} +\ No newline at end of file ++} +diff --git a/simpledroidbridge/src/main/java/org/simpleble/android/bridge/BluetoothGattCallback.java b/simpledroidbridge/src/main/java/org/simpleble/android/bridge/BluetoothGattCallback.java +index e9ac75ee..435468c5 100644 +--- a/simpledroidbridge/src/main/java/org/simpleble/android/bridge/BluetoothGattCallback.java ++++ b/simpledroidbridge/src/main/java/org/simpleble/android/bridge/BluetoothGattCallback.java +@@ -14 +14 @@ public class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallba +- // NOTE: This method has been deprecated on API 33, but we're still using API 31, so we need to support this. ++ // NOTE: This method is deprecated on API 33, but older Android versions still call it. +@@ -19,6 +19,5 @@ public class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallba +- // @Override +- // public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) { +- // // NOTE: This method is only available from API 33 onwards. +- // super.onCharacteristicChanged(gatt, characteristic, value); +- // onCharacteristicChangedCallback(gatt, characteristic, value); +- // } ++ @Override ++ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) { ++ // The API 33 default implementation forwards to the deprecated overload. ++ onCharacteristicChangedCallback(gatt, characteristic, value); ++ } +@@ -28 +27 @@ public class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallba +- // NOTE: This method has been deprecated on API 33, but we're still using API 31, so we need to support this. ++ // NOTE: This method is deprecated on API 33, but older Android versions still call it. +@@ -33,6 +32,5 @@ public class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallba +- // @Override +- // public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value, int status) { +- // // NOTE: This method is only available from API 33 onwards. +- // super.onCharacteristicRead(gatt, characteristic, value, status); +- // onCharacteristicReadCallback(gatt, characteristic, value, status); +- // } ++ @Override ++ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value, int status) { ++ // The API 33 default implementation forwards to the deprecated overload. ++ onCharacteristicReadCallback(gatt, characteristic, value, status); ++ } +@@ -52 +49,0 @@ public class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallba +- // NOTE: This method is only available from API 33 onwards +@@ -55 +52 @@ public class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallba +- // NOTE: This method has been deprecated on API 33, but we're still using API 31, so we need to support this. ++ // NOTE: This method is deprecated on API 33, but older Android versions still call it. +@@ -60,6 +57,5 @@ public class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallba +- // @Override +- // public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status, byte[] value) { +- // // NOTE: This method is only available from API 33 onwards. +- // super.onDescriptorRead(gatt, descriptor, status, value); +- // onDescriptorReadCallback(gatt, descriptor, value, status); +- // } ++ @Override ++ public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status, byte[] value) { ++ // The API 33 default implementation forwards to the deprecated overload. ++ onDescriptorReadCallback(gatt, descriptor, value, status); ++ } diff --git a/third_party/SimpleBLE/simpleble/src/backends/android/AdapterAndroid.cpp b/third_party/SimpleBLE/simpleble/src/backends/android/AdapterAndroid.cpp index 1db187fd5..37511b790 100644 --- a/third_party/SimpleBLE/simpleble/src/backends/android/AdapterAndroid.cpp +++ b/third_party/SimpleBLE/simpleble/src/backends/android/AdapterAndroid.cpp @@ -18,6 +18,10 @@ bool AdapterAndroid::bluetooth_enabled() { return BackendAndroid::get()->bluetoo AdapterAndroid::AdapterAndroid() { _btScanCallback.set_callback_onScanResult([this](Android::ScanResult scan_result) { + if (!this->scanning_) { + return; + } + std::string address = scan_result.getDevice().getAddress(); if (this->peripherals_.count(address) == 0) { @@ -68,14 +72,14 @@ bool AdapterAndroid::is_powered() { return _btAdapter.isEnabled(); } void AdapterAndroid::scan_start() { seen_peripherals_.clear(); - _btScanner.startScan(_btScanCallback); scanning_ = true; + _btScanner.startScan(_btScanCallback); SAFE_CALLBACK_CALL(this->_callback_on_scan_start); } void AdapterAndroid::scan_stop() { - _btScanner.stopScan(_btScanCallback); scanning_ = false; + _btScanner.stopScan(_btScanCallback); SAFE_CALLBACK_CALL(this->_callback_on_scan_stop); } diff --git a/third_party/SimpleBLE/simpleble/src/backends/android/PeripheralAndroid.cpp b/third_party/SimpleBLE/simpleble/src/backends/android/PeripheralAndroid.cpp index 1e130581f..93d1dc67b 100644 --- a/third_party/SimpleBLE/simpleble/src/backends/android/PeripheralAndroid.cpp +++ b/third_party/SimpleBLE/simpleble/src/backends/android/PeripheralAndroid.cpp @@ -6,8 +6,8 @@ #include "DescriptorBase.h" #include "ServiceBase.h" -#include #include +#include #include #include "CommonUtils.h" #include "LoggingInternal.h" @@ -16,6 +16,10 @@ using namespace SimpleBLE; using namespace std::chrono_literals; +namespace { +constexpr int ANDROID_REQUESTED_MTU = 517; +} + PeripheralAndroid::PeripheralAndroid(Android::BluetoothDevice device) : _device(device) { _btGattCallback.set_callback_onConnectionStateChange([this](bool connected) { if (connected) { @@ -23,8 +27,10 @@ PeripheralAndroid::PeripheralAndroid(Android::BluetoothDevice device) : _device( _gatt.requestConnectionPriority(static_cast(Config::Android::connection_priority_request)); } - // If a connection has been established, request service discovery. - _gatt.discoverServices(); + if (!_gatt.requestMtu(ANDROID_REQUESTED_MTU)) { + SIMPLEBLE_LOG_WARN("Failed to request Android GATT MTU"); + _gatt.discoverServices(); + } } else { // If a connection has been lost, close the GATT object. _gatt.close(); @@ -33,6 +39,8 @@ PeripheralAndroid::PeripheralAndroid(Android::BluetoothDevice device) : _device( } }); + _btGattCallback.set_callback_onMtuChanged([this]() { _gatt.discoverServices(); }); + _btGattCallback.set_callback_onServicesDiscovered([this]() { // Once services have been discovered, store them and notify the user. _services = _gatt.getServices(); diff --git a/third_party/SimpleBLE/simpleble/src/backends/android/bridge/BluetoothGattCallback.cpp b/third_party/SimpleBLE/simpleble/src/backends/android/bridge/BluetoothGattCallback.cpp index 75f7b426d..f9de8e58b 100644 --- a/third_party/SimpleBLE/simpleble/src/backends/android/bridge/BluetoothGattCallback.cpp +++ b/third_party/SimpleBLE/simpleble/src/backends/android/bridge/BluetoothGattCallback.cpp @@ -1,8 +1,8 @@ #include "BluetoothGattCallback.h" #include -#include #include +#include #include "LoggingInternal.h" namespace SimpleBLE { @@ -11,14 +11,17 @@ namespace Bridge { SimpleJNI::GlobalRef BluetoothGattCallback::_cls; jmethodID BluetoothGattCallback::_constructor = nullptr; -kvn::safe_map, BluetoothGattCallback*, SimpleJNI::ObjectComparator> BluetoothGattCallback::_map; +kvn::safe_map, BluetoothGattCallback*, + SimpleJNI::ObjectComparator> + BluetoothGattCallback::_map; // Define the JNI descriptor const SimpleJNI::JNIDescriptor BluetoothGattCallback::descriptor{ - "org/simpleble/android/bridge/BluetoothGattCallback", // Java class name - &_cls, // Where to store the jclass - { // Methods to preload - {"", "()V", &_constructor} // Constructor + "org/simpleble/android/bridge/BluetoothGattCallback", // Java class name + &_cls, // Where to store the jclass + { + // Methods to preload + {"", "()V", &_constructor} // Constructor }}; const SimpleJNI::AutoRegister BluetoothGattCallback::registrar{&descriptor}; @@ -35,7 +38,8 @@ const SimpleJNI::AutoRegister BluetoothGattCallback::regi BluetoothGattCallback::BluetoothGattCallback() : connected(false), services_discovered(false), mtu(UINT16_MAX) { if (!_cls.get()) { - throw std::runtime_error("BluetoothGattCallback JNI resources not preloaded. Ensure SimpleJNI::Registrar::preload() is called."); + throw std::runtime_error( + "BluetoothGattCallback JNI resources not preloaded. Ensure SimpleJNI::Registrar::preload() is called."); } SimpleJNI::Env env; @@ -68,8 +72,17 @@ void BluetoothGattCallback::set_callback_onServicesDiscovered(std::function characteristic, - std::function)> callback) { +void BluetoothGattCallback::set_callback_onMtuChanged(std::function callback) { + if (callback) { + _callback_onMtuChanged.load(callback); + } else { + _callback_onMtuChanged.unload(); + } +} + +void BluetoothGattCallback::set_callback_onCharacteristicChanged( + SimpleJNI::Object characteristic, + std::function)> callback) { if (callback) { _callback_onCharacteristicChanged[characteristic].load(callback); } else { @@ -77,18 +90,21 @@ void BluetoothGattCallback::set_callback_onCharacteristicChanged(SimpleJNI::Obje } } -void BluetoothGattCallback::clear_callback_onCharacteristicChanged(SimpleJNI::Object characteristic) { +void BluetoothGattCallback::clear_callback_onCharacteristicChanged( + SimpleJNI::Object characteristic) { _callback_onCharacteristicChanged[characteristic].unload(); } -void BluetoothGattCallback::set_flag_characteristicWritePending(SimpleJNI::Object characteristic) { +void BluetoothGattCallback::set_flag_characteristicWritePending( + SimpleJNI::Object characteristic) { auto& flag_data = _flag_characteristicWritePending[characteristic]; std::lock_guard lock(flag_data.mtx); flag_data.flag = true; } -void BluetoothGattCallback::clear_flag_characteristicWritePending(SimpleJNI::Object characteristic) { +void BluetoothGattCallback::clear_flag_characteristicWritePending( + SimpleJNI::Object characteristic) { auto& flag_data = _flag_characteristicWritePending[characteristic]; { std::lock_guard lock(flag_data.mtx); @@ -97,7 +113,8 @@ void BluetoothGattCallback::clear_flag_characteristicWritePending(SimpleJNI::Obj flag_data.cv.notify_all(); } -void BluetoothGattCallback::wait_flag_characteristicWritePending(SimpleJNI::Object characteristic) { +void BluetoothGattCallback::wait_flag_characteristicWritePending( + SimpleJNI::Object characteristic) { auto& flag_data = _flag_characteristicWritePending[characteristic]; std::unique_lock lock(flag_data.mtx); flag_data.cv.wait_for(lock, std::chrono::seconds(5), [&flag_data] { return !flag_data.flag; }); @@ -108,15 +125,16 @@ void BluetoothGattCallback::wait_flag_characteristicWritePending(SimpleJNI::Obje } } -void BluetoothGattCallback::set_flag_characteristicReadPending(SimpleJNI::Object characteristic) { +void BluetoothGattCallback::set_flag_characteristicReadPending( + SimpleJNI::Object characteristic) { auto& flag_data = _flag_characteristicReadPending[characteristic]; std::lock_guard lock(flag_data.mtx); flag_data.flag = true; } -void BluetoothGattCallback::clear_flag_characteristicReadPending(SimpleJNI::Object characteristic, - std::vector value) { +void BluetoothGattCallback::clear_flag_characteristicReadPending( + SimpleJNI::Object characteristic, std::vector value) { auto& flag_data = _flag_characteristicReadPending[characteristic]; { std::lock_guard lock(flag_data.mtx); @@ -126,7 +144,8 @@ void BluetoothGattCallback::clear_flag_characteristicReadPending(SimpleJNI::Obje flag_data.cv.notify_all(); } -std::vector BluetoothGattCallback::wait_flag_characteristicReadPending(SimpleJNI::Object characteristic) { +std::vector BluetoothGattCallback::wait_flag_characteristicReadPending( + SimpleJNI::Object characteristic) { auto& flag_data = _flag_characteristicReadPending[characteristic]; std::unique_lock lock(flag_data.mtx); flag_data.cv.wait_for(lock, std::chrono::seconds(5), [&flag_data] { return !flag_data.flag; }); @@ -139,14 +158,16 @@ std::vector BluetoothGattCallback::wait_flag_characteristicReadPending( return flag_data.value; } -void BluetoothGattCallback::set_flag_descriptorWritePending(SimpleJNI::Object descriptor) { +void BluetoothGattCallback::set_flag_descriptorWritePending( + SimpleJNI::Object descriptor) { auto& flag_data = _flag_descriptorWritePending[descriptor]; std::lock_guard lock(flag_data.mtx); flag_data.flag = true; } -void BluetoothGattCallback::clear_flag_descriptorWritePending(SimpleJNI::Object descriptor) { +void BluetoothGattCallback::clear_flag_descriptorWritePending( + SimpleJNI::Object descriptor) { auto& flag_data = _flag_descriptorWritePending[descriptor]; { std::lock_guard lock(flag_data.mtx); @@ -155,7 +176,8 @@ void BluetoothGattCallback::clear_flag_descriptorWritePending(SimpleJNI::Object< flag_data.cv.notify_all(); } -void BluetoothGattCallback::wait_flag_descriptorWritePending(SimpleJNI::Object descriptor) { +void BluetoothGattCallback::wait_flag_descriptorWritePending( + SimpleJNI::Object descriptor) { auto& flag_data = _flag_descriptorWritePending[descriptor]; std::unique_lock lock(flag_data.mtx); flag_data.cv.wait_for(lock, std::chrono::seconds(5), [&flag_data] { return !flag_data.flag; }); @@ -166,14 +188,16 @@ void BluetoothGattCallback::wait_flag_descriptorWritePending(SimpleJNI::Object descriptor) { +void BluetoothGattCallback::set_flag_descriptorReadPending( + SimpleJNI::Object descriptor) { auto& flag_data = _flag_descriptorReadPending[descriptor]; std::lock_guard lock(flag_data.mtx); flag_data.flag = true; } -void BluetoothGattCallback::clear_flag_descriptorReadPending(SimpleJNI::Object descriptor, std::vector value) { +void BluetoothGattCallback::clear_flag_descriptorReadPending( + SimpleJNI::Object descriptor, std::vector value) { auto& flag_data = _flag_descriptorReadPending[descriptor]; { std::lock_guard lock(flag_data.mtx); @@ -183,7 +207,8 @@ void BluetoothGattCallback::clear_flag_descriptorReadPending(SimpleJNI::Object BluetoothGattCallback::wait_flag_descriptorReadPending(SimpleJNI::Object descriptor) { +std::vector BluetoothGattCallback::wait_flag_descriptorReadPending( + SimpleJNI::Object descriptor) { auto& flag_data = _flag_descriptorReadPending[descriptor]; std::unique_lock lock(flag_data.mtx); flag_data.cv.wait_for(lock, std::chrono::seconds(5), [&flag_data] { return !flag_data.flag; }); @@ -198,7 +223,8 @@ std::vector BluetoothGattCallback::wait_flag_descriptorReadPending(Simp // JNI Callbacks -void BluetoothGattCallback::jni_onConnectionStateChangeCallback(SimpleJNI::Object thiz_obj, jint status, jint new_state) { +void BluetoothGattCallback::jni_onConnectionStateChangeCallback( + SimpleJNI::Object thiz_obj, jint status, jint new_state) { auto msg = fmt::format("onConnectionStateChangeCallback status: {} new_state: {}", status, new_state); SIMPLEBLE_LOG_INFO(msg); @@ -208,7 +234,8 @@ void BluetoothGattCallback::jni_onConnectionStateChangeCallback(SimpleJNI::Objec SAFE_CALLBACK_CALL(obj->_callback_onConnectionStateChange, connected); } -void BluetoothGattCallback::jni_onServicesDiscoveredCallback(SimpleJNI::Object thiz_obj, jint status) { +void BluetoothGattCallback::jni_onServicesDiscoveredCallback(SimpleJNI::Object thiz_obj, + jint status) { auto msg = "onServicesDiscoveredCallback"; SIMPLEBLE_LOG_INFO(msg); @@ -225,8 +252,10 @@ void BluetoothGattCallback::jni_onServiceChangedCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object characteristic_obj, - SimpleJNI::ByteArray value) { +void BluetoothGattCallback::jni_onCharacteristicChangedCallback( + SimpleJNI::Object thiz_obj, + SimpleJNI::Object characteristic_obj, + SimpleJNI::ByteArray value) { auto msg = "onCharacteristicChangedCallback with value: " + value.bytes().toHex(true); SIMPLEBLE_LOG_INFO(msg); @@ -237,8 +266,10 @@ void BluetoothGattCallback::jni_onCharacteristicChangedCallback(SimpleJNI::Objec } } -void BluetoothGattCallback::jni_onCharacteristicReadCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object characteristic_obj, - SimpleJNI::ByteArray value, jint status) { +void BluetoothGattCallback::jni_onCharacteristicReadCallback( + SimpleJNI::Object thiz_obj, + SimpleJNI::Object characteristic_obj, + SimpleJNI::ByteArray value, jint status) { auto msg = "onCharacteristicReadCallback"; SIMPLEBLE_LOG_INFO(msg); @@ -246,8 +277,9 @@ void BluetoothGattCallback::jni_onCharacteristicReadCallback(SimpleJNI::Objectclear_flag_characteristicReadPending(characteristic_obj, value.bytes()); } -void BluetoothGattCallback::jni_onCharacteristicWriteCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object characteristic_obj, - jint status) { +void BluetoothGattCallback::jni_onCharacteristicWriteCallback( + SimpleJNI::Object thiz_obj, + SimpleJNI::Object characteristic_obj, jint status) { auto msg = "onCharacteristicWriteCallback"; SIMPLEBLE_LOG_INFO(msg); @@ -255,8 +287,10 @@ void BluetoothGattCallback::jni_onCharacteristicWriteCallback(SimpleJNI::Object< obj->clear_flag_characteristicWritePending(characteristic_obj); } -void BluetoothGattCallback::jni_onDescriptorReadCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object descriptor_obj, - SimpleJNI::ByteArray value, jint status) { +void BluetoothGattCallback::jni_onDescriptorReadCallback( + SimpleJNI::Object thiz_obj, + SimpleJNI::Object descriptor_obj, SimpleJNI::ByteArray value, + jint status) { auto msg = "onDescriptorReadCallback"; SIMPLEBLE_LOG_INFO(msg); @@ -264,8 +298,9 @@ void BluetoothGattCallback::jni_onDescriptorReadCallback(SimpleJNI::Objectclear_flag_descriptorReadPending(descriptor_obj, value.bytes()); } -void BluetoothGattCallback::jni_onDescriptorWriteCallback(SimpleJNI::Object thiz_obj, SimpleJNI::Object descriptor_obj, - jint status) { +void BluetoothGattCallback::jni_onDescriptorWriteCallback( + SimpleJNI::Object thiz_obj, + SimpleJNI::Object descriptor_obj, jint status) { auto msg = "onDescriptorWriteCallback"; SIMPLEBLE_LOG_INFO(msg); @@ -273,36 +308,42 @@ void BluetoothGattCallback::jni_onDescriptorWriteCallback(SimpleJNI::Objectclear_flag_descriptorWritePending(descriptor_obj); } -void BluetoothGattCallback::jni_onMtuChangedCallback(SimpleJNI::Object thiz_obj, jint mtu, jint status) { - auto msg = "onMtuChangedCallback"; +void BluetoothGattCallback::jni_onMtuChangedCallback(SimpleJNI::Object thiz_obj, + jint mtu, jint status) { + auto msg = fmt::format("onMtuChangedCallback mtu: {} status: {}", mtu, status); SIMPLEBLE_LOG_INFO(msg); BluetoothGattCallback* obj = GET_CALLBACK_OBJECT_OR_RETURN(thiz_obj); obj->mtu = mtu; + SAFE_CALLBACK_CALL(obj->_callback_onMtuChanged); } -void BluetoothGattCallback::jni_onPhyReadCallback(SimpleJNI::Object thiz_obj, jint tx_phy, jint rx_phy, jint status) { +void BluetoothGattCallback::jni_onPhyReadCallback(SimpleJNI::Object thiz_obj, + jint tx_phy, jint rx_phy, jint status) { auto msg = "onPhyReadCallback"; SIMPLEBLE_LOG_INFO(msg); BluetoothGattCallback* obj = GET_CALLBACK_OBJECT_OR_RETURN(thiz_obj); } -void BluetoothGattCallback::jni_onPhyUpdateCallback(SimpleJNI::Object thiz_obj, jint tx_phy, jint rx_phy, jint status) { +void BluetoothGattCallback::jni_onPhyUpdateCallback(SimpleJNI::Object thiz_obj, + jint tx_phy, jint rx_phy, jint status) { auto msg = "onPhyUpdateCallback"; SIMPLEBLE_LOG_INFO(msg); BluetoothGattCallback* obj = GET_CALLBACK_OBJECT_OR_RETURN(thiz_obj); } -void BluetoothGattCallback::jni_onReadRemoteRssiCallback(SimpleJNI::Object thiz_obj, jint rssi, jint status) { +void BluetoothGattCallback::jni_onReadRemoteRssiCallback(SimpleJNI::Object thiz_obj, + jint rssi, jint status) { auto msg = "onReadRemoteRssiCallback"; SIMPLEBLE_LOG_INFO(msg); BluetoothGattCallback* obj = GET_CALLBACK_OBJECT_OR_RETURN(thiz_obj); } -void BluetoothGattCallback::jni_onReliableWriteCompletedCallback(SimpleJNI::Object thiz_obj, jint status) { +void BluetoothGattCallback::jni_onReliableWriteCompletedCallback( + SimpleJNI::Object thiz_obj, jint status) { auto msg = "onReliableWriteCompletedCallback"; SIMPLEBLE_LOG_INFO(msg); @@ -429,4 +470,4 @@ JNIEXPORT void JNICALL Java_org_simpleble_android_bridge_BluetoothGattCallback_o } // clang-format on -} // extern "C" \ No newline at end of file +} // extern "C" diff --git a/third_party/SimpleBLE/simpleble/src/backends/android/bridge/BluetoothGattCallback.h b/third_party/SimpleBLE/simpleble/src/backends/android/bridge/BluetoothGattCallback.h index 9431d1499..c08017fc1 100644 --- a/third_party/SimpleBLE/simpleble/src/backends/android/bridge/BluetoothGattCallback.h +++ b/third_party/SimpleBLE/simpleble/src/backends/android/bridge/BluetoothGattCallback.h @@ -21,6 +21,7 @@ class BluetoothGattCallback { void set_callback_onConnectionStateChange(std::function callback); void set_callback_onServicesDiscovered(std::function callback); + void set_callback_onMtuChanged(std::function callback); void set_callback_onCharacteristicChanged(SimpleJNI::Object characteristic, std::function value)> callback); @@ -31,15 +32,18 @@ class BluetoothGattCallback { void wait_flag_characteristicWritePending(SimpleJNI::Object characteristic); void set_flag_characteristicReadPending(SimpleJNI::Object characteristic); - void clear_flag_characteristicReadPending(SimpleJNI::Object characteristic, std::vector value); - std::vector wait_flag_characteristicReadPending(SimpleJNI::Object characteristic); + void clear_flag_characteristicReadPending(SimpleJNI::Object characteristic, + std::vector value); + std::vector wait_flag_characteristicReadPending( + SimpleJNI::Object characteristic); void set_flag_descriptorWritePending(SimpleJNI::Object descriptor); void clear_flag_descriptorWritePending(SimpleJNI::Object descriptor); void wait_flag_descriptorWritePending(SimpleJNI::Object descriptor); void set_flag_descriptorReadPending(SimpleJNI::Object descriptor); - void clear_flag_descriptorReadPending(SimpleJNI::Object descriptor, std::vector value); + void clear_flag_descriptorReadPending(SimpleJNI::Object descriptor, + std::vector value); std::vector wait_flag_descriptorReadPending(SimpleJNI::Object descriptor); bool connected; @@ -77,20 +81,32 @@ class BluetoothGattCallback { static SimpleJNI::GlobalRef _cls; static jmethodID _constructor; - static kvn::safe_map, BluetoothGattCallback*, SimpleJNI::ObjectComparator> _map; + static kvn::safe_map, BluetoothGattCallback*, + SimpleJNI::ObjectComparator> + _map; SimpleJNI::Object _obj; kvn::safe_callback _callback_onConnectionStateChange; kvn::safe_callback _callback_onServicesDiscovered; + kvn::safe_callback _callback_onMtuChanged; - kvn::safe_map, kvn::safe_callback)>, SimpleJNI::ObjectComparator> + kvn::safe_map, kvn::safe_callback)>, + SimpleJNI::ObjectComparator> _callback_onCharacteristicChanged; - kvn::safe_map, FlagData, SimpleJNI::ObjectComparator> _flag_characteristicWritePending; - kvn::safe_map, FlagData, SimpleJNI::ObjectComparator> _flag_characteristicReadPending; - kvn::safe_map, FlagData, SimpleJNI::ObjectComparator> _flag_descriptorWritePending; - kvn::safe_map, FlagData, SimpleJNI::ObjectComparator> _flag_descriptorReadPending; + kvn::safe_map, FlagData, + SimpleJNI::ObjectComparator> + _flag_characteristicWritePending; + kvn::safe_map, FlagData, + SimpleJNI::ObjectComparator> + _flag_characteristicReadPending; + kvn::safe_map, FlagData, + SimpleJNI::ObjectComparator> + _flag_descriptorWritePending; + kvn::safe_map, FlagData, + SimpleJNI::ObjectComparator> + _flag_descriptorReadPending; // Static JNI resources managed by Registrar static const SimpleJNI::JNIDescriptor descriptor; @@ -99,4 +115,4 @@ class BluetoothGattCallback { } // namespace Bridge } // namespace Android -} // namespace SimpleBLE \ No newline at end of file +} // namespace SimpleBLE diff --git a/third_party/SimpleBLE/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.cpp b/third_party/SimpleBLE/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.cpp index e12c9d8b3..65064618e 100644 --- a/third_party/SimpleBLE/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.cpp +++ b/third_party/SimpleBLE/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.cpp @@ -19,23 +19,25 @@ jmethodID BluetoothGatt::_method_setCharacteristicNotification = nullptr; jmethodID BluetoothGatt::_method_writeCharacteristic = nullptr; jmethodID BluetoothGatt::_method_writeDescriptor = nullptr; jmethodID BluetoothGatt::_method_requestConnectionPriority = nullptr; +jmethodID BluetoothGatt::_method_requestMtu = nullptr; // Define the JNI descriptor const SimpleJNI::JNIDescriptor BluetoothGatt::descriptor{ - "android/bluetooth/BluetoothGatt", // Java class name - &_cls, // Where to store the jclass - { // Methods to preload - {"close", "()V", &_method_close}, - {"connect", "()Z", &_method_connect}, - {"disconnect", "()V", &_method_disconnect}, - {"discoverServices", "()Z", &_method_discoverServices}, - {"getServices", "()Ljava/util/List;", &_method_getServices}, - {"readCharacteristic", "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", &_method_readCharacteristic}, - {"readDescriptor", "(Landroid/bluetooth/BluetoothGattDescriptor;)Z", &_method_readDescriptor}, - {"setCharacteristicNotification", "(Landroid/bluetooth/BluetoothGattCharacteristic;Z)Z", &_method_setCharacteristicNotification}, - {"writeCharacteristic", "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", &_method_writeCharacteristic}, - {"writeDescriptor", "(Landroid/bluetooth/BluetoothGattDescriptor;)Z", &_method_writeDescriptor}, - {"requestConnectionPriority", "(I)Z", &_method_requestConnectionPriority} - }}; + "android/bluetooth/BluetoothGatt", // Java class name + &_cls, // Where to store the jclass + { // Methods to preload + {"close", "()V", &_method_close}, + {"connect", "()Z", &_method_connect}, + {"disconnect", "()V", &_method_disconnect}, + {"discoverServices", "()Z", &_method_discoverServices}, + {"getServices", "()Ljava/util/List;", &_method_getServices}, + {"readCharacteristic", "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", &_method_readCharacteristic}, + {"readDescriptor", "(Landroid/bluetooth/BluetoothGattDescriptor;)Z", &_method_readDescriptor}, + {"setCharacteristicNotification", "(Landroid/bluetooth/BluetoothGattCharacteristic;Z)Z", + &_method_setCharacteristicNotification}, + {"writeCharacteristic", "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", &_method_writeCharacteristic}, + {"writeDescriptor", "(Landroid/bluetooth/BluetoothGattDescriptor;)Z", &_method_writeDescriptor}, + {"requestConnectionPriority", "(I)Z", &_method_requestConnectionPriority}, + {"requestMtu", "(I)Z", &_method_requestMtu}}}; const SimpleJNI::AutoRegister BluetoothGatt::registrar{&descriptor}; @@ -111,5 +113,10 @@ bool BluetoothGatt::requestConnectionPriority(int connectionPriority) { return _obj.call_boolean_method(_method_requestConnectionPriority, connectionPriority); } +bool BluetoothGatt::requestMtu(int mtu) { + if (!_obj) throw std::runtime_error("BluetoothGatt is not initialized"); + return _obj.call_boolean_method(_method_requestMtu, mtu); +} + } // namespace Android } // namespace SimpleBLE diff --git a/third_party/SimpleBLE/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.h b/third_party/SimpleBLE/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.h index 131f5bea1..6bac0d576 100644 --- a/third_party/SimpleBLE/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.h +++ b/third_party/SimpleBLE/simpleble/src/backends/android/types/android/bluetooth/BluetoothGatt.h @@ -12,7 +12,6 @@ namespace Android { class BluetoothGatt { public: - static constexpr int CONNECTION_PRIORITY_BALANCED = 0; static constexpr int CONNECTION_PRIORITY_HIGH = 1; static constexpr int CONNECTION_PRIORITY_LOW_POWER = 2; @@ -42,7 +41,7 @@ class BluetoothGatt { // void readPhy(); // bool readRemoteRssi(); bool requestConnectionPriority(int connectionPriority); - // bool requestMtu(int mtu); + bool requestMtu(int mtu); bool setCharacteristicNotification(BluetoothGattCharacteristic characteristic, bool enable); // void setPreferredPhy(int txPhy, int rxPhy, int phyOptions); bool writeCharacteristic(BluetoothGattCharacteristic characteristic); @@ -66,6 +65,7 @@ class BluetoothGatt { static jmethodID _method_writeCharacteristic; static jmethodID _method_writeDescriptor; static jmethodID _method_requestConnectionPriority; + static jmethodID _method_requestMtu; // JNI descriptor for auto-registration static const SimpleJNI::JNIDescriptor descriptor; static const SimpleJNI::AutoRegister registrar; diff --git a/third_party/SimpleBLE/simplecble/CMakeLists.txt b/third_party/SimpleBLE/simplecble/CMakeLists.txt index 75e20dce5..2a75a3593 100644 --- a/third_party/SimpleBLE/simplecble/CMakeLists.txt +++ b/third_party/SimpleBLE/simplecble/CMakeLists.txt @@ -40,6 +40,7 @@ add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../simpleble ${CMAKE_BINARY_DIR}/simp set(SIMPLEBLE_C_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/simplecble.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/android.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/adapter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/peripheral.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/logging.cpp diff --git a/third_party/SimpleBLE/simplecble/include/simplecble/android.h b/third_party/SimpleBLE/simplecble/include/simplecble/android.h new file mode 100644 index 000000000..930065192 --- /dev/null +++ b/third_party/SimpleBLE/simplecble/include/simplecble/android.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +SIMPLECBLE_EXPORT void simpleble_android_set_jvm(void* java_vm); + +#ifdef __cplusplus +} +#endif diff --git a/third_party/SimpleBLE/simplecble/src/adapter.cpp b/third_party/SimpleBLE/simplecble/src/adapter.cpp index b6a73d917..3cc478e3d 100644 --- a/third_party/SimpleBLE/simplecble/src/adapter.cpp +++ b/third_party/SimpleBLE/simplecble/src/adapter.cpp @@ -372,12 +372,16 @@ simpleble_err_t simpleble_adapter_set_callback_on_scan_updated( simpleble_err_t simpleble_adapter_set_callback_on_scan_found( simpleble_adapter_t handle, void (*callback)(simpleble_adapter_t, simpleble_peripheral_t, void*), void* userdata) { - if (handle == nullptr || callback == nullptr) { + if (handle == nullptr) { return SIMPLEBLE_FAILURE; } SimpleBLE::Adapter* adapter = (SimpleBLE::Adapter*)handle; try { + if (callback == nullptr) { + adapter->set_callback_on_scan_found(nullptr); + return SIMPLEBLE_SUCCESS; + } adapter->set_callback_on_scan_found([=](SimpleBLE::Peripheral peripheral) { SimpleBLE::Peripheral* peripheral_handle = new SimpleBLE::Peripheral(peripheral); callback(handle, (simpleble_peripheral_t)peripheral_handle, userdata); @@ -386,4 +390,4 @@ simpleble_err_t simpleble_adapter_set_callback_on_scan_found( } catch (...) { return SIMPLEBLE_FAILURE; } -} \ No newline at end of file +} diff --git a/third_party/SimpleBLE/simplecble/src/android.cpp b/third_party/SimpleBLE/simplecble/src/android.cpp new file mode 100644 index 000000000..e75a4e557 --- /dev/null +++ b/third_party/SimpleBLE/simplecble/src/android.cpp @@ -0,0 +1,14 @@ +#include + +#if defined(__ANDROID__) +#include +#include +#endif + +void simpleble_android_set_jvm(void* java_vm) { +#if defined(__ANDROID__) + SimpleBLE::Advanced::Android::set_jvm(static_cast(java_vm)); +#else + (void)java_vm; +#endif +} diff --git a/third_party/SimpleBLE/simpledroidbridge/src/main/java/org/simpleble/android/bridge/BluetoothGattCallback.java b/third_party/SimpleBLE/simpledroidbridge/src/main/java/org/simpleble/android/bridge/BluetoothGattCallback.java index e9ac75eeb..435468c5e 100644 --- a/third_party/SimpleBLE/simpledroidbridge/src/main/java/org/simpleble/android/bridge/BluetoothGattCallback.java +++ b/third_party/SimpleBLE/simpledroidbridge/src/main/java/org/simpleble/android/bridge/BluetoothGattCallback.java @@ -11,31 +11,29 @@ public BluetoothGattCallback() {} @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - // NOTE: This method has been deprecated on API 33, but we're still using API 31, so we need to support this. + // NOTE: This method is deprecated on API 33, but older Android versions still call it. super.onCharacteristicChanged(gatt, characteristic); onCharacteristicChangedCallback(gatt, characteristic, characteristic.getValue()); } - // @Override - // public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) { - // // NOTE: This method is only available from API 33 onwards. - // super.onCharacteristicChanged(gatt, characteristic, value); - // onCharacteristicChangedCallback(gatt, characteristic, value); - // } + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) { + // The API 33 default implementation forwards to the deprecated overload. + onCharacteristicChangedCallback(gatt, characteristic, value); + } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - // NOTE: This method has been deprecated on API 33, but we're still using API 31, so we need to support this. + // NOTE: This method is deprecated on API 33, but older Android versions still call it. super.onCharacteristicRead(gatt, characteristic, status); onCharacteristicReadCallback(gatt, characteristic, characteristic.getValue(), status); } - // @Override - // public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value, int status) { - // // NOTE: This method is only available from API 33 onwards. - // super.onCharacteristicRead(gatt, characteristic, value, status); - // onCharacteristicReadCallback(gatt, characteristic, value, status); - // } + @Override + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value, int status) { + // The API 33 default implementation forwards to the deprecated overload. + onCharacteristicReadCallback(gatt, characteristic, value, status); + } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { @@ -49,20 +47,18 @@ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState onConnectionStateChangeCallback(gatt, status, newState); } - // NOTE: This method is only available from API 33 onwards @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - // NOTE: This method has been deprecated on API 33, but we're still using API 31, so we need to support this. + // NOTE: This method is deprecated on API 33, but older Android versions still call it. super.onDescriptorRead(gatt, descriptor, status); onDescriptorReadCallback(gatt, descriptor, descriptor.getValue(), status); } - // @Override - // public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status, byte[] value) { - // // NOTE: This method is only available from API 33 onwards. - // super.onDescriptorRead(gatt, descriptor, status, value); - // onDescriptorReadCallback(gatt, descriptor, value, status); - // } + @Override + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status, byte[] value) { + // The API 33 default implementation forwards to the deprecated overload. + onDescriptorReadCallback(gatt, descriptor, value, status); + } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { diff --git a/tools/build_android_aar.py b/tools/build_android_aar.py new file mode 100644 index 000000000..6b8167896 --- /dev/null +++ b/tools/build_android_aar.py @@ -0,0 +1,205 @@ +import argparse +import os +import shutil +import subprocess +import sys +from pathlib import Path + + +ANDROID_ABIS = ('arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64') +DEFAULT_ANDROID_API_LEVEL = 'android-31' + + +def run_command(cmd, cwd): + print('Running command: %s' % ' '.join(str(item) for item in cmd), flush=True) + subprocess.check_call([str(item) for item in cmd], cwd=str(cwd)) + + +def resolve_executable(name): + resolved = shutil.which(name) + if resolved is None: + raise RuntimeError('required executable not found in PATH: %s' % name) + return resolved + + +def resolve_android_sdk_root(value): + candidates = [ + value, + os.environ.get('ANDROID_SDK_ROOT'), + os.environ.get('ANDROID_HOME'), + ] + for candidate in candidates: + if candidate: + path = Path(candidate).resolve() + if path.is_dir(): + return path + raise RuntimeError('Android SDK not found. Set ANDROID_SDK_ROOT or pass --android-sdk-root.') + + +def resolve_android_jar(android_sdk_root, android_api_level): + requested = android_sdk_root / 'platforms' / android_api_level / 'android.jar' + if requested.is_file(): + return requested, android_api_level + + platforms = android_sdk_root / 'platforms' + available = sorted( + [path for path in platforms.glob('android-*') if (path / 'android.jar').is_file()], + key=lambda path: int(path.name.split('-')[1]) if path.name.split('-')[1].isdigit() else -1) + if available: + fallback = available[-1] + print('Requested %s was not found, using %s for bridge compilation.' % + (android_api_level, fallback.name), flush=True) + return fallback / 'android.jar', fallback.name + + raise RuntimeError('No Android platform android.jar found under %s' % platforms) + + +def resolve_ndk_root(android_sdk_root, value): + candidates = [ + value, + os.environ.get('ANDROID_NDK_ROOT'), + os.environ.get('ANDROID_NDK_HOME'), + os.environ.get('ANDROID_NDK'), + android_sdk_root / 'ndk' / '25.1.8937393', + ] + ndk_dir = android_sdk_root / 'ndk' + if ndk_dir.is_dir(): + versions = sorted([path for path in ndk_dir.iterdir() if path.is_dir()]) + candidates.extend(reversed(versions)) + + for candidate in candidates: + if candidate: + path = Path(candidate).resolve() + if (path / 'build' / 'cmake' / 'android.toolchain.cmake').is_file(): + return path + raise RuntimeError('Android NDK not found. Set ANDROID_NDK_ROOT or pass --ndk-root.') + + +def resolve_ninja(root): + path = shutil.which('ninja') + if path is not None: + return Path(path) + local_ninja = root / 'tools' / ('ninja.exe' if os.name == 'nt' else 'ninja') + if local_ninja.is_file(): + return local_ninja + raise RuntimeError('Ninja not found in PATH or tools directory.') + + +def build_native(args, root, android_sdk_root): + ndk_root = resolve_ndk_root(android_sdk_root, args.ndk_root) + ninja = resolve_ninja(root) + toolchain = ndk_root / 'build' / 'cmake' / 'android.toolchain.cmake' + build_root = args.native_build_root.resolve() + + for abi in args.abis: + build_dir = build_root / abi + build_dir.mkdir(parents=True, exist_ok=True) + + cmake_cmd = [ + resolve_executable('cmake'), + '-G', 'Ninja', + '-S', root, + '-B', build_dir, + '-DCMAKE_MAKE_PROGRAM=%s' % ninja, + '-DCMAKE_TOOLCHAIN_FILE=%s' % toolchain, + '-DANDROID_ABI=%s' % abi, + '-DCMAKE_BUILD_TYPE=Release', + '-DANDROID_NATIVE_API_LEVEL=%s' % args.android_api_level, + '-DBUILD_SYNCHRONI_SDK=OFF', + ] + if args.use_libftdi: + cmake_cmd.append('-DUSE_LIBFTDI=ON') + if args.cmake_find_root_path: + cmake_cmd.append('-DCMAKE_FIND_ROOT_PATH=%s' % args.cmake_find_root_path) + for define in args.cmake_define: + cmake_cmd.append('-D%s' % define) + + run_command(cmake_cmd, root) + run_command( + [resolve_executable('cmake'), '--build', build_dir, '--parallel', str(args.jobs)], root) + + +def build_java(root): + run_command( + [resolve_executable('mvn'), '-q', '-f', root / 'java_package' / 'brainflow' / 'pom.xml', + '-Pandroid', '-DskipTests', '-Dmaven.compiler.source=8', '-Dmaven.compiler.target=8', + 'clean', 'package'], + root) + + +def build_simpleble_bridge(root, android_jar): + source_root = root / 'third_party' / 'SimpleBLE' / 'simpledroidbridge' / 'src' / 'main' / 'java' + source_files = sorted(source_root.rglob('*.java')) + if not source_files: + raise RuntimeError('No SimpleBLE Android bridge Java sources found under %s' % source_root) + + classes_dir = root / 'tools' / 'simpleble-bridge-classes' + sources_file = root / 'tools' / 'simpleble-bridge-sources.txt' + bridge_jar = root / 'tools' / 'simpleble-bridge.jar' + + if classes_dir.exists(): + shutil.rmtree(classes_dir) + classes_dir.mkdir(parents=True) + sources_file.write_text( + '\n'.join(str(path) for path in source_files) + '\n', encoding='utf-8') + + run_command([ + resolve_executable('javac'), '-source', '8', '-target', '8', '-classpath', android_jar, + '-d', classes_dir, '@%s' % sources_file + ], root) + run_command([resolve_executable('jar'), 'cf', bridge_jar, '-C', classes_dir, '.'], root) + + +def package_aar(args, root): + cmd = [ + sys.executable, + root / 'tools' / 'package_android_aar.py', + '--output', + args.output.resolve(), + '--min-sdk', + args.min_sdk, + ] + if args.allow_missing_abis: + cmd.append('--allow-missing-abis') + run_command(cmd, root) + + +def parse_args(): + root = Path(__file__).resolve().parents[1] + parser = argparse.ArgumentParser(description='Build BrainFlow Android AAR.') + parser.add_argument('--android-sdk-root', type=Path) + parser.add_argument('--android-api-level', default=os.environ.get( + 'ANDROID_API_LEVEL', DEFAULT_ANDROID_API_LEVEL)) + parser.add_argument('--min-sdk', default='31') + parser.add_argument('--output', type=Path, default=root / 'tools' / 'brainflow-android.aar') + parser.add_argument('--allow-missing-abis', action='store_true') + parser.add_argument('--build-native', action='store_true', + help='Build native Android libraries before packaging.') + parser.add_argument('--abis', nargs='+', choices=ANDROID_ABIS, default=list(ANDROID_ABIS)) + parser.add_argument('--native-build-root', type=Path, default=root / 'build_android_aar') + parser.add_argument('--ndk-root', type=Path) + parser.add_argument('--jobs', type=int, default=2) + parser.add_argument('--use-libftdi', action='store_true') + parser.add_argument('--cmake-find-root-path') + parser.add_argument('--cmake-define', action='append', default=[], + help='Extra CMake definition without leading -D, for example FOO=ON.') + return parser.parse_args() + + +def main(): + args = parse_args() + root = Path(__file__).resolve().parents[1] + android_sdk_root = resolve_android_sdk_root(args.android_sdk_root) + android_jar, android_platform = resolve_android_jar(android_sdk_root, args.android_api_level) + print('Using Android SDK: %s' % android_sdk_root, flush=True) + print('Using Android platform for bridge: %s' % android_platform, flush=True) + + if args.build_native: + build_native(args, root, android_sdk_root) + build_java(root) + build_simpleble_bridge(root, android_jar) + package_aar(args, root) + + +if __name__ == '__main__': + main() diff --git a/tools/package_android_aar.py b/tools/package_android_aar.py new file mode 100644 index 000000000..454f5eb1d --- /dev/null +++ b/tools/package_android_aar.py @@ -0,0 +1,126 @@ +import argparse +import shutil +import tempfile +import zipfile +from pathlib import Path + + +ANDROID_ABIS = ('arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64') +REQUIRED_NATIVE_LIBS = ('libBoardController.so', 'libsimpleble-c.so') + + +def copy_file(src, dst): + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, dst) + + +def copy_android_classes_jar(src, dst): + dst.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(src, 'r') as src_zip: + with zipfile.ZipFile(dst, 'w', zipfile.ZIP_DEFLATED) as dst_zip: + for info in src_zip.infolist(): + name = info.filename + if name == 'module-info.class' or name.endswith('/module-info.class'): + continue + if name.startswith('META-INF/versions/'): + continue + if name.startswith('com/sun/jna/platform/'): + continue + dst_zip.writestr(info, src_zip.read(info.filename)) + + +def write_manifest(path, package_name, min_sdk): + path.write_text( + ''' + + + + + + + +'''.format(package_name=package_name, min_sdk=min_sdk), + encoding='utf-8') + + +def add_directory_to_zip(zip_file, directory): + for path in sorted(directory.rglob('*')): + if path.is_file(): + zip_file.write(path, path.relative_to(directory).as_posix()) + + +def parse_args(): + root = Path(__file__).resolve().parents[1] + parser = argparse.ArgumentParser(description='Package BrainFlow Android AAR.') + parser.add_argument('--classes-jar', type=Path, + default=root / 'java_package' / 'brainflow' / 'target' / + 'brainflow-jar-with-dependencies.jar') + parser.add_argument('--bridge-jar', type=Path, + default=root / 'tools' / 'simpleble-bridge.jar') + parser.add_argument('--jni-libs', type=Path, + default=root / 'tools' / 'jniLibs') + parser.add_argument('--output', type=Path, + default=root / 'tools' / 'brainflow-android.aar') + parser.add_argument('--package-name', default='org.brainflow') + parser.add_argument('--min-sdk', default='31') + parser.add_argument('--allow-missing-abis', action='store_true', + help='Package only ABI folders present under jniLibs.') + return parser.parse_args() + + +def main(): + args = parse_args() + classes_jar = args.classes_jar.resolve() + bridge_jar = args.bridge_jar.resolve() + jni_libs = args.jni_libs.resolve() + output = args.output.resolve() + + if not classes_jar.is_file(): + raise RuntimeError('classes jar not found: %s' % classes_jar) + if not bridge_jar.is_file(): + raise RuntimeError('SimpleBLE bridge jar not found: %s' % bridge_jar) + if not jni_libs.is_dir(): + raise RuntimeError('jniLibs directory not found: %s' % jni_libs) + + missing_abis = [] + packaged_abis = [] + for abi in ANDROID_ABIS: + abi_dir = jni_libs / abi + if not abi_dir.is_dir(): + missing_abis.append(abi) + continue + missing_libs = [lib for lib in REQUIRED_NATIVE_LIBS if not (abi_dir / lib).is_file()] + if missing_libs: + if args.allow_missing_abis: + missing_abis.append(abi) + continue + raise RuntimeError('ABI %s is missing required libraries: %s' % + (abi, ', '.join(missing_libs))) + packaged_abis.append(abi) + + if not packaged_abis: + raise RuntimeError('no Android ABI directories found in %s' % jni_libs) + if missing_abis and not args.allow_missing_abis: + raise RuntimeError('missing Android ABI directories: %s' % + ', '.join(missing_abis)) + + output.parent.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory() as tmp_dir_name: + tmp_dir = Path(tmp_dir_name) + write_manifest(tmp_dir / 'AndroidManifest.xml', args.package_name, args.min_sdk) + copy_android_classes_jar(classes_jar, tmp_dir / 'classes.jar') + copy_file(bridge_jar, tmp_dir / 'libs' / 'simpleble-bridge.jar') + (tmp_dir / 'R.txt').write_text('', encoding='utf-8') + + for abi in packaged_abis: + for native_lib in sorted((jni_libs / abi).glob('*.so')): + copy_file(native_lib, tmp_dir / 'jni' / abi / native_lib.name) + + with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as zip_file: + add_directory_to_zip(zip_file, tmp_dir) + + print('Packaged %s with ABIs: %s' % (output, ', '.join(packaged_abis)), flush=True) + + +if __name__ == '__main__': + main()