├── .gitignore ├── .cargo └── config.toml ├── rust-toolchain.toml ├── examples ├── python │ ├── README.md │ ├── rainbow.py │ └── main.py └── cpp │ ├── README.md │ ├── rainbow.cpp │ ├── CMakeLists.txt │ ├── CMakeLists_Standalone.txt │ └── simple.cpp ├── Cargo.toml ├── src ├── build.rs └── lib.rs ├── LICENSE ├── docs └── source │ ├── conf.py │ └── index.rst ├── pyproject.toml ├── README.md └── .github └── workflows └── action.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | macros/target 3 | macros/Cargo.lock 4 | build 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all())'] 2 | rustflags = ["-Ctarget-feature=-crt-static"] 3 | 4 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | # Necessary for cbindgen work with macro expansion 3 | channel = "nightly-2023-05-25" 4 | -------------------------------------------------------------------------------- /examples/python/README.md: -------------------------------------------------------------------------------- 1 | # How to use 2 | 3 | ```shell 4 | pip install maturin --user 5 | pip install ../.. --user 6 | python main.py 7 | ``` 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bluerobotics_navigator" 3 | version = "0.1.2" 4 | edition = "2021" 5 | build = "src/build.rs" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | cpy-binder = "1.0" 12 | libc = "0.2" 13 | pyo3 = { version = "0.18", features = ["extension-module", "abi3-py39"], optional = true } 14 | navigator-rs = { version = "0.6.0" } 15 | rand = "0.8" 16 | lazy_static = "1.4.0" 17 | 18 | [build-dependencies] 19 | cbindgen = "0.24" 20 | 21 | [features] 22 | python = ["pyo3"] 23 | -------------------------------------------------------------------------------- /src/build.rs: -------------------------------------------------------------------------------- 1 | extern crate cbindgen; 2 | 3 | fn main() { 4 | let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); 5 | let target_dir = std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "target".to_string()); 6 | let profile = std::env::var("PROFILE").unwrap_or_else(|_| "debug".to_string()); 7 | let bindings_file = std::path::Path::new(&target_dir) 8 | .join(profile) 9 | .join("bindings.h"); 10 | cbindgen::Builder::new() 11 | .with_crate(crate_dir) 12 | .with_parse_deps(false) 13 | .with_language(cbindgen::Language::Cxx) 14 | .with_parse_expand(&["bluerobotics_navigator"]) 15 | .generate() 16 | .expect("Unable to generate bindings") 17 | .write_to_file(bindings_file); 18 | } 19 | -------------------------------------------------------------------------------- /examples/cpp/README.md: -------------------------------------------------------------------------------- 1 | # How to use 2 | 3 | ## Running example on BlueOS (red-pill) 4 | 5 | ```shell 6 | # Prepare the environment with cmake and cargo 7 | sudo apt-get update && sudo apt-get install cmake git 8 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 9 | source "$HOME/.cargo/env" 10 | 11 | # Clone and move to Cpp folder 12 | git clone https://github.com/bluerobotics/navigator-lib.git 13 | cd examples/cpp 14 | 15 | # Build with the following commands 16 | cmake -B build -DCMAKE_BUILD_TYPE=Debug && cmake --build build --config Debug --parallel 17 | 18 | # Run the binary 19 | ./build/simple 20 | 21 | ``` 22 | 23 | ## Using Standalone Version 24 | Rename the CMakeLists_Standalone to CMakeLists. 25 | By default Standalone project will use latest versions, but a version can be selected as follows: 26 | ```shell 27 | cmake -B build -DCMAKE_BUILD_TYPE=Debug -DNAVIGATOR_VERSION="0.0.1" && cmake --build build --config Debug --parallel 28 | ``` -------------------------------------------------------------------------------- /examples/cpp/rainbow.cpp: -------------------------------------------------------------------------------- 1 | #include "bindings.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void color_from_sine(float percentage, uint8_t (&ledValues)[3]) { 8 | float pi = M_PI; 9 | float red = std::sin(percentage * 2.0 * pi) * 0.5 + 0.5; 10 | float green = std::sin((percentage + 0.33) * 2.0 * pi) * 0.5 + 0.5; 11 | float blue = std::sin((percentage + 0.67) * 2.0 * pi) * 0.5 + 0.5; 12 | ledValues[0] = static_cast(red * 255.0); 13 | ledValues[1] = static_cast(green * 255.0); 14 | ledValues[2] = static_cast(blue * 255.0); 15 | } 16 | 17 | int main() { 18 | std::cout << "Creating rainbow effect!" << std::endl; 19 | uint8_t rgb_array[3]; 20 | while (true) { 21 | int steps = 1000; 22 | for (int i = 0; i <= steps; i++) { 23 | float ratio = static_cast(i) / static_cast(steps); 24 | color_from_sine(ratio, rgb_array); 25 | set_neopixel(&rgb_array, 1); 26 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 27 | } 28 | } 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Blue Robotics 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(example) 3 | 4 | include(ExternalProject) 5 | 6 | set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/Rust) 7 | set(RUST_PROJ_DIR "${CMAKE_SOURCE_DIR}/../..") 8 | set(RUST_OUT_DIR "${RUST_PROJ_DIR}/target/debug") 9 | 10 | add_custom_target(navigator 11 | COMMAND cargo build 12 | COMMENT "C binds generated: ${RUST_OUT_DIR}" 13 | ) 14 | 15 | if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") 16 | set(LIB_EXT "dylib") 17 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") 18 | set(LIB_EXT "dll") 19 | else() 20 | set(LIB_EXT "so") 21 | endif() 22 | 23 | # List of binaries 24 | set(BINARIES simple rainbow) 25 | 26 | foreach(BINARY ${BINARIES}) 27 | add_executable(${BINARY} ${BINARY}.cpp) 28 | add_dependencies(${BINARY} navigator) 29 | 30 | target_link_libraries( 31 | ${BINARY} 32 | "${RUST_OUT_DIR}/libbluerobotics_navigator.${LIB_EXT}" 33 | ) 34 | 35 | set_target_properties(${BINARY} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON) 36 | target_include_directories(${BINARY} PRIVATE "${RUST_OUT_DIR}") 37 | endforeach() 38 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | import bluerobotics_navigator 10 | 11 | project = 'bluerobotics-navigator' 12 | release = "0.0.0" # todo: navigator.__version__ 13 | version = release 14 | 15 | 16 | extensions = [ 17 | 'sphinx.ext.autodoc', 18 | 'sphinx.ext.autosummary', 19 | 'sphinx.ext.intersphinx', 20 | 'sphinx.ext.todo', 21 | 'sphinx.ext.inheritance_diagram', 22 | 'sphinx.ext.autosectionlabel', 23 | 'sphinx.ext.napoleon', 24 | 'sphinx_rtd_theme', 25 | ] 26 | 27 | autosummary_generate = True 28 | autosummary_imported_members = True 29 | 30 | templates_path = ['_templates'] 31 | exclude_patterns = [] 32 | 33 | # -- Options for HTML output ------------------------------------------------- 34 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 35 | 36 | html_theme = 'sphinx_rtd_theme' 37 | html_static_path = ['_static'] 38 | html_logo = "https://upload.wikimedia.org/wikipedia/commons/1/12/Bluerobotics-logo.svg" 39 | -------------------------------------------------------------------------------- /examples/python/rainbow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import bluerobotics_navigator as navigator 4 | import time 5 | import math 6 | 7 | 8 | def color_from_sine(percentage): 9 | pi = math.pi 10 | red = (math.sin(percentage * 2.0 * pi) * 0.5) + 0.5 11 | green = (math.sin((percentage + 0.33) * 2.0 * pi) * 0.5) + 0.5 12 | blue = (math.sin((percentage + 0.67) * 2.0 * pi) * 0.5) + 0.5 13 | return [ 14 | int(red * 255), 15 | int(green * 255), 16 | int(blue * 255), 17 | ] 18 | 19 | 20 | def main(): 21 | if platform.machine() == "aarch64": 22 | # It's possible to set the configuration before initializing the navigator, check this example 23 | from bluerobotics_navigator import NavigatorVersion, Raspberry 24 | print("Setting up for Navigator V2 on Raspberry Pi 5") 25 | navigator.set_rgb_led_strip_size(1) 26 | navigator.set_navigator_version(NavigatorVersion.Version2) 27 | navigator.set_raspberry_pi_version(Raspberry.Pi5) 28 | 29 | navigator.init() 30 | 31 | print("Creating rainbow effect!") 32 | while True: 33 | steps = 1000 34 | for i in range(steps): 35 | ratio = i / steps 36 | navigator.set_neopixel([color_from_sine(ratio)]) 37 | time.sleep(0.01) 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.0.1", "hatchling>=1.21.1"] 3 | build-backend = "hatchling.build" 4 | 5 | [tool.hatch.version] 6 | path = "Cargo.toml" 7 | 8 | [project] 9 | name = "bluerobotics_navigator" 10 | requires-python = ">=3.7" 11 | classifiers = [ 12 | "Framework :: Robot Framework", 13 | "Topic :: Scientific/Engineering", 14 | "Topic :: Software Development :: Embedded Systems", 15 | "Programming Language :: Rust", 16 | ] 17 | description = "This package serves as the entry point for embedded applications using Python on Blue Robotics's Navigator" 18 | readme = "README.md" 19 | dynamic = ["version"] 20 | 21 | [project.urls] 22 | Homepage = "https://bluerobotics.com/store/comm-control-power/control/navigator/" 23 | Documentation = "https://docs.bluerobotics.com/navigator-lib/python" 24 | Repository = "https://github.com/bluerobotics/navigator-lib" 25 | 26 | [tool.maturin] 27 | features = ["python"] 28 | 29 | [tool.hatch.build.targets.wheel] 30 | packages = ["bluerobotics_navigator"] 31 | 32 | [tool.hatch.envs.dev] 33 | dependencies = [ 34 | "maturin >=1.0.1", 35 | "sphinx >=6.2.1", 36 | "sphinx-rtd-theme >=1.2.2", 37 | "sphinxcontrib-napoleon >=0.7", 38 | "sphinx-pyproject >=0.1.0", 39 | "ziglang >=0.10.1", 40 | ] 41 | 42 | [tool.hatch.envs.dev.scripts] 43 | build = "maturin build" 44 | install = "maturin develop" 45 | build-doc = "install && sphinx-build ./docs/source docs/_build" 46 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. extension documentation master file, created by 2 | sphinx-quickstart on Mon Jun 19 15:02:05 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | BlueRobotics's Navigator Library 7 | ===================================== 8 | 9 | This library serves as the entry point for embedding applications using Python on Blue Robotics's Navigator_. 10 | 11 | The Navigator board has the Raspberry Pi HAT form factor, which allows you to easily attach it to a Raspberry Pi 4 board. Then you can unleash the power of Navigator to develop new solutions or interact with your ROV hardware. 12 | 13 | The board offers the following capabilities: 14 | 15 | Control: 16 | 17 | - LEDs 18 | 19 | - PWM (Pulse Width Modulation) with 16 channels 20 | 21 | Measurements: 22 | 23 | - ADC (Analog Digital Converter) entries 24 | 25 | - Magnetic field 26 | 27 | - Acceleration 28 | 29 | - Angular velocity 30 | 31 | - Temperature 32 | 33 | - Pressure 34 | 35 | Currently, it supports **armv7** and **aarch64** architectures, which are the official defaults for BlueOS_. 36 | However, despite using the embedded-hal concept, new ports can be added as long as the platform matches the hardware design and specifications. 37 | 38 | For more information, including installation instructions, schematics, and hardware specifications, please check the navigator hardware setup guide_. 39 | 40 | .. _Navigator: https://bluerobotics.com/store/comm-control-power/control/navigator/ 41 | .. _BlueOS: https://docs.bluerobotics.com/ardusub-zola/software/onboard/BlueOS-1.1/ 42 | .. _guide: https://bluerobotics.com/learn/navigator-hardware-setup/#introduction 43 | 44 | .. automodule:: bluerobotics_navigator 45 | :members: 46 | :undoc-members: 47 | -------------------------------------------------------------------------------- /examples/cpp/CMakeLists_Standalone.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(example) 3 | 4 | include(ExternalProject) 5 | 6 | if(NOT DEFINED NAVIGATOR_VERSION) 7 | set(ZIP_URL "https://github.com/bluerobotics/navigator-lib/releases/latest/download/cpp.zip") 8 | message(STATUS "Using Navigator lib version: latest") 9 | else() 10 | set(ZIP_URL "https://github.com/bluerobotics/navigator-lib/releases/download/${NAVIGATOR_VERSION}/cpp.zip") 11 | message(STATUS "Using Navigator lib version: ${NAVIGATOR_VERSION}") 12 | endif() 13 | 14 | set(ZIP_UNPACK_DIR "${CMAKE_BINARY_DIR}/navigator_zip_contents") 15 | message(STATUS "Identified system processor: ${CMAKE_SYSTEM_PROCESSOR}") 16 | 17 | # Download released navigator lib files 18 | ExternalProject_Add( 19 | navigator_zip 20 | URL ${ZIP_URL} 21 | DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/navigator_zip_download" 22 | SOURCE_DIR ${ZIP_UNPACK_DIR} 23 | # Since this is not a cmake project but just files, we need to cleanup the commands 24 | CONFIGURE_COMMAND "" 25 | BUILD_COMMAND "" 26 | INSTALL_COMMAND "" 27 | TEST_COMMAND "" 28 | ) 29 | 30 | # Adjust library path based on the system processor 31 | if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "armv7") 32 | set(LIB_PATH_SUBDIR "armv7-unknown-linux-gnueabihf") 33 | elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "armv8" OR ${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64") 34 | set(LIB_PATH_SUBDIR "aarch64-unknown-linux-gnu") 35 | else() 36 | message(FATAL_ERROR "Unsupported architecture") 37 | endif() 38 | 39 | set(LIBRARY_PATH "${ZIP_UNPACK_DIR}/${LIB_PATH_SUBDIR}/libbluerobotics_navigator.so") 40 | 41 | add_executable(simple simple.cpp) 42 | add_dependencies(simple navigator_zip) 43 | 44 | target_link_libraries(simple ${LIBRARY_PATH}) 45 | 46 | set_target_properties(simple PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON) 47 | target_include_directories(simple PRIVATE "${ZIP_UNPACK_DIR}") 48 | -------------------------------------------------------------------------------- /examples/cpp/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "bindings.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() { 9 | 10 | struct utsname uts; 11 | uname(&uts); 12 | printf("Navigator C test, system details:\n"); 13 | printf("System is %s on %s hardware\n", uts.sysname, uts.machine); 14 | printf("OS Release is %s\n", uts.release); 15 | printf("OS Version is %s\n", uts.version); 16 | 17 | const char *ci_env = std::getenv("CI"); 18 | if (ci_env && std::string(ci_env) == "true") { 19 | printf("Running from CI\n"); 20 | printf("Not possible to test navigator sensors yet.\n"); 21 | 22 | return 0; 23 | } 24 | 25 | printf("Initiating navigator module.\n"); 26 | // Possible to set the configuration before initializing the navigator 27 | // set_rgb_led_strip_size(1); 28 | // set_navigator_version(NavigatorVersion::Version2); 29 | // set_raspberry_pi_version(Raspberry::Pi5); 30 | init(); 31 | 32 | printf("Setting led on!\n"); 33 | set_led(UserLed::Led1, true); 34 | 35 | printf("Temperature: %f\n", read_temp()); 36 | 37 | printf("Pressure: %f\n", read_pressure()); 38 | 39 | printf("Leak sensor: %s\n", read_leak() ? "true" : "false"); 40 | 41 | float adc[4]; 42 | read_adc_all(adc, 4); 43 | printf("Reading ADC Channels: 1 = %f, 2 = %f, 3 = %f, 4 = %f\n", adc[0], 44 | adc[1], adc[2], adc[3]); 45 | 46 | printf("Data ADC Channels: 1 = %f\n", read_adc(AdcChannel::Ch1)); 47 | 48 | AxisData mag = read_mag(); 49 | printf("Magnetic field: X = %f, Y = %f, Z = %f\n", mag.x, mag.y, mag.z); 50 | 51 | AxisData accel = read_accel(); 52 | printf("Acceleration: X = %f, Y = %f, Z = %f\n", accel.x, accel.y, accel.z); 53 | 54 | AxisData gyro = read_gyro(); 55 | printf("Gyroscope: X = %f, Y = %f, Z = %f\n", gyro.x, gyro.y, gyro.z); 56 | 57 | printf("Setting led off!\n"); 58 | set_led(UserLed::Led1, false); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /examples/python/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import platform 5 | import bluerobotics_navigator as navigator 6 | from bluerobotics_navigator import AdcChannel, UserLed 7 | 8 | 9 | def navigator_check(): 10 | print(f"Functions available: {navigator.__all__}") 11 | 12 | if os.environ.get("CI") == "true": 13 | print("Running in CI") 14 | print("Not possible to test navigator sensors yet.") 15 | return 16 | 17 | print("Initializing navigator module.") 18 | 19 | if platform.machine() == "aarch64": 20 | # It's possible to set the configuration before initializing the navigator, check this example 21 | from bluerobotics_navigator import NavigatorVersion, Raspberry 22 | print("Setting up for Navigator V2 on Raspberry Pi 5") 23 | navigator.set_rgb_led_strip_size(1) 24 | navigator.set_navigator_version(NavigatorVersion.Version2) 25 | navigator.set_raspberry_pi_version(Raspberry.Pi5) 26 | 27 | navigator.init() 28 | 29 | print("Setting led on!") 30 | navigator.set_led(UserLed.Led1, True) 31 | 32 | print(f"Temperature: {navigator.read_temp()}") 33 | 34 | print(f"Pressure: {navigator.read_pressure()}") 35 | 36 | print(f"Leak sensor: {navigator.read_leak()}") 37 | 38 | Data = navigator.read_adc_all() 39 | print( 40 | f"Data ADC Channels: 1 = {Data[0]}, 2 = {Data[1]}, 3 = {Data[2]}, 4 = {Data[3]}" 41 | ) 42 | 43 | print(f"Data ADC Channel: 1 = {navigator.read_adc(AdcChannel.Ch1)}") 44 | 45 | Data = navigator.read_mag() 46 | print(f"Magnetic field: X = {Data.x}, Y = {Data.y}, Z = {Data.z}") 47 | 48 | Data = navigator.read_accel() 49 | print(f"Acceleration: X = {Data.x}, Y = {Data.y}, Z = {Data.z}") 50 | 51 | Data = navigator.read_gyro() 52 | print(f"Gyroscope: X = {Data.x}, Y = {Data.y}, Z = {Data.z}") 53 | 54 | print("Setting led off!") 55 | navigator.set_led(UserLed.Led1, False) 56 | 57 | 58 | if __name__ == "__main__": 59 | navigator_check() 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Navigator Library 2 | 3 | [![Actions](https://github.com/bluerobotics/navigator-lib/actions/workflows/action.yml/badge.svg)](https://github.com/bluerobotics/navigator-lib/actions/workflows/action.yml) 4 | [![PyPI](https://img.shields.io/pypi/v/bluerobotics_navigator)](https://pypi.org/project/bluerobotics-navigator/) 5 | 6 | This library serves as the entry point for applications that want to use [Navigator](https://bluerobotics.com/store/comm-control-power/control/navigator/) with Python or C++. 7 | 8 | > 1 . How-to setup the Raspberry Pi computer, please read [Instructions](#ocean-instructions-for-blueoshttpsdiscussblueroboticscomtblueos-official-release12024-recommended). 9 | 2 . For **Rust** 🦀, please check the [navigator-rs library](https://github.com/bluerobotics/navigator-rs). 10 | 11 | 12 | ## Features 13 | - **LEDs (User and RGB) access** 14 | - **PWM (Pulse Width Modulation) control** 15 | - **ADC (Analog Digital Converter) reading** 16 | - **Magnetometer / Accelerometer / Gyroscope sampling** 17 | - **Temperature reading** 18 | - **Pressure estimation** 19 | 20 | # 📖 Documentation: 21 | * [Python](https://docs.bluerobotics.com/navigator-lib/python) 22 | * [C++ (WIP)](https://gist.github.com/patrickelectric/133bc706a7397479bfae6f57665bddeb) 23 | 24 | Check the examples folder for further [information and guide](https://github.com/bluerobotics/navigator-lib/tree/master/examples). 25 | 26 | ## 🐍 Python: 27 | 28 | Install the library. 29 | 30 | ```shell 31 | pip install bluerobotics_navigator 32 | ``` 33 | 34 | With that, you'll bee able to run the examples, or creating your own: 35 | 36 | ```python 37 | #!/usr/bin/env python 38 | 39 | import bluerobotics_navigator as navigator 40 | 41 | print("Initializing navigator module.") 42 | navigator.init() 43 | 44 | print("Setting led on!") 45 | navigator.set_led(navigator.UserLed.Led1, True) 46 | 47 | print(f"Temperature: {navigator.read_temp()}") 48 | print(f"Pressure: {navigator.read_pressure()}") 49 | 50 | print( 51 | f"Data ADC Channels: {navigator.read_adc_all().channel}" 52 | ) 53 | 54 | print(f"Data ADC Channel: 1 = {navigator.read_adc(navigator.AdcChannel.Ch1)}") 55 | 56 | data = navigator.read_mag() 57 | print(f"Magnetic field: X = {data.x}, Y = {data.y}, Z = {data.z}") 58 | ``` 59 | 60 | ## 🛠️ C++: 61 | 62 | Follow our example folder as a template to create your own project. To compile and run the examples, you can run: 63 | 64 | ```shell 65 | cd examples/cpp 66 | cmake -B build -DCMAKE_BUILD_TYPE=Debug && cmake --build build --config Debug --parallel 67 | # Run one of the examples 68 | ./build/simple 69 | ./build/rainbow 70 | ``` 71 | 72 | For an example of C++ code, you can check the following code: 73 | 74 | ```cpp 75 | #include "bindings.h" 76 | #include 77 | #include 78 | #include 79 | #include 80 | 81 | int main() { 82 | printf("Initiating navigator module.\n"); 83 | init(); 84 | 85 | printf("Setting led on!\n"); 86 | set_led(UserLed::Led1, true); 87 | 88 | printf("Temperature: %f\n", read_temp()); 89 | printf("Pressure: %f\n", read_pressure()); 90 | 91 | ADCData adc = read_adc_all(); 92 | printf("Reading ADC Channels: 1 = %f, 2 = %f, 3 = %f, 4 = %f\n", 93 | adc.channel[0], adc.channel[1], adc.channel[2], adc.channel[3]); 94 | printf("Data ADC Channels: 1 = %f\n", read_adc(AdcChannel::Ch1)); 95 | 96 | AxisData mag = read_mag(); 97 | printf("Magnetic field: X = %f, Y = %f, Z = %f\n", mag.x, mag.y, mag.z); 98 | 99 | return 0; 100 | } 101 | 102 | ``` 103 | 104 | > Note: The CMakeLists_Standalone.txt is a self-contained CMake project file example. Users can use it as a template to create their standalone projects based on the navigator-lib. 105 | 106 | ## 🏗️ Supported Architectures 107 | 108 | Currently, the library supports **armv7** and **aarch64** architectures, which are the official defaults for [BlueOS](https://docs.bluerobotics.com/ardusub-zola/software/onboard/BlueOS-1.1/). The library also provides C++ `.so` files for both `gnu` and `musl`. 109 | 110 | For more detailed information, including installation instructions, schematics, and hardware specifications, please refer to the [navigator hardware setup guide](https://bluerobotics.com/learn/navigator-hardware-setup/#introduction). 111 | 112 | ## :ocean: Instructions for [BlueOS](https://discuss.bluerobotics.com/t/blueos-official-release/12024) (Recommended) 113 | 114 | 1. Open the [Autopilot Firmware](https://blueos.cloud/docs/blueos/latest/advanced-usage/#autopilot-firmware) page, enable [Pirate Mode](https://blueos.cloud/docs/blueos/latest/advanced-usage/#pirate-mode), and "change board" to SITL 115 | - This stops the autopilot firmware from trying to operate while the WebAssistant is in use 116 | 1. [Reboot](https://blueos.cloud/docs/blueos/latest/advanced-usage/#power) the vehicle computer, and wait for the interface to re-connect 117 | > Note: Since this library access the Navigator hardware, it **can´t** run in parallel with ArduPilot. 118 | If you are running ArduPilot, be sure to disable it or set the board as **SITL** (Software Simulation) before running Navigator WebAssistant 119 | 120 | ## :cherries: Instructions for [Raspberry Pi OS](https://www.raspberrypi.com/software/) 121 | 122 | 1. Download the Raspberry OS image (32 or 64 bits) 123 | > Note: To use a kernel that matches with the BlueOS and avoid incompatilities, please use: 124 | **32** Bits: [raspios_lite_armhf-2023-02-22](https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2023-02-22/) 125 | **64** Bits: [raspios_lite_arm64-2023-02-22](https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2023-02-22/) 126 | 127 | 2. Install the [Raspberry Pi Imager](https://www.raspberrypi.com/software/) and flash the image 128 | > Note: You can also automatically setup the ssh, user, hostname and wi-fi settings. 129 | 130 | 3. Execute the following command to setup the overlay required for Navigator ( I2C, SPI, GPIOs & others) 131 | 132 | 133 | ```shell 134 | sudo su -c 'curl -fsSL https://raw.githubusercontent.com/bluerobotics/blueos-docker/master/install/boards/configure_board.sh | bash' 135 | sudo reboot 136 | ``` 137 | -------------------------------------------------------------------------------- /.github/workflows/action.yml: -------------------------------------------------------------------------------- 1 | name: Test all targets 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: write 7 | 8 | jobs: 9 | quick-tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Repo 13 | uses: actions/checkout@v3 14 | - name: Rust setup 15 | uses: actions-rs/toolchain@v1.0.6 16 | with: 17 | toolchain: nightly 18 | override: true 19 | components: rustfmt, clippy 20 | - name: Rust | Cache 21 | uses: Swatinem/rust-cache@v2 22 | with: 23 | prefix-key: "rust-cache" 24 | shared-key: "quick-tests" 25 | - name: Check Rust formatting 26 | run: cargo fmt -- --check 27 | - name: Check Rust code with Clippy 28 | run: cargo clippy -- -Dwarnings -A clippy::not_unsafe_ptr_arg_deref 29 | - name: Run internal tests 30 | run: cargo test --verbose -- --nocapture 31 | - name: Build Rust project 32 | run: cargo build 33 | - name: Build Python module 34 | run: | 35 | pip install --user maturin hatch 36 | hatch run dev:install 37 | - name: Check Python integration 38 | run: hatch run dev:examples/python/main.py 39 | - name: Check C++ integration 40 | run: | 41 | cd examples/cpp 42 | cmake -B build -DCMAKE_BUILD_TYPE=Debug 43 | cmake --build build --config Debug --parallel 44 | ./build/simple 45 | - name: Run clang-format style check for C++ example. 46 | uses: jidicula/clang-format-action@v4.11.0 47 | with: 48 | clang-format-version: '16' 49 | check-path: 'examples/cpp/' 50 | exclude-regex: 'examples/cpp/build' 51 | 52 | version-manager: 53 | needs: [quick-tests] 54 | runs-on: ubuntu-latest 55 | outputs: 56 | head_hash: ${{ steps.head-hash.outputs.head_hash }} 57 | steps: 58 | - name: Checkout Repo 59 | uses: actions/checkout@v3 60 | - name: Rust setup 61 | uses: actions-rs/toolchain@v1.0.6 62 | with: 63 | toolchain: nightly 64 | override: true 65 | - name: Modify version if release-tag 66 | if: startsWith(github.ref, 'refs/tags/') 67 | run: | 68 | cargo install cargo-bump --force \ 69 | && cargo bump ${{ github.ref_name }} 70 | - name: Automatic commit for version upgrade 71 | if: startsWith(github.ref, 'refs/tags/') 72 | uses: stefanzweifel/git-auto-commit-action@v4 73 | with: 74 | branch: master 75 | commit_message: "Cargo: Update the navigator-lib version to ${{ github.ref_name }}" 76 | - name: Store the head actual hash 77 | id: head-hash 78 | run: echo "head_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT 79 | 80 | build-cpp: 81 | needs: version-manager 82 | runs-on: ubuntu-latest 83 | strategy: 84 | fail-fast: false 85 | matrix: 86 | include: 87 | - TARGET: armv7-unknown-linux-gnueabihf 88 | - TARGET: aarch64-unknown-linux-gnu 89 | - TARGET: armv7-unknown-linux-musleabihf 90 | - TARGET: aarch64-unknown-linux-musl 91 | steps: 92 | - name: Building ${{ matrix.TARGET }} 93 | run: echo "${{ matrix.TARGET }}" 94 | - uses: actions/checkout@v3 95 | with: 96 | ref: ${{ needs.version-manager.outputs.head_hash }} 97 | - name: Rust Setup 98 | uses: actions-rs/toolchain@v1.0.1 99 | with: 100 | toolchain: nightly 101 | target: ${{ matrix.TARGET }} 102 | override: true 103 | - name: Rust Cache 104 | uses: Swatinem/rust-cache@v2 105 | with: 106 | prefix-key: "rust-cache" 107 | shared-key: "build-cpp-${{ matrix.TARGET }}" 108 | - name: Rust Cross Build Cpp 109 | uses: actions-rs/cargo@v1 110 | with: 111 | use-cross: true 112 | command: build 113 | args: --verbose --release --target=${{ matrix.TARGET }} -Z unstable-options --out-dir output/ 114 | - name: Move to it's target 115 | run: | 116 | mkdir dist 117 | mkdir "dist/${{ matrix.TARGET }}" 118 | mv output/* "dist/${{ matrix.TARGET }}" 119 | mv target/release/bindings.h dist/ 120 | - name: Upload bin 121 | uses: actions/upload-artifact@v4 122 | with: 123 | name: cpp-${{ matrix.TARGET }} 124 | path: dist 125 | 126 | build-python: 127 | needs: version-manager 128 | runs-on: ubuntu-latest 129 | strategy: 130 | fail-fast: false 131 | matrix: 132 | target: [aarch64, armv7] 133 | libc: [auto, musllinux_1_1] 134 | steps: 135 | - uses: actions/checkout@v3 136 | with: 137 | ref: ${{ needs.version-manager.outputs.head_hash }} 138 | - uses: actions/setup-python@v4 139 | with: 140 | python-version: "3.9" 141 | - name: Build wheels 142 | uses: PyO3/maturin-action@v1 143 | with: 144 | target: ${{ matrix.target }} 145 | args: --release --out dist 146 | manylinux: ${{ matrix.libc }} 147 | - uses: uraimo/run-on-arch-action@master 148 | if: matrix.libc == 'auto' 149 | name: Install built wheel manylinux 150 | with: 151 | arch: ${{ matrix.target }} 152 | distro: bullseye 153 | githubToken: ${{ github.token }} 154 | install: | 155 | apt-get update 156 | apt-get install -y --no-install-recommends python3 python3-pip 157 | pip3 install -U pip 158 | run: | 159 | pip3 install bluerobotics_navigator --no-index --find-links dist/ --force-reinstall 160 | - uses: uraimo/run-on-arch-action@master 161 | if: matrix.libc == 'musllinux_1_1' 162 | name: Install built wheel musl 163 | with: 164 | arch: ${{ matrix.target}} 165 | distro: alpine_latest 166 | githubToken: ${{ github.token }} 167 | install: | 168 | apk add py3-pip 169 | run: | 170 | python3 -m venv ./venv 171 | . ./venv/bin/activate 172 | pip3 install -U pip 173 | pip3 install bluerobotics_navigator --no-index --find-links dist/ --force-reinstall 174 | - name: Upload wheels 175 | uses: actions/upload-artifact@v4 176 | with: 177 | name: python-${{ matrix.TARGET }}-${{ matrix.libc }} 178 | path: dist 179 | 180 | raspberry-test-python: 181 | needs: build-python 182 | if: ${{ github.repository_owner == 'bluerobotics' }} 183 | strategy: 184 | matrix: 185 | include: 186 | - runner: raspbian-armv7-kernel-5.10.33 187 | arch: armv7 188 | - runner: raspbian-aarch64-kernel-6.6.51 189 | arch: aarch64 190 | runs-on: ${{ matrix.runner }} 191 | steps: 192 | - uses: actions/checkout@v3 193 | with: 194 | ref: ${{ needs.version-manager.outputs.head_hash }} 195 | - name: Download ${{ matrix.arch }} wheels 196 | uses: actions/download-artifact@v4 197 | with: 198 | name: python-${{ matrix.arch }}-auto 199 | path: dist 200 | - name: Setup Python environment 201 | run: | 202 | sudo apt-get update 203 | sudo apt-get install -y --no-install-recommends python3-venv python3-full 204 | - name: Create and activate virtual environment 205 | run: | 206 | python3 -m venv .venv 207 | source .venv/bin/activate 208 | python -m pip install -U pip 209 | pip install bluerobotics_navigator --no-index --find-links dist/ --force-reinstall 210 | - name: Check Python main example 211 | run: | 212 | export CI=false 213 | source .venv/bin/activate 214 | python examples/python/main.py 215 | 216 | python-release-pypi: 217 | needs: [raspberry-test-python] 218 | runs-on: ubuntu-latest 219 | if: "startsWith(github.ref, 'refs/tags/')" 220 | permissions: 221 | id-token: write 222 | steps: 223 | - uses: actions/download-artifact@v4 224 | with: 225 | name: python-armv7-auto 226 | - uses: actions/download-artifact@v4 227 | with: 228 | name: python-aarch64-auto 229 | - name: Publish package distributions to PyPI 230 | uses: pypa/gh-action-pypi-publish@release/v1 231 | with: 232 | packages-dir: . 233 | 234 | release-assets: 235 | needs: ["build-cpp","raspberry-test-python"] 236 | runs-on: ubuntu-latest 237 | if: "startsWith(github.ref, 'refs/tags/')" 238 | steps: 239 | - name: Download cpp artifacts armv7 240 | uses: actions/download-artifact@v4 241 | with: 242 | name: cpp-armv7-unknown-linux-gnueabihf 243 | path: cpp-armv7 244 | - name: Download cpp artifacts aarch64 245 | uses: actions/download-artifact@v4 246 | with: 247 | name: cpp-aarch64-unknown-linux-gnu 248 | path: cpp-aarch64 249 | - name: Download python artifacts 250 | uses: actions/download-artifact@v4 251 | with: 252 | name: python-armv7-auto 253 | path: python 254 | - name: Compress artifacts 255 | run: | 256 | sudo apt update 257 | sudo apt install -y zip 258 | mkdir -p cpp 259 | cp -r cpp-armv7/* cpp/ 260 | cp -r cpp-aarch64/* cpp/ 261 | cd cpp 262 | zip -rT ../cpp.zip * 263 | cd ../python 264 | zip -rT ../python.zip * 265 | - name: Upload assets to release 266 | uses: svenstaro/upload-release-action@v2 267 | with: 268 | repo_token: ${{ secrets.GITHUB_TOKEN }} 269 | file: ./*.zip 270 | tag: ${{ github.ref }} 271 | file_glob: true 272 | 273 | deploy-doc: 274 | needs: version-manager 275 | runs-on: ubuntu-latest 276 | steps: 277 | - uses: actions/checkout@v3 278 | - uses: actions-rs/toolchain@v1.0.6 279 | with: 280 | toolchain: nightly 281 | override: true 282 | - name: Build docs 283 | run: pip install hatch && hatch run dev:build-doc 284 | # Move it to its own folder 285 | - name: Move python documentation 286 | run: mkdir -p pages/python && mv docs/_build/* pages/python/ 287 | - name: Deploy 288 | uses: peaceiris/actions-gh-pages@v3 289 | if: ${{ github.ref == 'refs/heads/master' }} 290 | with: 291 | github_token: ${{ secrets.GITHUB_TOKEN }} 292 | publish_dir: ./pages 293 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use cpy_binder::{cpy_enum, cpy_fn, cpy_fn_c, cpy_fn_py, cpy_module, cpy_struct}; 2 | 3 | use lazy_static::lazy_static; 4 | use std::sync::Mutex; 5 | 6 | #[cpy_enum] 7 | #[comment = "Raspberry Pi version."] 8 | enum Raspberry { 9 | Pi4, 10 | Pi5, 11 | } 12 | 13 | impl From for navigator_rs::PiVersion { 14 | fn from(item: Raspberry) -> Self { 15 | match item { 16 | Raspberry::Pi4 => navigator_rs::PiVersion::Pi4, 17 | Raspberry::Pi5 => navigator_rs::PiVersion::Pi5, 18 | } 19 | } 20 | } 21 | #[cpy_enum] 22 | #[comment = "Navigator version."] 23 | enum NavigatorVersion { 24 | Version1, 25 | Version2, 26 | } 27 | 28 | impl From for navigator_rs::NavigatorVersion { 29 | fn from(item: NavigatorVersion) -> Self { 30 | match item { 31 | NavigatorVersion::Version1 => navigator_rs::NavigatorVersion::V1, 32 | NavigatorVersion::Version2 => navigator_rs::NavigatorVersion::V2, 33 | } 34 | } 35 | } 36 | 37 | #[derive(Clone)] 38 | struct NavigatorBuilderManager { 39 | rgb_led_strip_size: usize, 40 | raspberry_pi_version: Raspberry, 41 | navigator_version: NavigatorVersion, 42 | } 43 | 44 | lazy_static! { 45 | static ref NAVIGATORBUILDER: Mutex = 46 | Mutex::new(NavigatorBuilderManager { 47 | rgb_led_strip_size: 1, 48 | raspberry_pi_version: Raspberry::Pi4, 49 | navigator_version: NavigatorVersion::Version1, 50 | }); 51 | } 52 | 53 | macro_rules! with_navigator_builder { 54 | () => { 55 | NAVIGATORBUILDER.lock().unwrap() 56 | }; 57 | } 58 | 59 | #[cpy_fn] 60 | #[comment_c = "Sets the size of the navigator led strip (1 is the default), should be called before `init`."] 61 | #[comment_py = "Sets the size of the navigator led strip (1 is the default), should be called before `init`.\n 62 | Examples:\n 63 | >>> import bluerobotics_navigator as navigator\n 64 | >>> navigator.set_rgb_led_strip_size(1)\n 65 | >>> navigator.init()"] 66 | fn set_rgb_led_strip_size(size: usize) { 67 | with_navigator_builder!().rgb_led_strip_size = size; 68 | } 69 | 70 | #[cpy_fn] 71 | #[comment = "Sets the navigator version."] 72 | fn set_navigator_version(version: NavigatorVersion) { 73 | with_navigator_builder!().navigator_version = version; 74 | } 75 | 76 | #[cpy_fn] 77 | #[comment = "Sets the raspberry pi version."] 78 | fn set_raspberry_pi_version(version: Raspberry) { 79 | with_navigator_builder!().raspberry_pi_version = version; 80 | } 81 | 82 | struct NavigatorManager { 83 | navigator: navigator_rs::Navigator, 84 | } 85 | 86 | lazy_static! { 87 | static ref NAVIGATOR: Mutex> = Mutex::new(None); 88 | } 89 | 90 | impl NavigatorManager { 91 | fn get_instance() -> &'static Mutex> { 92 | if NAVIGATOR.lock().unwrap().is_none() { 93 | let configuration = with_navigator_builder!().clone(); 94 | let navigator = navigator_rs::Navigator::create() 95 | .with_rgb_led_strip_size(configuration.rgb_led_strip_size) 96 | .with_navigator(configuration.navigator_version.into()) 97 | .with_pi(configuration.raspberry_pi_version.into()) 98 | .build(); 99 | *NAVIGATOR.lock().unwrap() = Some(NavigatorManager { navigator }); 100 | } 101 | &NAVIGATOR 102 | } 103 | } 104 | 105 | macro_rules! with_navigator { 106 | () => { 107 | NavigatorManager::get_instance() 108 | .lock() 109 | .unwrap() 110 | .as_mut() 111 | .unwrap() 112 | .navigator 113 | }; 114 | } 115 | 116 | macro_rules! impl_from_enum { 117 | ($from:ty, $to:ty, $($variant:ident),+ $(,)?) => { 118 | impl From<$from> for $to { 119 | fn from(item: $from) -> Self { 120 | match item { 121 | $( 122 | <$from>::$variant => <$to>::$variant, 123 | )+ 124 | } 125 | } 126 | } 127 | }; 128 | } 129 | 130 | // Help with conversion from navigator enum API to our stable API 131 | impl_from_enum!(AdcChannel, navigator_rs::AdcChannel, Ch0, Ch1, Ch2, Ch3); 132 | impl_from_enum!(UserLed, navigator_rs::UserLed, Led1, Led2, Led3); 133 | 134 | impl From for AxisData { 135 | fn from(read_axis: navigator_rs::AxisData) -> Self { 136 | Self { 137 | x: read_axis.x, 138 | y: read_axis.y, 139 | z: read_axis.z, 140 | } 141 | } 142 | } 143 | 144 | #[cpy_enum] 145 | #[comment = "Available ADC channels to read from."] 146 | enum AdcChannel { 147 | Ch0, 148 | Ch1, 149 | Ch2, 150 | Ch3, 151 | } 152 | 153 | #[cpy_enum] 154 | #[comment = "Onboard user-controllable LEDs."] 155 | enum UserLed { 156 | Led1, 157 | Led2, 158 | Led3, 159 | } 160 | 161 | #[cpy_struct] 162 | #[comment = "Board-oriented direction axes (x is forwards, y is right, z is down)."] 163 | struct AxisData { 164 | x: f32, 165 | y: f32, 166 | z: f32, 167 | } 168 | 169 | #[cpy_fn] 170 | #[comment_c = "Initializes the Navigator module with default settings (not necessary)."] 171 | #[comment_py = "Initializes the Navigator module with default settings (not necessary).\n 172 | Examples:\n 173 | >>> import bluerobotics_navigator as navigator\n 174 | >>> navigator.init()"] 175 | fn init() { 176 | // Keep to avoid API break 177 | } 178 | 179 | #[cpy_fn] 180 | #[comment_c = "Runs some tests on available sensors, then returns the result (not necessary)."] 181 | #[comment_py = "Runs some tests on available sensors, then returns the result (not necessary).\n 182 | Returns:\n 183 | bool: `True` if the sensors are responding as expected.\n 184 | Examples:\n 185 | >>> import bluerobotics_navigator as navigator\n 186 | >>> sensors_ok = navigator.self_test()"] 187 | fn self_test() -> bool { 188 | // Keep to avoid API break 189 | true 190 | } 191 | 192 | #[cpy_fn] 193 | #[comment_c = "Sets the state of the selected onboard LED."] 194 | #[comment_py = "Sets the state of the selected onboard LED.\n 195 | Args:\n 196 | select (:py:class:`UserLed`): A pin to be selected.\n 197 | state (bool): The desired output state. `True` -> ON, `False` -> OFF.\n 198 | Examples:\n 199 | >>> import bluerobotics_navigator as navigator\n 200 | >>> from bluerobotics_navigator import UserLed\n 201 | >>> navigator.set_led(UserLed.Led1, True)"] 202 | fn set_led(select: UserLed, state: bool) { 203 | with_navigator!().set_led(select.into(), state) 204 | } 205 | 206 | #[cpy_fn] 207 | #[comment_c = "Gets the selected onboard LED output state."] 208 | #[comment_py = "Gets the selected onboard LED output state.\n\n 209 | Args:\n 210 | select (:py:class:`UserLed`): A pin to be selected.\n 211 | Returns:\n 212 | bool: The current state. `True` -> ON, `False` -> OFF.\n 213 | Examples:\n 214 | >>> import bluerobotics_navigator as navigator\n 215 | >>> from bluerobotics_navigator import UserLed\n 216 | >>> led1_on = navigator.get_led(UserLed.Led1)"] 217 | fn get_led(select: UserLed) -> bool { 218 | with_navigator!().get_led(select.into()) 219 | } 220 | 221 | #[cpy_fn] 222 | #[comment_c = "Toggle the output of the selected LED."] 223 | #[comment_py = "Toggle the output of the selected LED.\n\n 224 | Args:\n 225 | select (:py:class:`UserLed`): A pin to be selected.\n 226 | Examples:\n 227 | >>> import bluerobotics_navigator as navigator\n 228 | >>> from bluerobotics_navigator import UserLed\n 229 | >>> navigator.set_led_toggle(UserLed.Led1)"] 230 | fn set_led_toggle(select: UserLed) { 231 | with_navigator!().set_led_toggle(select.into()) 232 | } 233 | 234 | #[cpy_fn] 235 | #[comment_c = "Sets all user LEDs to the desired state ( Blue, Green, and Red )."] 236 | #[comment_py = "Sets all user LEDs to the desired state ( Blue, Green, and Red ).\n 237 | Args:\n 238 | state (bool): The desired output state. `True` -> ON, `False` -> OFF.\n 239 | Examples:\n 240 | >>> import bluerobotics_navigator as navigator\n 241 | >>> navigator.set_led_all(True)"] 242 | fn set_led_all(state: bool) { 243 | for led in [UserLed::Led1, UserLed::Led2, UserLed::Led3] { 244 | with_navigator!().set_led(led.into(), state); 245 | } 246 | } 247 | 248 | #[cpy_fn_c] 249 | #[comment = "Set the color brightnesses of a connected NeoPixel LED array."] 250 | fn set_neopixel_c(rgb_array: *const [u8; 3], length: usize) { 251 | let array = unsafe { 252 | assert!(!rgb_array.is_null()); 253 | std::slice::from_raw_parts(rgb_array, length) 254 | }; 255 | with_navigator!().set_neopixel(array); 256 | } 257 | 258 | #[cpy_fn_py] 259 | #[comment = "Set the color brightnesses of a connected NeoPixel LED array.\n 260 | Args:\n 261 | state ([[uint8, uint8, uint8], ...]): A 2D array containing RGB values for each LED.\n 262 | Set the Red, Green, and Blue components independently, with values from 0-255.\n 263 | Examples:\n 264 | >>> import bluerobotics_navigator as navigator\n 265 | >>> navigator.set_neopixel([[100,0,0]])"] 266 | fn set_neopixel_py(rgb_array: Vec<[u8; 3]>) { 267 | with_navigator!().set_neopixel(&rgb_array) 268 | } 269 | 270 | #[cpy_fn_c] 271 | #[comment = "Set the color brightnesses of a connected NeoPixel LED array."] 272 | fn set_neopixel_rgbw_c(rgb_array: *const [u8; 4], length: usize) { 273 | let array = unsafe { 274 | assert!(!rgb_array.is_null()); 275 | std::slice::from_raw_parts(rgb_array, length) 276 | }; 277 | with_navigator!().set_neopixel_rgbw(array); 278 | } 279 | 280 | #[cpy_fn_py] 281 | #[comment = "Set the color brightnesses of a connected NeoPixel LED array.\n 282 | Args:\n 283 | state ([[uint8, uint8, uint8, uint8], ...]): A 2D array containing RGB values for each LED.\n 284 | Set the Red, Green, Blue and White components independently, with values from 0-255.\n 285 | Examples:\n 286 | >>> import bluerobotics_navigator as navigator\n 287 | >>> navigator.set_neopixel([[100,0,0,128]])"] 288 | fn set_neopixel_rgbw_py(rgb_array: Vec<[u8; 4]>) { 289 | with_navigator!().set_neopixel_rgbw(&rgb_array) 290 | } 291 | 292 | #[cpy_fn_py] 293 | #[comment_py = "Reads the ADC channel values (from the ADS1115 chip).\n 294 | Same as :py:func:`read_adc`, but it returns an array with all channel readings.\n 295 | Returns:\n 296 | :py:class:`ADCData`: Measurements in [V].\n 297 | Examples:\n 298 | >>> import bluerobotics_navigator as navigator\n 299 | >>> adc_measurements = navigator.read_adc_all().channel"] 300 | fn read_adc_all_py() -> Vec { 301 | with_navigator!().read_adc_all() 302 | } 303 | 304 | #[cpy_fn_c] 305 | #[comment_c = "Reads the ADC channel values (from the ADS1115 chip)."] 306 | fn read_adc_all_c(adc_array: *mut f32, length: usize) { 307 | let array = unsafe { 308 | assert!(!adc_array.is_null()); 309 | std::slice::from_raw_parts_mut::(adc_array, length) 310 | }; 311 | 312 | let values = with_navigator!().read_adc_all(); 313 | array[..length].copy_from_slice(&values[..length]); 314 | } 315 | 316 | #[cpy_fn] 317 | #[comment_c = "Reads a specific ADC channel (from the ADS1115 chip)."] 318 | #[comment_py = "Reads a specific ADC channel (from the ADS1115 chip).\n\n 319 | Args:\n 320 | select (:py:class:`AdcChannel`): An ADC channel to read from.\n 321 | Returns:\n 322 | float32: Measurement in [V].\n 323 | Examples:\n 324 | >>> import bluerobotics_navigator as navigator\n 325 | >>> from bluerobotics_navigator import AdcChannel\n 326 | >>> adc1_measurement = navigator.read_adc(AdcChannel.Ch1)"] 327 | fn read_adc(channel: AdcChannel) -> f32 { 328 | with_navigator!().read_adc(channel.into()) 329 | } 330 | 331 | #[cpy_fn] 332 | #[comment_c = "Reads the current pressure (from the onboard BMP280 chip)."] 333 | #[comment_py = "Reads the current pressure (from the onboard BMP280 chip).\n 334 | Returns:\n 335 | float32: Measurement in [kPa]\n 336 | Examples:\n 337 | >>> import bluerobotics_navigator as navigator\n 338 | >>> air_pressure = navigator.read_pressure()"] 339 | fn read_pressure() -> f32 { 340 | with_navigator!().read_pressure() 341 | } 342 | 343 | #[cpy_fn] 344 | #[comment_c = "Reads the current temperature (from the onboard BMP280 chip)."] 345 | #[comment_py = "Reads the current temperature (from the onboard BMP280 chip).\n 346 | Returns:\n 347 | float32: Measurement in [˚C]\n 348 | Examples:\n 349 | >>> import bluerobotics_navigator as navigator\n 350 | >>> air_temperature = navigator.read_temperature()"] 351 | fn read_temp() -> f32 { 352 | with_navigator!().read_temperature() 353 | } 354 | 355 | #[cpy_fn] 356 | #[comment_c = "Reads the local magnetic field strengths (from the onboard Ak09915 magnetometer)."] 357 | #[comment_py = "Reads the local magnetic field strengths (from the onboard Ak09915 magnetometer).\n 358 | Returns:\n 359 | :py:class:`AxisData`: Measurements in [µT]\n 360 | Examples:\n 361 | >>> import bluerobotics_navigator as navigator\n 362 | >>> mag_field = navigator.read_mag()"] 363 | fn read_mag() -> AxisData { 364 | with_navigator!().read_mag().into() 365 | } 366 | 367 | #[cpy_fn] 368 | #[comment_c = "Reads the current acceleration values (from the ICM20689 chip's accelerometer)."] 369 | #[comment_py = "Reads the current acceleration values (from the ICM20689 chip's accelerometer).\n 370 | Returns:\n 371 | :py:class:`AxisData`: Measurements in [m/s²]\n 372 | Examples:\n 373 | >>> import bluerobotics_navigator as navigator\n 374 | >>> acceleration = navigator.read_accel()\n 375 | >>> forward_acc = acceleration.x"] 376 | fn read_accel() -> AxisData { 377 | with_navigator!().read_accel().into() 378 | } 379 | 380 | #[cpy_fn] 381 | #[comment_c = "Reads the current angular velocity (from the ICM20689 chip's gyroscope)."] 382 | #[comment_py = "Reads the current angular velocity (from the ICM20689 chip's gyroscope).\n 383 | Returns:\n 384 | :py:class:`AxisData`: Measurements in [rad/s]\n 385 | Examples:\n 386 | >>> import bluerobotics_navigator as navigator\n 387 | >>> angular_velocity = navigator.read_gyro()\n 388 | >>> roll_rate = angular_velocity.x\n 389 | >>> pitch_rate = angular_velocity.y\n 390 | >>> yaw_rate = angular_velocity.z"] 391 | fn read_gyro() -> AxisData { 392 | with_navigator!().read_gyro().into() 393 | } 394 | 395 | #[cpy_fn] 396 | #[comment_c = "Reads the state of leak detector pin from Navigator."] 397 | #[comment_py = "Reads the state of leak detector pin from Navigator.\n\n 398 | Returns:\n 399 | bool: The current state. `True` -> Leak detection, `False` -> No leak.\n 400 | Examples:\n 401 | >>> import bluerobotics_navigator as navigator\n 402 | >>> leak_detector = navigator.read_leak()"] 403 | fn read_leak() -> bool { 404 | with_navigator!().read_leak() 405 | } 406 | 407 | #[cpy_fn] 408 | #[comment_c = "Enables or disables the PWM chip (PCA9685), using the firmware and OE_pin."] 409 | #[comment_py = "Enables or disables the PWM chip (PCA9685), using the firmware and OE_pin.\n 410 | Args:\n 411 | state (bool): The desired PWM chip state. `True` -> ON, `False` -> OFF.\n 412 | Examples:\n 413 | Please check :py:func:`set_pwm_channel_value`\n 414 | >>> navigator.set_pwm_enable(True)"] 415 | fn set_pwm_enable(state: bool) { 416 | with_navigator!().set_pwm_enable(state) 417 | } 418 | 419 | #[cpy_fn] 420 | #[comment_c = "Sets the PWM frequency of the PCA9685 chip. All channels use the same frequency."] 421 | #[comment_py = "Sets the PWM frequency of the PCA9685 chip. All channels use the same frequency.\n 422 | This is a convenience wrapper around :py:func:`set_pwm_freq_prescale`, which chooses the closest 423 | possible pre-scaler to achieve the desired frequency.\n 424 | Notes:\n 425 | Servo motors generally work best with PWM frequencies between 50-200 Hz.\n 426 | LEDs flicker less in video streams when driven at a frequency multiple of the camera's 427 | framerate (e.g. a 30fps camera stream should have LEDs at 30/60/90/120/... Hz).\n 428 | Args:\n 429 | freq (float32) : The desired PWM frequency (24..1526) [Hz].\n 430 | Examples:\n 431 | >>> import bluerobotics_navigator as navigator\n 432 | >>> navigator.set_pwm_freq_hz(60)\n 433 | >>> navigator.set_pwm_channel_value(1, 2000)\n 434 | >>> navigator.set_pwm_enable(True)"] 435 | fn set_pwm_freq_hz(freq: f32) { 436 | with_navigator!().set_pwm_frequency(freq) 437 | } 438 | 439 | #[cpy_fn] 440 | #[comment_c = "Sets the duty cycle (the proportion of ON time) for the selected PWM channel."] 441 | #[comment_py = "Sets the duty cycle (the proportion of ON time) for the selected PWM channel.\n 442 | This sets the PWM channel's OFF counter, with the ON counter hard-coded to 0.\n 443 | The output turns ON at the start of each cycle, then turns OFF after the specified count 444 | (value), where each full cycle (defined by :py:func:`set_pwm_freq_hz`) is split into 4096 445 | segments.\n 446 | Notes:\n 447 | A duty cycle of 20% is achieved using a count of 819.\n 448 | To achieve a specific pulse-duration you need to consider the cycle frequency:\n 449 | `value = 4095 * pulse_duration / cycle_period`\n 450 | As an example, if the frequency is set to 50 Hz (20 ms period) then a 1100 µs 451 | pulse-duration can be achieved with a 5.5% duty cycle, which requires a count of 225. 452 | Similarly, 1900 µs pulses would be achieved with a count of 389. 453 | Args:\n 454 | channel (:py:class:`PwmChannel`): The channel to be selected for PWM.\n 455 | value (u16) : Duty cycle count value (0..4095).\n 456 | Examples:\n 457 | >>> import bluerobotics_navigator as navigator\n 458 | >>> from bluerobotics_navigator import PwmChannel\n 459 | >>> navigator.init()\n 460 | >>> navigator.set_pwm_freq_hz(1000)\n 461 | >>> navigator.set_pwm_channel_value(PwmChannel.Ch1, 2000)\n 462 | >>> navigator.set_pwm_enable(True)"] 463 | fn set_pwm_channel_value(channel: usize, value: f32) { 464 | with_navigator!().set_pwm_duty_cycle(channel, value / 4096.0) 465 | } 466 | 467 | #[cpy_fn] 468 | #[comment_c = "Sets the duty cycle (the proportion of ON time) for the selected PWM channel."] 469 | #[comment_py = "Sets the duty cycle (the proportion of ON time) for the selected PWM channel.\n 470 | Similar to :py:func:`set_pwm_channel_value`, this function calculate the OFF counter 471 | value to match desired PWM channel's duty_cyle.\n 472 | Notes:\n 473 | A duty cycle of 1.0 or 0.0 acts like a relay.\n 474 | Details of counters on IC, check :py:func:`set_pwm_channel_value`. 475 | Args:\n 476 | channel (:py:class:`PwmChannel`): The channel to be selected for PWM.\n 477 | duty_cycle (f32) : Duty cycle count value (0.0 : 1.0).\n 478 | Examples:\n 479 | >>> import bluerobotics_navigator as navigator\n 480 | >>> from bluerobotics_navigator import PwmChannel\n 481 | >>> navigator.init()\n 482 | >>> navigator.set_pwm_freq_hz(1000)\n 483 | >>> navigator.set_pwm_channel_duty_cycle(PwmChannel.Ch1, 0.5)\n 484 | >>> navigator.set_pwm_enable(True)"] 485 | fn set_pwm_channel_duty_cycle(channel: usize, duty_cycle: f32) { 486 | with_navigator!().set_pwm_duty_cycle(channel, duty_cycle) 487 | } 488 | 489 | #[cpy_fn_c] 490 | #[comment = "Sets the duty cycle (based on OFF counter from 0 to 1) for a list of multiple PWM channels."] 491 | fn set_pwm_channels_value_c(channels: *const usize, value: f32, length: usize) { 492 | let array_channels = unsafe { 493 | assert!(!channels.is_null()); 494 | std::slice::from_raw_parts(channels, length) 495 | }; 496 | for channel in array_channels.iter().take(length) { 497 | with_navigator!().set_pwm_duty_cycle(*channel, value); 498 | } 499 | } 500 | 501 | #[cpy_fn_c] 502 | #[comment = "Sets the duty cycle (from 0.0 to 1.0) for a list of multiple PWM channels."] 503 | fn set_pwm_channels_duty_cycle_c(channels: *const usize, duty_cycle: f32, length: usize) { 504 | let array_channels = unsafe { 505 | assert!(!channels.is_null()); 506 | std::slice::from_raw_parts(channels, length) 507 | }; 508 | for channel in array_channels.iter().take(length) { 509 | with_navigator!().set_pwm_duty_cycle(*channel, duty_cycle); 510 | } 511 | } 512 | 513 | #[cpy_fn_py] 514 | #[comment = "Like :py:func:`set_pwm_channel_value`. This function sets the duty cycle for a list of multiple PWM channels.\n 515 | Args:\n 516 | channels ([:py:class:`PwmChannel`]): A list of PWM channels to configure.\n 517 | value (u16) : The desired duty cycle value (0..4095).\n 518 | Examples:\n 519 | You can use this method like :py:func:`set_pwm_channel_value`.\n 520 | >>> navigator.set_pwm_channels_value([PwmChannel.Ch1, PwmChannel.Ch16], 1000)"] 521 | fn set_pwm_channels_value_py(channels: Vec, value: u16) { 522 | for i in 0..channels.len() { 523 | with_navigator!().set_pwm_duty_cycle(channels[i], value as f32 / 4096.0); 524 | } 525 | } 526 | 527 | #[cpy_fn_py] 528 | #[comment = "Like :py:func:`set_pwm_channel_duty_cycle`. This function sets the duty cycle for a list of multiple PWM channels.\n 529 | Args:\n 530 | channels ([:py:class:`PwmChannel`]): A list of PWM channels to configure.\n 531 | duty_cycle (f32) : Duty cycle count value (0.0 : 1.0).\n 532 | Examples:\n 533 | You can use this method like :py:func:`set_pwm_channel_duty_cycle`.\n 534 | >>> navigator.set_pwm_channels_value([PwmChannel.Ch1, PwmChannel.Ch16], 0.5)"] 535 | fn set_pwm_channels_duty_cycle_py(channels: Vec, duty_cycle: f32) { 536 | for channel in channels { 537 | with_navigator!().set_pwm_duty_cycle(channel.into(), duty_cycle); 538 | } 539 | } 540 | 541 | #[cpy_fn_c] 542 | #[comment = "Sets the duty cycle (from 0 to 4096) for a list of multiple channels with multiple values."] 543 | fn set_pwm_channels_values_c(channels: *const usize, values: *const f32, length: usize) { 544 | let array_channels = unsafe { 545 | assert!(!channels.is_null()); 546 | std::slice::from_raw_parts(channels, length) 547 | }; 548 | let array_values = unsafe { 549 | assert!(!values.is_null()); 550 | std::slice::from_raw_parts(values, length) 551 | }; 552 | for i in 0..length { 553 | with_navigator!().set_pwm_duty_cycle(array_channels[i], array_values[i] / 4096.0); 554 | } 555 | } 556 | 557 | #[cpy_fn_c] 558 | #[comment = "Sets the duty cycle (from 0.0 to 1.0) for a list of multiple channels with multiple values."] 559 | fn set_pwm_channels_duty_cycle_values_c( 560 | channels: *const usize, 561 | duty_cycle: *const f32, 562 | length: usize, 563 | ) { 564 | let array_channels = unsafe { 565 | assert!(!channels.is_null()); 566 | std::slice::from_raw_parts(channels, length) 567 | }; 568 | let array_values = unsafe { 569 | assert!(!duty_cycle.is_null()); 570 | std::slice::from_raw_parts(duty_cycle, length) 571 | }; 572 | for i in 0..length { 573 | with_navigator!().set_pwm_duty_cycle(array_channels[i], array_values[i]); 574 | } 575 | } 576 | 577 | #[cpy_fn_py] 578 | #[comment = "Like :py:func:`set_pwm_channel_value`. This function sets the duty cycle for a list of 579 | multiple channels with multiple values.\n 580 | Args:\n 581 | channels ([:py:class:`PwmChannel`]): A list of PWM channels to configure.\n 582 | values ([u16]) : A corresponding list of duty cycle values.\n 583 | Examples:\n 584 | You can use this method like :py:func:`set_pwm_channel_value`.\n 585 | >>> navigator.set_pwm_channels_values([PwmChannel.Ch1, PwmChannel.Ch5], [1000, 500])"] 586 | fn set_pwm_channels_values_py(channels: Vec, values: Vec) { 587 | if channels.len() != values.len() { 588 | println!("The number of values is different from the number of PWM channels."); 589 | return; 590 | } 591 | 592 | for i in 0..channels.len() { 593 | with_navigator!().set_pwm_duty_cycle(channels[i].clone().into(), values[i] as f32 / 4096.0); 594 | } 595 | } 596 | 597 | #[cpy_fn_py] 598 | #[comment = "Like :py:func:`set_pwm_channel_duty_cycle`. This function sets the duty cycle for a list of 599 | multiple channels with multiple values.\n 600 | Args:\n 601 | channels ([:py:class:`PwmChannel`]): A list of PWM channels to configure.\n 602 | duty_cycle_values (f32) : Duty cycle count value (0.0 : 1.0).\n 603 | Examples:\n 604 | You can use this method like :py:func:`set_pwm_channel_duty_cycle`.\n 605 | >>> navigator.set_pwm_channels_duty_cycle_values([PwmChannel.Ch1, PwmChannel.Ch5], [0.25, 0.75])"] 606 | fn set_pwm_channels_duty_cycle_values_py(channels: Vec, duty_cycle_values: Vec) { 607 | if channels.len() != duty_cycle_values.len() { 608 | println!("The number of values is different from the number of PWM channels."); 609 | return; 610 | } 611 | 612 | for i in 0..channels.len() { 613 | with_navigator!().set_pwm_duty_cycle(channels[i].clone().into(), duty_cycle_values[i]); 614 | } 615 | } 616 | cpy_module!( 617 | name = bluerobotics_navigator, 618 | types = [AdcChannel, UserLed, AxisData, Raspberry, NavigatorVersion], 619 | functions = [ 620 | init, 621 | set_rgb_led_strip_size, 622 | set_navigator_version, 623 | set_raspberry_pi_version, 624 | self_test, 625 | set_led, 626 | get_led, 627 | set_led_toggle, 628 | set_led_all, 629 | set_neopixel, 630 | set_neopixel_rgbw, 631 | read_adc_all, 632 | read_adc, 633 | read_pressure, 634 | read_temp, 635 | read_leak, 636 | read_mag, 637 | read_accel, 638 | read_gyro, 639 | set_pwm_enable, 640 | set_pwm_freq_hz, 641 | set_pwm_channel_value, 642 | set_pwm_channel_duty_cycle, 643 | set_pwm_channels_value, 644 | set_pwm_channels_duty_cycle, 645 | set_pwm_channels_values, 646 | set_pwm_channels_duty_cycle_values 647 | ] 648 | ); 649 | --------------------------------------------------------------------------------