From 86642a331d4f3a2d0a38a363126351deae443fb9 Mon Sep 17 00:00:00 2001 From: Kevin Dewald <5274600+kdewald@users.noreply.github.com> Date: Sun, 16 Oct 2022 09:31:16 -0700 Subject: [PATCH] Fixed broken examples, added unit tests. (#123) * Fixed broken examples, added unit tests. * Linting * More fixes * Fixed PyTest execution * Fixed PyTest execution * Fixed PyTest execution * Fixed uninitialized memory --- .github/workflows/ci_test.yml | 28 +++++++++++- .gitignore | 1 + docs/changelog.rst | 4 ++ examples/simplepyble/connect.py | 6 +-- examples/simplepyble/notify.py | 4 +- examples/simplepyble/read.py | 4 +- examples/simplepyble/write.py | 4 +- simpleble/CMakeLists.txt | 3 +- simpleble/src/plain/AdapterBase.cpp | 9 ++-- simpleble/src/plain/AdapterBase.h | 2 +- simpleble/src/plain/PeripheralBase.cpp | 6 ++- simpleble/src/plain/PeripheralBase.h | 7 ++- simplepyble/requirements.txt | 4 ++ simplepyble/setup.py | 17 +++++++ simplepyble/test/test_simpleble.py | 62 ++++++++++++++++++++++++++ utils/build_lib.sh | 2 +- utils/build_py.sh | 27 +++++++++-- 17 files changed, 161 insertions(+), 29 deletions(-) create mode 100644 simplepyble/test/test_simpleble.py diff --git a/.github/workflows/ci_test.yml b/.github/workflows/ci_test.yml index 203c0b94..c977b7dc 100644 --- a/.github/workflows/ci_test.yml +++ b/.github/workflows/ci_test.yml @@ -144,4 +144,30 @@ jobs: cmake --build $GITHUB_WORKSPACE/build_tsan_simpledbus --config Release --parallel 4 $GITHUB_WORKSPACE/build_tsan_simpledbus/bin/simpledbus_test cp "$(ls tsan_log.txt.* | head -1)" tsan_log.txt || true - (test ! -f tsan_log.txt && echo "No TSAN log found") || (cat tsan_log.txt && exit 1) \ No newline at end of file + (test ! -f tsan_log.txt && echo "No TSAN log found") || (cat tsan_log.txt && exit 1) + + # ------------------------------------------------------------ + + test-python: + runs-on: "ubuntu-20.04" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + cache: "pip" + + - name: Install dependencies + run: pip install -r simplepyble/requirements.txt + + - name: Install SimplePyBLE with Plain flavor + run: python setup.py install --plain + working-directory: ./simplepyble + + - name: Run PyTest + run: pytest + working-directory: ./simplepyble/test \ No newline at end of file diff --git a/.gitignore b/.gitignore index c5c886bd..b3c94aec 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ _doxygen /build_* simplepyble/build +simplepyble/dist __pycache__ *.egg-info diff --git a/docs/changelog.rst b/docs/changelog.rst index b0565ced..80469259 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,6 +13,10 @@ The format is based on `Keep a Changelog`_, and this project adheres to - Option to build SimpleBLE plain-flavored (without any BLE code) for testing and debugging purposes. +**Fixed** + +- Fixed incorrect handling of services and characteristics in the Python examples. *(Thanks Carl-CWX!)* + [0.5.0] - 2022-09-25 -------------------- diff --git a/examples/simplepyble/connect.py b/examples/simplepyble/connect.py index fb75c12a..78ef3e51 100644 --- a/examples/simplepyble/connect.py +++ b/examples/simplepyble/connect.py @@ -38,8 +38,8 @@ print("Successfully connected, listing services...") services = peripheral.services() for service in services: - print(f"Service: {service.uuid}") - for characteristic in service.characteristics: - print(f" Characteristic: {characteristic}") + print(f"Service: {service.uuid()}") + for characteristic in service.characteristics(): + print(f" Characteristic: {characteristic.uuid()}") peripheral.disconnect() diff --git a/examples/simplepyble/notify.py b/examples/simplepyble/notify.py index 17d657cd..245b6960 100644 --- a/examples/simplepyble/notify.py +++ b/examples/simplepyble/notify.py @@ -40,8 +40,8 @@ services = peripheral.services() service_characteristic_pair = [] for service in services: - for characteristic in service.characteristics: - service_characteristic_pair.append((service.uuid, characteristic)) + for characteristic in service.characteristics(): + service_characteristic_pair.append((service.uuid(), characteristic.uuid())) # Query the user to pick a service/characteristic pair print("Please select a service/characteristic pair:") diff --git a/examples/simplepyble/read.py b/examples/simplepyble/read.py index 9b52c070..e75b08eb 100644 --- a/examples/simplepyble/read.py +++ b/examples/simplepyble/read.py @@ -39,8 +39,8 @@ services = peripheral.services() service_characteristic_pair = [] for service in services: - for characteristic in service.characteristics: - service_characteristic_pair.append((service.uuid, characteristic)) + for characteristic in service.characteristics(): + service_characteristic_pair.append((service.uuid(), characteristic.uuid())) # Query the user to pick a service/characteristic pair print("Please select a service/characteristic pair:") diff --git a/examples/simplepyble/write.py b/examples/simplepyble/write.py index f8b54735..31e49f0c 100644 --- a/examples/simplepyble/write.py +++ b/examples/simplepyble/write.py @@ -39,8 +39,8 @@ services = peripheral.services() service_characteristic_pair = [] for service in services: - for characteristic in service.characteristics: - service_characteristic_pair.append((service.uuid, characteristic)) + for characteristic in service.characteristics(): + service_characteristic_pair.append((service.uuid(), characteristic.uuid())) # Query the user to pick a service/characteristic pair print("Please select a service/characteristic pair:") diff --git a/simpleble/CMakeLists.txt b/simpleble/CMakeLists.txt index cf5aa975..0590ce85 100644 --- a/simpleble/CMakeLists.txt +++ b/simpleble/CMakeLists.txt @@ -309,6 +309,5 @@ if(SIMPLEBLE_TEST) POSITION_INDEPENDENT_CODE ON WINDOWS_EXPORT_ALL_SYMBOLS ON) - target_link_libraries(simpleble_test PRIVATE simpleble::simpleble ${GTEST_LIBRARIES}) - target_include_directories(simpleble_test PRIVATE ${GTEST_INCLUDE_DIRS}) + target_link_libraries(simpleble_test PRIVATE simpleble::simpleble GTest::gtest) endif() diff --git a/simpleble/src/plain/AdapterBase.cpp b/simpleble/src/plain/AdapterBase.cpp index 4df532d5..e7ab8d90 100644 --- a/simpleble/src/plain/AdapterBase.cpp +++ b/simpleble/src/plain/AdapterBase.cpp @@ -13,19 +13,17 @@ std::vector> AdapterBase::get_adapters() { return adapter_list; } -bool AdapterBase::bluetooth_enabled() { - return true; -} +bool AdapterBase::bluetooth_enabled() { return true; } AdapterBase::AdapterBase() {} -AdapterBase::~AdapterBase() { } +AdapterBase::~AdapterBase() {} void* AdapterBase::underlying() const { return nullptr; } std::string AdapterBase::identifier() { return "Plain Adapter"; } -BluetoothAddress AdapterBase::address() { return "00:00:00:00:00:00"; } +BluetoothAddress AdapterBase::address() { return "AA:BB:CC:DD:EE:FF"; } void AdapterBase::scan_start() { is_scanning_ = true; @@ -34,7 +32,6 @@ void AdapterBase::scan_start() { PeripheralBuilder peripheral_builder(std::make_shared()); SAFE_CALLBACK_CALL(this->callback_on_scan_found_, peripheral_builder); SAFE_CALLBACK_CALL(this->callback_on_scan_updated_, peripheral_builder); - } void AdapterBase::scan_stop() { diff --git a/simpleble/src/plain/AdapterBase.h b/simpleble/src/plain/AdapterBase.h index 0a311043..1261e865 100644 --- a/simpleble/src/plain/AdapterBase.h +++ b/simpleble/src/plain/AdapterBase.h @@ -42,7 +42,7 @@ class AdapterBase { static std::vector> get_adapters(); private: - std::atomic_bool is_scanning_; + std::atomic_bool is_scanning_{false}; kvn::safe_callback callback_on_scan_start_; kvn::safe_callback callback_on_scan_stop_; diff --git a/simpleble/src/plain/PeripheralBase.cpp b/simpleble/src/plain/PeripheralBase.cpp index 0e94ac39..ca525235 100644 --- a/simpleble/src/plain/PeripheralBase.cpp +++ b/simpleble/src/plain/PeripheralBase.cpp @@ -23,9 +23,9 @@ void* PeripheralBase::underlying() const { return nullptr; } std::string PeripheralBase::identifier() { return "Plain Peripheral"; } -BluetoothAddress PeripheralBase::address() { return "00:00:00:00:00:00"; } +BluetoothAddress PeripheralBase::address() { return "11:22:33:44:55:66"; } -int16_t PeripheralBase::rssi() { return -127; } +int16_t PeripheralBase::rssi() { return -60; } void PeripheralBase::connect() { connected_ = true; @@ -46,6 +46,8 @@ bool PeripheralBase::is_paired() { return paired_; } void PeripheralBase::unpair() { paired_ = false; } std::vector PeripheralBase::services() { + if (!connected_) return {}; + std::vector service_list; service_list.push_back( diff --git a/simpleble/src/plain/PeripheralBase.h b/simpleble/src/plain/PeripheralBase.h index 7a2f4d86..c00bdd65 100644 --- a/simpleble/src/plain/PeripheralBase.h +++ b/simpleble/src/plain/PeripheralBase.h @@ -8,8 +8,8 @@ #include #include -#include #include +#include namespace SimpleBLE { @@ -50,9 +50,8 @@ class PeripheralBase { void set_callback_on_disconnected(std::function on_disconnected); private: - - std::atomic_bool connected_; - std::atomic_bool paired_; + std::atomic_bool connected_{false}; + std::atomic_bool paired_{false}; kvn::safe_callback callback_on_connected_; kvn::safe_callback callback_on_disconnected_; diff --git a/simplepyble/requirements.txt b/simplepyble/requirements.txt index 96077a2f..e3142d4c 100644 --- a/simplepyble/requirements.txt +++ b/simplepyble/requirements.txt @@ -1,3 +1,7 @@ twine +wheel +ninja +pytest pybind11 +setuptools cibuildwheel==2.9 diff --git a/simplepyble/setup.py b/simplepyble/setup.py index 152875db..2dd35991 100644 --- a/simplepyble/setup.py +++ b/simplepyble/setup.py @@ -1,6 +1,12 @@ import pathlib import sys import setuptools +import argparse + +argparser = argparse.ArgumentParser(add_help=False) +argparser.add_argument('--plain', help='Use Plain SimpleBLE', required=False, action='store_true') +args, unknown = argparser.parse_known_args() +sys.argv = [sys.argv[0]] + unknown here = pathlib.Path(__file__).parent.resolve() root = here.parent.resolve() @@ -26,6 +32,9 @@ cmake_options.append(f"-DPYTHON_EXECUTABLE={sys.executable}") cmake_options.append(f"-DSIMPLEPYBLE_VERSION={version_str}") +if args.plain: + cmake_options.append("-DSIMPLEBLE_PLAIN=ON") + # The information here can also be placed in setup.cfg - better separation of # logic and declaration, and simpler if you include description/version in a file. setuptools.setup( @@ -51,6 +60,14 @@ "build_ext": cmake_build_extension.BuildExtension }, zip_safe=False, + install_requires=[ + "wheel", + "pybind11", + "ninja" + ], + test_requires=[ + "pytest", + ], extras_require={}, platforms="Windows, macOS, Linux", python_requires=">=3.7", diff --git a/simplepyble/test/test_simpleble.py b/simplepyble/test/test_simpleble.py new file mode 100644 index 00000000..5f80e4c5 --- /dev/null +++ b/simplepyble/test/test_simpleble.py @@ -0,0 +1,62 @@ +# Note: This test suite is only evaluating the Python bindings, not the C++ library. +# The SimpleBLE implementation to test this on is the PLAIN version. +import simplepyble + + +def test_get_adapters(): + assert simplepyble.Adapter.bluetooth_enabled() == True + + adapters = simplepyble.Adapter.get_adapters() + assert len(adapters) == 1 + + adapter = adapters[0] + assert adapter.identifier() == "Plain Adapter" + assert adapter.address() == "AA:BB:CC:DD:EE:FF" + + +def test_scan_blocking(): + adapter = simplepyble.Adapter.get_adapters()[0] + + adapter.scan_for(1) + peripherals = adapter.scan_get_results() + assert len(peripherals) == 1 + + peripheral = peripherals[0] + assert peripheral.identifier() == "Plain Peripheral" + assert peripheral.address() == "11:22:33:44:55:66" + assert peripheral.rssi() == -60 + assert peripheral.is_connected() == False + assert peripheral.is_paired() == False + + +def test_scan_async(): + # TODO: Implement once we have proper callback and advertising emulation. + pass + + +def test_connect(): + adapter = simplepyble.Adapter.get_adapters()[0] + + adapter.scan_for(1) + peripherals = adapter.scan_get_results() + peripheral = peripherals[0] + + peripheral.connect() + assert peripheral.is_connected() == True + assert peripheral.is_paired() == True + + services = peripheral.services() + assert len(services) == 1 + + service = services[0] + assert service.uuid() == "0000180f-0000-1000-8000-00805f9b34fb" + + characteristics = service.characteristics() + assert len(characteristics) == 1 + + characteristic = characteristics[0] + assert characteristic.uuid() == "00002a19-0000-1000-8000-00805f9b34fb" + + peripheral.disconnect() + assert peripheral.is_connected() == False + assert peripheral.is_paired() == True \ No newline at end of file diff --git a/utils/build_lib.sh b/utils/build_lib.sh index 50b97408..b56c83b9 100755 --- a/utils/build_lib.sh +++ b/utils/build_lib.sh @@ -126,7 +126,7 @@ if [[ ! -z "$FLAG_CLEAN" ]]; then rm -rf $EXAMPLE_BUILD_PATH fi -cmake -H$SOURCE_PATH -B $BUILD_PATH $BUILD_TEST_ARG $BUILD_SANITIZE_ADDRESS_ARG $BUILD_SANITIZE_THREAD_ARG $BUILD_SHARED_ARG $EXTRA_BUILD_ARGS $BUILD_PLAIN +cmake -H$SOURCE_PATH -B $BUILD_PATH $BUILD_TEST_ARG $BUILD_SANITIZE_ADDRESS_ARG $BUILD_SANITIZE_THREAD_ARG $BUILD_SHARED_ARG $BUILD_PLAIN $EXTRA_BUILD_ARGS cmake --build $BUILD_PATH -j7 cmake --install $BUILD_PATH --prefix "${INSTALL_PATH}" diff --git a/utils/build_py.sh b/utils/build_py.sh index 70ce02c0..9c98790c 100755 --- a/utils/build_py.sh +++ b/utils/build_py.sh @@ -30,6 +30,14 @@ while (( "$#" )); do FLAG_CLEAN=0 shift ;; + -p|--plain) + FLAG_PLAIN=0 + shift + ;; + -i|--install) + FLAG_INSTALL=0 + shift + ;; -*|--*=) # unsupported flags echo "Error: Unsupported flag $1" >&2 exit 1 @@ -46,13 +54,26 @@ eval set -- "$PARAMS" PROJECT_ROOT=$(realpath $(dirname `realpath $0`)/..) SOURCE_PATH=$PROJECT_ROOT/simplepyble -BUILD_PATH=$PROJECT_ROOT/build # Note: setup.py will append the project name to the build path +BUILD_PATH=$PROJECT_ROOT/build_simplepyble # Note: setup.py will append the project name to the build path +DIST_PATH=$BUILD_PATH/dist # If FLAG_CLEAN is set, clean the build directory if [[ ! -z "$FLAG_CLEAN" ]]; then rm -rf "$BUILD_PATH"_simplepyble fi +if [[ ! -z "$FLAG_PLAIN" ]]; then + BUILD_PLAIN="--plain" +fi + +SETUP_INSTRUCTIONS="build --build-base $BUILD_PATH" +SETUP_INSTRUCTIONS="$SETUP_INSTRUCTIONS egg_info --egg-base $BUILD_PATH" +SETUP_INSTRUCTIONS="$SETUP_INSTRUCTIONS bdist_wheel --dist-dir $DIST_PATH" + cd $SOURCE_PATH -python3 setup.py build --build-temp $BUILD_PATH -cd - \ No newline at end of file +python3 setup.py $SETUP_INSTRUCTIONS $BUILD_PLAIN +cd - + +if [[ ! -z "$FLAG_INSTALL" ]]; then + pip3 install $DIST_PATH/*.whl --force-reinstall +fi \ No newline at end of file