From 7387ea1555ac0c76237158d34e5bfc577113c441 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 5 Jun 2021 03:16:29 -0700 Subject: [PATCH 01/72] [no ci] preliminary CMake implementation --- .github/workflows/build_linux.yml | 174 +++++++++++++++++++++ .github/workflows/doxygen.yml | 13 +- CMakeLists.txt | 84 +++++++++- cmake/CPackInfo.cmake | 73 +++++++++ cmake/Cache.cmake | 31 ++++ cmake/CompilerWarnings.cmake | 78 +++++++++ cmake/GetLibInfo.cmake | 43 +++++ cmake/PreventInSourceBuilds.cmake | 18 +++ cmake/StandardProjectSettings.cmake | 37 +++++ cmake/detectCPU.cmake | 43 +++++ cmake/toolchains/arm64.cmake | 34 ++++ cmake/toolchains/armhf.cmake | 34 ++++ cmake/toolchains/default.cmake | 1 + cmake/toolchains/i686.cmake | 34 ++++ cmake/toolchains/x86_64.cmake | 34 ++++ usePicoSDK.cmake => cmake/usePicoSDK.cmake | 0 examples_RPi/CMakeLists.txt | 30 ++++ examples_RPi/RF24Mesh_Example.cpp | 82 +++++----- examples_RPi/RF24Mesh_Example_Master.cpp | 83 +++++----- library.json | 2 +- library.properties | 2 +- 21 files changed, 829 insertions(+), 101 deletions(-) create mode 100644 .github/workflows/build_linux.yml create mode 100644 cmake/CPackInfo.cmake create mode 100644 cmake/Cache.cmake create mode 100644 cmake/CompilerWarnings.cmake create mode 100644 cmake/GetLibInfo.cmake create mode 100644 cmake/PreventInSourceBuilds.cmake create mode 100644 cmake/StandardProjectSettings.cmake create mode 100644 cmake/detectCPU.cmake create mode 100644 cmake/toolchains/arm64.cmake create mode 100644 cmake/toolchains/armhf.cmake create mode 100644 cmake/toolchains/default.cmake create mode 100644 cmake/toolchains/i686.cmake create mode 100644 cmake/toolchains/x86_64.cmake rename usePicoSDK.cmake => cmake/usePicoSDK.cmake (100%) create mode 100644 examples_RPi/CMakeLists.txt diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml new file mode 100644 index 0000000..c8c96c8 --- /dev/null +++ b/.github/workflows/build_linux.yml @@ -0,0 +1,174 @@ +name: Linux build + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + release: + types: [published, edited] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + using_cmake: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + toolchain: + - compiler: "armhf" + usr_dir: "arm-linux-gnueabihf" + - compiler: "arm64" + usr_dir: "aarch64-linux-gnu" + # - compiler: "x86_64" + # usr_dir: "x86_64-linux-gnux32" + # - compiler: "i686" + # usr_dir: "i686-linux-gnu" + - compiler: "default" # github runner is hosted on a "amd64" + usr_dir: "local" + + steps: + - uses: actions/checkout@v1 + - name: install rpmbuild + run: sudo apt-get install rpm + + # - name: provide toolchain (for x86_64) + # if: ${{ matrix.toolchain.compiler == 'x86_64' }} + # run: | + # sudo apt-get update + # sudo apt-get install gcc-x86-64-linux-gnux32 g++-x86-64-linux-gnux32 + + # - name: provide toolchain (for i686) + # if: ${{ matrix.toolchain.compiler == 'i686' }} + # run: | + # sudo apt-get update + # sudo apt-get install gcc-i686-linux-gnu g++-i686-linux-gnu + + - name: provide toolchain (for arm64) + if: ${{ matrix.toolchain.compiler == 'arm64' }} + run: | + sudo apt-get update + sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + + - name: provide toolchain (for armhf) + if: ${{ matrix.toolchain.compiler == 'armhf' }} + run: | + sudo apt-get update + sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf + + - name: checkout RF24 + uses: actions/checkout@v2 + with: + repository: nRF24/RF24 + ref: rp2xxx + + - name: checkout RF24Network + uses: actions/checkout@v2 + with: + repository: nRF24/RF24Network + ref: CMake-4-Linux + + - name: build & install RF24 + if: ${{ matrix.toolchain.compiler == 'default' }} + working-directory: ${{ github.workspace }}/RF24 + run: | + mkdir build + cd build + cmake .. -D CMAKE_BUILD_TYPE=$BUILD_TYPE -D RF24_DRIVER=SPIDEV \ + -D CMAKE_INSTALL_PREFIX=/usr/${{ matrix.toolchain.usr_dir }} \ + -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/${{ matrix.toolchain.compiler }}.cmake + sudo make install + + - name: build & install RF24Network + if: ${{ matrix.toolchain.compiler == 'default' }} + working-directory: ${{ github.workspace }}/RF24RF24Network + run: | + mkdir build + cd build + cmake .. -D CMAKE_BUILD_TYPE=$BUILD_TYPE \ + -D CMAKE_INSTALL_PREFIX=/usr/${{ matrix.toolchain.usr_dir }} \ + -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/${{ matrix.toolchain.compiler }}.cmake + sudo make install + + - name: create CMake build environment + run: cmake -E make_directory ${{ github.workspace }}/build + + - name: configure lib + if: ${{ matrix.toolchain.compiler == 'default' }} + working-directory: ${{ github.workspace }}/build + run: cmake .. -D CMAKE_BUILD_TYPE=$BUILD_TYPE -D RF24_DRIVER=${{ matrix.driver }} + + - name: configure lib (with toolchain compilers) + if: ${{ matrix.toolchain.compiler != 'default' }} + working-directory: ${{ github.workspace }}/build + run: | + cmake .. -D CMAKE_BUILD_TYPE=$BUILD_TYPE \ + -D CMAKE_INSTALL_PREFIX=/usr/${{ matrix.toolchain.usr_dir }} \ + -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/${{ matrix.toolchain.compiler }}.cmake + + - name: build lib + working-directory: ${{ github.workspace }}/build + run: cmake --build . + + - name: install lib + working-directory: ${{ github.workspace }}/build + run: sudo cmake --install . + + - name: package lib + working-directory: ${{ github.workspace }}/build + run: sudo cpack + + - name: Save artifact + uses: actions/upload-artifact@v2 + with: + name: "pkg_RF24Mesh" + path: | + ${{ github.workspace }}/build/pkgs/*.deb + ${{ github.workspace }}/build/pkgs/*.rpm + + - name: Upload Release assets + if: github.event_name == 'release' && (matrix.toolchain.compiler == 'armhf' || matrix.toolchain.compiler == 'arm64') + uses: csexton/release-asset-action@master + with: + pattern: "${{ github.workspace }}/build/pkgs/librf24*" + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: clean build environment + working-directory: ${{ github.workspace }}/build + run: sudo rm -r ./* + + - name: configure examples + working-directory: ${{ github.workspace }}/build + run: | + cmake ../examples_RPi \ + -D CMAKE_TOOLCHAIN_FILE=../cmake/toolchains/${{ matrix.toolchain.compiler }}.cmake + + - name: build examples + working-directory: ${{ github.workspace }}/build + run: | + cmake --build . + file ./RF24Mesh_Example + + # Apparently we need python3 for ARM installed (or at least the Python-C API for arm) + - name: provide python wrapper prerequisites + if: ${{ matrix.toolchain.compiler == 'default' }} + # python3-rpi.gpio is only required for physical hardware + run: sudo apt-get install python3-dev libboost-python-dev python3-setuptools + + - name: create alias symlink to libboost_python3*.so + if: ${{ matrix.toolchain.compiler == 'default' }} + run: sudo ln -s $(ls /usr/lib/$(ls /usr/lib/gcc | tail -1)/libboost_python3*.so | tail -1) /usr/lib/$(ls /usr/lib/gcc | tail -1)/libboost_python3.so + + - name: build python wrapper + if: ${{ matrix.toolchain.compiler == 'default' }} + working-directory: ${{ github.workspace }}/pyRF24Mesh + run: python3 setup.py build + + - name: install python wrapper + if: ${{ matrix.toolchain.compiler == 'default' }} + working-directory: ${{ github.workspace }}/pyRF24Mesh + run: sudo python3 setup.py install \ No newline at end of file diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index da74cd3..62fa304 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -2,17 +2,12 @@ name: DoxyGen build on: pull_request: - branches: - - master + branches: [master] push: - branches: - - master + branches: [master] release: - branches: - - master - types: - - published - - edited + branches: [master] + types: [published, edited] jobs: build-doxygen: diff --git a/CMakeLists.txt b/CMakeLists.txt index 50109cb..943b9ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,88 @@ # (or more exactly: if we just got included in a pico-sdk based project) if (PICO_SDK_PATH) # If so, load the relevant CMakeLists-file but don't do anything else - include(${CMAKE_CURRENT_LIST_DIR}/usePicoSDK.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/cmake/usePicoSDK.cmake) return() endif() -## TODO: Non-PicoSDK builds +############################ +# for non-RPi-Pico platforms +############################ +cmake_minimum_required(VERSION 3.15) + +# Set the project name to your project name +project(RF24Mesh C CXX) +include(cmake/StandardProjectSettings.cmake) +include(cmake/PreventInSourceBuilds.cmake) + +# Link this 'library' to set the c++ standard / compile-time options requested +add_library(project_options INTERFACE) +target_compile_features(project_options INTERFACE cxx_std_17) +add_compile_options(-Ofast -Wall) + +# detect CPU and add compiler flags accordingly +include(cmake/detectCPU.cmake) + +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + option(ENABLE_BUILD_WITH_TIME_TRACE "Enable -ftime-trace to generate time tracing .json files on clang" OFF) + if(ENABLE_BUILD_WITH_TIME_TRACE) + add_compile_definitions(project_options INTERFACE -ftime-trace) + endif() +endif() + +# Link this 'library' to use the warnings specified in CompilerWarnings.cmake +add_library(project_warnings INTERFACE) + +# enable cache system +include(cmake/Cache.cmake) + +# standard compiler warnings +include(cmake/CompilerWarnings.cmake) +set_project_warnings(project_warnings) + +# get library info from Arduino IDE's required library.properties file +include(cmake/GetLibInfo.cmake) # sets the variable LibTargetName + +# setup CPack options +include(cmake/CPackInfo.cmake) + +########################### +# create target for bulding the RF24Log lib +########################### +add_library(${LibTargetName} SHARED + RF24Mesh.cpp + ) +target_include_directories(${LibTargetName} PUBLIC + ${CMAKE_CURRENT_LIST_DIR} + ) + +target_link_libraries(${LibTargetName} INTERFACE + project_options + project_warnings + ) + +set_target_properties( + ${LibTargetName} + PROPERTIES + SOVERSION ${${LibName}_VERSION_MAJOR} + VERSION ${${LibName}_VERSION_STRING} + ) + +########################### +# target install rules for the RF24Log lib +########################### +install(TARGETS ${LibTargetName} + DESTINATION lib + ) + +install(FILES + RF24Mesh.h + RF24Mesh_config.h + DESTINATION include/RF24Mesh + ) + +# CMAKE_CROSSCOMPILING is only TRUE when CMAKE_TOOLCHAIN_FILE is specified via CLI +if(CMAKE_HOST_UNIX AND "${CMAKE_CROSSCOMPILING}" STREQUAL "FALSE") + install(CODE "message(STATUS \"Updating ldconfig\")") + install(CODE "execute_process(COMMAND ldconfig)") +endif() diff --git a/cmake/CPackInfo.cmake b/cmake/CPackInfo.cmake new file mode 100644 index 0000000..be698eb --- /dev/null +++ b/cmake/CPackInfo.cmake @@ -0,0 +1,73 @@ +# This module will build a debian compatible package to install - handy for cross-compiling + +if(NOT PKG_REV) + set(PKG_REV "1") +endif() + +# get target arch if not cross-compiling +if(NOT TARGET_ARCH) # TARGET_ARCH is defined only in the toolchain_.cmake files + if(WIN32) + set(TARGET_ARCH $ENV{PROCESSOR_ARCHITECTURE}) + else() + execute_process(COMMAND dpkg --print-architecture + OUTPUT_VARIABLE TARGET_ARCH + ) + endif() + string(STRIP "${TARGET_ARCH}" TARGET_ARCH) +endif() + +# set the Cpack generators (specific to types of packages to create) +if(NOT WIN32) + set(CPACK_GENERATOR DEB RPM) # RPM requires rpmbuild executable +else() + set(CPACK_GENERATOR "") # should find out how to build vcpkg packages +endif() + +# assemble a debian package filename from known info +include(InstallRequiredSystemLibraries) +set(CPACK_PACKAGE_FILE_NAME "lib${LibTargetName}_${${LibName}_VERSION_STRING}-${PKG_REV}_${TARGET_ARCH}") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE") +set(CPACK_PACKAGE_VERSION_MAJOR "${${LibName}_VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${${LibName}_VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${${LibName}_VERSION_PATCH}") +set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}/pkgs") # for easy uploading to github releases + +if(NOT WIN32) + ############################### + # info specific debian packages + ############################### + set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${TARGET_ARCH}) + set(CPACK_DEBIAN_PACKAGE_SECTION libs) + set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE) + set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON) + + ############################### + # info specific rpm (fedora) packages + ############################### + set(CPACK_RPM_FILE_NAME "lib${LibTargetName}-${${LibName}_VERSION_STRING}-${PKG_REV}.${TARGET_ARCH}.rpm") + set(CPACK_RPM_PACKAGE_ARCHITECTURE ${TARGET_ARCH}) + set(CPACK_RPM_PACKAGE_LICENSE "GPLv2.0") + set(CPACK_RPM_PACKAGE_VENDOR "Humanity") + + # create a post-install & post-removal scripts to update linker + set(POST_SCRIPTS + ${CMAKE_BINARY_DIR}/DEBIAN/postrm + ${CMAKE_BINARY_DIR}/DEBIAN/postinst + ) + foreach(script ${POST_SCRIPTS}) + file(WRITE ${script} /sbin/ldconfig) + execute_process(COMMAND chmod +x ${script}) + execute_process(COMMAND chmod 775 ${script}) + endforeach() + # declare scripts for deb pkgs + list(JOIN POST_SCRIPTS ";" EXTRA_CTRL_FILES) + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA EXTRA_CTRL_FILES) + # declare scripts for rpm pkgs + list(POP_FRONT POST_SCRIPTS CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE) + list(POP_FRONT POST_SCRIPTS CPACK_RPM_POST_INSTALL_SCRIPT_FILE) + + message(STATUS "ready to package: ${CPACK_PACKAGE_FILE_NAME}.deb") + message(STATUS "ready to package: ${CPACK_RPM_FILE_NAME}") +endif() + +include(CPack) diff --git a/cmake/Cache.cmake b/cmake/Cache.cmake new file mode 100644 index 0000000..4cc2e8c --- /dev/null +++ b/cmake/Cache.cmake @@ -0,0 +1,31 @@ +option(ENABLE_CACHE "Enable cache if available" ON) +if(NOT ENABLE_CACHE) + return() +endif() + +set(CACHE_OPTION + "ccache" + CACHE STRING "Compiler cache to be used" + ) +set(CACHE_OPTION_VALUES "ccache" "sccache") +set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) +list( + FIND + CACHE_OPTION_VALUES + ${CACHE_OPTION} + CACHE_OPTION_INDEX + ) + +if(${CACHE_OPTION_INDEX} EQUAL -1) + message(STATUS + "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}" + ) +endif() + +find_program(CACHE_BINARY ${CACHE_OPTION}) +if(CACHE_BINARY) + message(STATUS "${CACHE_OPTION} found and enabled") + set(CMAKE_CXX_COMPILER_LAUNCHER ${CACHE_BINARY}) +else() + message(WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") +endif() diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 0000000..9031160 --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,78 @@ +# from here: +# +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md + +function(set_project_warnings project_name) + option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" TRUE) + + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not + # be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside + # the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + + set(CLANG_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + ) + + if(WARNINGS_AS_ERRORS) + set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) + set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) + endif() + + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + ) + + if(MSVC) + set(PROJECT_WARNINGS ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_WARNINGS ${CLANG_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_WARNINGS ${GCC_WARNINGS}) + else() + message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") + endif() + + target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) + +endfunction() diff --git a/cmake/GetLibInfo.cmake b/cmake/GetLibInfo.cmake new file mode 100644 index 0000000..99ee0ba --- /dev/null +++ b/cmake/GetLibInfo.cmake @@ -0,0 +1,43 @@ +# get lib info from the maintained library.properties required by the Arduino IDE +file(STRINGS ${CMAKE_SOURCE_DIR}/../library.properties LibInfo) +foreach(line ${LibInfo}) + string(FIND ${line} "=" label_delimiter) + if(${label_delimiter} GREATER 0) + math(EXPR label_delimiter "${label_delimiter} + 1") + string(FIND ${line} "version" has_version) + string(FIND ${line} "name" has_name) + string(FIND ${line} "maintainer" has_maintainer) + string(FIND ${line} "sentence" has_sentence) + string(FIND ${line} "url" has_url) + string(FIND ${line} "paragraph" has_paragraph) + if(${has_version} EQUAL 0) + string(SUBSTRING ${line} ${label_delimiter} "-1" VERSION) + elseif(${has_name} EQUAL 0) + string(SUBSTRING ${line} ${label_delimiter} "-1" LibName) + elseif(${has_maintainer} EQUAL 0) + string(SUBSTRING ${line} ${label_delimiter} "-1" CPACK_PACKAGE_CONTACT) + elseif(${has_sentence} EQUAL 0) + string(SUBSTRING ${line} ${label_delimiter} "-1" CPACK_PACKAGE_DESCRIPTION_SUMMARY) + elseif(${has_url} EQUAL 0) + string(SUBSTRING ${line} ${label_delimiter} "-1" CMAKE_PROJECT_HOMEPAGE_URL) + elseif(${has_paragraph} EQUAL 0) + string(SUBSTRING ${line} ${label_delimiter} "-1" CPACK_PACKAGE_DESCRIPTION) + endif() + endif() +endforeach() + +# convert the LibName to lower case & strip surrounding whitespace +string(TOLOWER ${LibName} LibTargetName) + +#parse the version information into pieces. +string(REGEX REPLACE "^([0-9]+)\\..*" "\\1" VERSION_MAJOR "${VERSION}") +string(REGEX REPLACE "^[0-9]+\\.([0-9]+).*" "\\1" VERSION_MINOR "${VERSION}") +string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" VERSION_PATCH "${VERSION}") + +# this is the library version +set(${LibName}_VERSION_MAJOR ${VERSION_MAJOR}) +set(${LibName}_VERSION_MINOR ${VERSION_MINOR}) +set(${LibName}_VERSION_PATCH ${VERSION_PATCH}) +set(${LibName}_VERSION_STRING ${${LibName}_VERSION_MAJOR}.${${LibName}_VERSION_MINOR}.${${LibName}_VERSION_PATCH}) + +message(STATUS "${LibName} library version: ${${LibName}_VERSION_STRING}") diff --git a/cmake/PreventInSourceBuilds.cmake b/cmake/PreventInSourceBuilds.cmake new file mode 100644 index 0000000..dc4fd81 --- /dev/null +++ b/cmake/PreventInSourceBuilds.cmake @@ -0,0 +1,18 @@ +# +# This function will prevent in-source builds +function(AssureOutOfSourceBuilds) + # make sure the user doesn't play dirty with symlinks + get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) + + # disallow in-source builds + if("${srcdir}" STREQUAL "${bindir}") + message("######################################################") + message("Warning: in-source builds are disabled") + message("Please create a separate build directory and run cmake from there") + message("######################################################") + message(FATAL_ERROR "Quitting configuration") + endif() +endfunction() + +assureoutofsourcebuilds() diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake new file mode 100644 index 0000000..0414e99 --- /dev/null +++ b/cmake/StandardProjectSettings.cmake @@ -0,0 +1,37 @@ +# Set a default build type if none was specified +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") + set(CMAKE_BUILD_TYPE + RelWithDebInfo + CACHE STRING "Choose the type of build." FORCE + ) + # Set the possible values of build type for cmake-gui, ccmake + set_property( + CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS + "Debug" + "Release" + "MinSizeRel" + "RelWithDebInfo" + ) +endif() + +# Generate compile_commands.json to make it easier to work with clang based tools +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) + +if(ENABLE_IPO) + include(CheckIPOSupported) + check_ipo_supported( + RESULT + result + OUTPUT + output + ) + if(result) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(SEND_ERROR "IPO is not supported: ${output}") + endif() +endif() diff --git a/cmake/detectCPU.cmake b/cmake/detectCPU.cmake new file mode 100644 index 0000000..ebd7fe7 --- /dev/null +++ b/cmake/detectCPU.cmake @@ -0,0 +1,43 @@ +# try to get the CPU model using a Linux bash command +execute_process(COMMAND cat /proc/cpuinfo + OUTPUT_VARIABLE CPU_MODEL + ) + +# If above command is not executed on an actual SOC board (& compatible OS), then +# there won't be a "Hardware" field to describe the CPU model +string(FIND ${CPU_MODEL} "Hardware" cpu_info_has_hw_field) +if(${cpu_info_has_hw_field} GREATER 0) # Hardware field does exist + string(SUBSTRING ${CPU_MODEL} ${cpu_info_has_hw_field} -1 CPU_MODEL) + string(REGEX MATCH "[ ]+([A-Za-z0-9_])+" SOC ${CPU_MODEL}) + string(STRIP ${SOC} SOC) +else() # Hardware field does not exist + set(SOC "UNKNOWN") # use this string as a sentinel +endif() + +# detect machine hardware name +execute_process(COMMAND uname -m + OUTPUT_VARIABLE CPU_TYPE) +string(STRIP "${CPU_TYPE}" CPU_TYPE) +message(STATUS "detected CPU type: ${CPU_TYPE}") + +# add compiler flags to optomize builds with arm-linux-gnueabihf-g* compilers +if("${CMAKE_C_COMPILER}" STREQUAL "/usr/bin/arm-linux-gnueabihf-gcc" AND + "${CMAKE_CXX_COMPILER}" STREQUAL "/usr/bin/arm-linux-gnueabihf-g++") + if("${SOC}" STREQUAL "BCM2835") + add_compile_options(-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard) + elseif("$SOC" STREQUAL "BCM2836") + add_compile_options(-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard) + elseif("$SOC" STREQUAL "AM33XX") + add_compile_options(-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard) + elseif("$SOC" STREQUAL "A10") + add_compile_options(-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard) + elseif("$SOC" STREQUAL "A13") + add_compile_options(-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard) + elseif("$SOC" STREQUAL "A20") + add_compile_options(-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard) + elseif("$SOC" STREQUAL "H3") + add_compile_options(-march=armv8-a -mtune=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard) + endif() +endif() + +message(STATUS "detected SoC: ${SOC}") \ No newline at end of file diff --git a/cmake/toolchains/arm64.cmake b/cmake/toolchains/arm64.cmake new file mode 100644 index 0000000..add14e1 --- /dev/null +++ b/cmake/toolchains/arm64.cmake @@ -0,0 +1,34 @@ +###################### FOR CROSS-COMPILING using the aarch64-linux-gnu-g** compiler +# invoke this toolchain file using `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/.cmake` +# this file is meant to be used generically, but will not work for all CMake projects +# this toolchain file's cmds was copied from the CMake docs then modified for better explanation and re-use + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm64) +set(TARGET_ARCH arm64) # only used in cmake/createDebianPkg.cmake +set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++) + +# CMAKE_SYSROOT can only be set in a toolchain file +# set(CMAKE_SYSROOT /usr/aarch64-linux-gnu) # useful when a target machine's files are available + +# set the directory for searching installed headers +# add_compile_options(-I /usr/aarch64-linux-gnu/include) # this may not be best practice + +#[[ +# CMAKE_STAGING_PREFIX is only useful for transfering a built CMake project to a target machine +set(CMAKE_STAGING_PREFIX /home/devel/stage) # use CMAKE_INSTALL_PREFIX instead (see below comments) + +CMAKE_FIND_ROOT_PATH is an empty list by default (this list can be modified where applicable) +if cross-compiling a dependent lib (like MRAA - which is optional), then +set the lib's CMAKE_INSTALL_PREFIX to a value that is appended to RF24 lib's CMAKE_FIND_ROOT_PATH +example using MRAA: +(for MRAA/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=path/to/RF24/repo/cmake/toolchains/arm64.cmake -D CMAKE_INSTALL_PREFIX:PATH=/usr/aarch64-linux-gnu +(for RF24/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/arm64.cmake +]] +list(APPEND CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu) +# message("CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search CMAKE_SYSROOT when find_program() is called +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_library() is called +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_file() is called +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_package() is called diff --git a/cmake/toolchains/armhf.cmake b/cmake/toolchains/armhf.cmake new file mode 100644 index 0000000..f1611f2 --- /dev/null +++ b/cmake/toolchains/armhf.cmake @@ -0,0 +1,34 @@ +###################### FOR CROSS-COMPILING using the arm-linux-gnueabihf-g** compiler +# invoke this toolchain file using `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/.cmake` +# this file is meant to be used generically, but will not work for all CMake projects +# this toolchain file's cmds was copied from the CMake docs then modified for better explanation and re-use + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR armhf) +set(TARGET_ARCH armhf) # only used in cmake/CPackInfo.cmake +set(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc) +set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++) + +# CMAKE_SYSROOT can only be set in a toolchain file +# set(CMAKE_SYSROOT /usr/arm-linux-gnueabihf) # useful when a target machine's files are available + +# set the directory for searching installed headers +# add_compile_options(-I /usr/arm-linux-gnueabihf/include) # this may not be best practice + +#[[ +# CMAKE_STAGING_PREFIX is only useful for transfering a built CMake project to a target machine +set(CMAKE_STAGING_PREFIX /home/devel/stage) # use CMAKE_INSTALL_PREFIX instead (see below comments) + +CMAKE_FIND_ROOT_PATH is an empty list by default (this list can be modified where applicable) +if cross-compiling a dependent lib (like MRAA - which is optional), then +set the lib's CMAKE_INSTALL_PREFIX to a value that is appended to RF24 lib's CMAKE_FIND_ROOT_PATH +example using MRAA: +(for MRAA/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=path/to/RF24/repo/cmake/toolchains/arm.cmake -D CMAKE_INSTALL_PREFIX:PATH=/usr/arm-linux-gnueabihf +(for RF24/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/arm.cmake +]] +list(APPEND CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf) +# message("CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search CMAKE_SYSROOT when find_program() is called +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_library() is called +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_file() is called +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_package() is called diff --git a/cmake/toolchains/default.cmake b/cmake/toolchains/default.cmake new file mode 100644 index 0000000..e67d723 --- /dev/null +++ b/cmake/toolchains/default.cmake @@ -0,0 +1 @@ +# empty toolchain file to allow CI scripts to still use system default toolchains \ No newline at end of file diff --git a/cmake/toolchains/i686.cmake b/cmake/toolchains/i686.cmake new file mode 100644 index 0000000..344fd8b --- /dev/null +++ b/cmake/toolchains/i686.cmake @@ -0,0 +1,34 @@ +###################### FOR CROSS-COMPILING using the i686-linux-gnu-g** compiler +# invoke this toolchain file using `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/.cmake` +# this file is meant to be used generically, but will not work for all CMake projects +# this toolchain file's cmds was copied from the CMake docs then modified for better explanation and re-use + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR i686) +set(TARGET_ARCH i686) # only used in cmake/createDebianPkg.cmake +set(CMAKE_C_COMPILER /usr/bin/i686-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER /usr/bin/i686-linux-gnu-g++) + +# CMAKE_SYSROOT can only be set in a toolchain file +# set(CMAKE_SYSROOT /usr/i686-linux-gnu) # useful when a target machine's files are available + +# set the directory for searching installed headers +# add_compile_options(-I /usr/i686-linux-gnu/include) # this may not be best practice + +#[[ +# CMAKE_STAGING_PREFIX is only useful for transfering a built CMake project to a target machine +set(CMAKE_STAGING_PREFIX /home/devel/stage) # use CMAKE_INSTALL_PREFIX instead (see below comments) + +CMAKE_FIND_ROOT_PATH is an empty list by default (this list can be modified where applicable) +if cross-compiling a dependent lib (like MRAA - which is optional), then +set the lib's CMAKE_INSTALL_PREFIX to a value that is appended to RF24 lib's CMAKE_FIND_ROOT_PATH +example using MRAA: +(for MRAA/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=path/to/RF24/repo/cmake/toolchains/i686.cmake -D CMAKE_INSTALL_PREFIX:PATH=/usr/i686-linux-gnu +(for RF24/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/i686.cmake +]] +list(APPEND CMAKE_FIND_ROOT_PATH /usr/i686-linux-gnu) +# message("CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search CMAKE_SYSROOT when find_program() is called +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_library() is called +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_file() is called +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_package() is called diff --git a/cmake/toolchains/x86_64.cmake b/cmake/toolchains/x86_64.cmake new file mode 100644 index 0000000..b75ab3f --- /dev/null +++ b/cmake/toolchains/x86_64.cmake @@ -0,0 +1,34 @@ +###################### FOR CROSS-COMPILING using the x86_64-linux-gnux32-g** compiler +# invoke this toolchain file using `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/.cmake` +# this file is meant to be used generically, but will not work for all CMake projects +# this toolchain file's cmds was copied from the CMake docs then modified for better explanation and re-use + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR x86_64) +set(TARGET_ARCH x86_64) # only used in cmake/createDebianPkg.cmake +set(CMAKE_C_COMPILER /usr/bin/x86_64-linux-gnux32-gcc) +set(CMAKE_CXX_COMPILER /usr/bin/x86_64-linux-gnux32-g++) + +# CMAKE_SYSROOT can only be set in a toolchain file +# set(CMAKE_SYSROOT /usr/x86_64-linux-gnux32) # useful when a target machine's files are available + +# set the directory for searching installed headers +# add_compile_options(-I /usr/x86_64-linux-gnux32/include) # this may not be best practice + +#[[ +# CMAKE_STAGING_PREFIX is only useful for transfering a built CMake project to a target machine +set(CMAKE_STAGING_PREFIX /home/devel/stage) # use CMAKE_INSTALL_PREFIX instead (see below comments) + +CMAKE_FIND_ROOT_PATH is an empty list by default (this list can be modified where applicable) +if cross-compiling a dependent lib (like MRAA - which is optional), then +set the lib's CMAKE_INSTALL_PREFIX to a value that is appended to RF24 lib's CMAKE_FIND_ROOT_PATH +example using MRAA: +(for MRAA/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=path/to/RF24/repo/cmake/toolchains/x86_64.cmake -D CMAKE_INSTALL_PREFIX:PATH=/usr/x86_64-linux-gnux32 +(for RF24/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/x86_64.cmake +]] +list(APPEND CMAKE_FIND_ROOT_PATH /usr/x86_64-linux-gnux32) +# message("CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search CMAKE_SYSROOT when find_program() is called +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_library() is called +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_file() is called +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_package() is called diff --git a/usePicoSDK.cmake b/cmake/usePicoSDK.cmake similarity index 100% rename from usePicoSDK.cmake rename to cmake/usePicoSDK.cmake diff --git a/examples_RPi/CMakeLists.txt b/examples_RPi/CMakeLists.txt new file mode 100644 index 0000000..dff3704 --- /dev/null +++ b/examples_RPi/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.12) + +# iterate over a list of examples by filename +set(EXAMPLES_LIST + RF24Mesh_Example + RF24Mesh_Example_Master + ) + +project(RF24LogExamples) +add_compile_options(-Ofast -Wall) # passing the compiler a `-pthread` flag doesn't work here + +# detect the CPU make and type +include(../cmake/detectCPU.cmake) # sets the variable SOC accordingly + +find_library(RF24 rf24 REQUIRED) +message(STATUS "using RF24 library: ${RF24}") + +find_library(RF24Network rf24 REQUIRED) +message(STATUS "using RF24Network library: ${RF24Network}") + +find_library(RF24Mesh rf24 REQUIRED) +message(STATUS "using RF24Mesh library: ${RF24Mesh}") + +foreach(example ${EXAMPLES_LIST}) + # make a target + add_executable(${example} ${example}.cpp) + + # link the RF24 lib to the target. Notice we specify pthread as a linked lib here + target_link_libraries(${example} PUBLIC ${RF24} pthread ${RF24Network} ${RF24Mesh}) +endforeach() diff --git a/examples_RPi/RF24Mesh_Example.cpp b/examples_RPi/RF24Mesh_Example.cpp index ad813ef..27c295c 100644 --- a/examples_RPi/RF24Mesh_Example.cpp +++ b/examples_RPi/RF24Mesh_Example.cpp @@ -1,7 +1,5 @@ - - - /** RF24Mesh_Example.cpp by TMRh20 - * +/** RF24Mesh_Example.cpp by TMRh20 + * * Note: This sketch only functions on -RPi- * * This example sketch shows how to manually configure a node via RF24Mesh, and send data to the @@ -10,56 +8,56 @@ * nodes to change position in relation to each other and the master node. * */ - -#include "RF24Mesh/RF24Mesh.h" +#include "RF24Mesh/RF24Mesh.h" #include #include - -RF24 radio(22,0); +RF24 radio(22, 0); RF24Network network(radio); -RF24Mesh mesh(radio,network); +RF24Mesh mesh(radio, network); -uint32_t displayTimer=0; +uint32_t displayTimer = 0; + +int main(int argc, char **argv) +{ -int main(int argc, char** argv) { - // Set the nodeID to 0 for the master node mesh.setNodeID(4); // Connect to the mesh - printf("start nodeID %d\n",mesh.getNodeID()); + printf("start nodeID %d\n", mesh.getNodeID()); mesh.begin(); radio.printDetails(); -while(1) -{ - - // Call mesh.update to keep the network updated - mesh.update(); - - // Send the current millis() to the master node every second - if(millis() - displayTimer >= 1000){ - displayTimer = millis(); - - if(!mesh.write(&displayTimer,'M',sizeof(displayTimer))){ - - // If a write fails, check connectivity to the mesh network - if( ! mesh.checkConnection() ){ - // The address could be refreshed per a specified timeframe or only when sequential writes fail, etc. - printf("Renewing Address\n"); - mesh.renewAddress(); - }else{ - printf("Send fail, Test OK\n"); + while (1) + { + // Call mesh.update to keep the network updated + mesh.update(); + + // Send the current millis() to the master node every second + if (millis() - displayTimer >= 1000) + { + displayTimer = millis(); + + if (!mesh.write(&displayTimer, 'M', sizeof(displayTimer))) + { + // If a write fails, check connectivity to the mesh network + if (!mesh.checkConnection()) + { + // The address could be refreshed per a specified timeframe or only when sequential writes fail, etc. + printf("Renewing Address\n"); + mesh.renewAddress(); + } + else + { + printf("Send fail, Test OK\n"); + } } - }else{ - printf("Send OK: %u\n",displayTimer); - } - } - delay(1); + else + { + printf("Send OK: %u\n", displayTimer); + } + } + delay(1); } -return 0; + return 0; } - - - - diff --git a/examples_RPi/RF24Mesh_Example_Master.cpp b/examples_RPi/RF24Mesh_Example_Master.cpp index e0bf957..dd1d2df 100644 --- a/examples_RPi/RF24Mesh_Example_Master.cpp +++ b/examples_RPi/RF24Mesh_Example_Master.cpp @@ -1,7 +1,5 @@ - - - /** RF24Mesh_Example_Master.ino by TMRh20 - * +/** RF24Mesh_Example_Master.ino by TMRh20 + * * Note: This sketch only functions on -Arduino Due- * * This example sketch shows how to manually configure a node via RF24Mesh as a master node, which @@ -12,20 +10,16 @@ * in a manner similar to DHCP. * */ - -#include "RF24Mesh/RF24Mesh.h" +#include "RF24Mesh/RF24Mesh.h" #include #include - -RF24 radio(22,0); +RF24 radio(22, 0); RF24Network network(radio); -RF24Mesh mesh(radio,network); - +RF24Mesh mesh(radio, network); - -int main(int argc, char** argv) { - +int main(int argc, char **argv) +{ // Set the nodeID to 0 for the master node mesh.setNodeID(0); // Connect to the mesh @@ -33,39 +27,36 @@ int main(int argc, char** argv) { mesh.begin(); radio.printDetails(); -while(1) -{ - - // Call network.update as usual to keep the network updated - mesh.update(); - - // In addition, keep the 'DHCP service' running on the master node so addresses will - // be assigned to the sensor nodes - mesh.DHCP(); - - - // Check for incoming data from the sensors - while(network.available()){ -// printf("rcv\n"); - RF24NetworkHeader header; - network.peek(header); - - uint32_t dat=0; - switch(header.type){ - // Display the incoming millis() values from the sensor nodes - case 'M': network.read(header,&dat,sizeof(dat)); - printf("Rcv %u from 0%o\n",dat,header.from_node); - break; - default: network.read(header,0,0); - printf("Rcv bad type %d from 0%o\n",header.type,header.from_node); - break; + while (1) + { + // Call network.update as usual to keep the network updated + mesh.update(); + + // In addition, keep the 'DHCP service' running on the master node so addresses will + // be assigned to the sensor nodes + mesh.DHCP(); + + // Check for incoming data from the sensors + while (network.available()) + { + // printf("rcv\n"); + RF24NetworkHeader header; + network.peek(header); + + uint32_t dat = 0; + switch (header.type) + { // Display the incoming millis() values from the sensor nodes + case 'M': + network.read(header, &dat, sizeof(dat)); + printf("Rcv %u from 0%o\n", dat, header.from_node); + break; + default: + network.read(header, 0, 0); + printf("Rcv bad type %d from 0%o\n", header.type, header.from_node); + break; + } } + delay(2); } -delay(2); - } -return 0; + return 0; } - - - - diff --git a/library.json b/library.json index 813adf0..7d0ee9e 100644 --- a/library.json +++ b/library.json @@ -7,7 +7,7 @@ "type": "git", "url": "https://github.com/TMRh20/RF24Mesh.git" }, - "version": "1.1.5", + "version": "1.1.6", "dependencies": [ { "name": "RF24", "authors": "TMRh20", "frameworks": "arduino" }, { "name": "RF24Network", "authors": "TMRh20", "frameworks": "arduino" } diff --git a/library.properties b/library.properties index 36f83dc..6ebcb80 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=RF24Mesh -version=1.1.5 +version=1.1.6 author=TMRh20 maintainer=TMRh20,Avamander sentence=OSI Layer 7, Automated 'mesh' style networking for nrf24L01(+) radios. From a90920ab3782b568734f5e6e419af8ae6521370a Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 5 Jun 2021 04:13:08 -0700 Subject: [PATCH 02/72] add GPLv2 license; this builds locally --- .github/workflows/build_linux.yml | 42 +--- .gitignore | 5 +- CMakeLists.txt | 8 + LICENSE | 339 ++++++++++++++++++++++++++++++ cmake/CPackInfo.cmake | 2 +- cmake/GetLibInfo.cmake | 2 +- examples_RPi/CMakeLists.txt | 4 +- 7 files changed, 365 insertions(+), 37 deletions(-) create mode 100644 LICENSE diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index c8c96c8..b84dfdb 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -32,7 +32,6 @@ jobs: usr_dir: "local" steps: - - uses: actions/checkout@v1 - name: install rpmbuild run: sudo apt-get install rpm @@ -66,15 +65,7 @@ jobs: repository: nRF24/RF24 ref: rp2xxx - - name: checkout RF24Network - uses: actions/checkout@v2 - with: - repository: nRF24/RF24Network - ref: CMake-4-Linux - - name: build & install RF24 - if: ${{ matrix.toolchain.compiler == 'default' }} - working-directory: ${{ github.workspace }}/RF24 run: | mkdir build cd build @@ -83,9 +74,13 @@ jobs: -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/${{ matrix.toolchain.compiler }}.cmake sudo make install + - name: checkout RF24Network + uses: actions/checkout@v2 + with: + repository: nRF24/RF24Network + ref: CMake-4-Linux + - name: build & install RF24Network - if: ${{ matrix.toolchain.compiler == 'default' }} - working-directory: ${{ github.workspace }}/RF24RF24Network run: | mkdir build cd build @@ -94,13 +89,16 @@ jobs: -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/${{ matrix.toolchain.compiler }}.cmake sudo make install + - name: checkout RF24Mesh repo + uses: actions/checkout@v2 + - name: create CMake build environment run: cmake -E make_directory ${{ github.workspace }}/build - name: configure lib if: ${{ matrix.toolchain.compiler == 'default' }} working-directory: ${{ github.workspace }}/build - run: cmake .. -D CMAKE_BUILD_TYPE=$BUILD_TYPE -D RF24_DRIVER=${{ matrix.driver }} + run: cmake .. -D CMAKE_BUILD_TYPE=$BUILD_TYPE - name: configure lib (with toolchain compilers) if: ${{ matrix.toolchain.compiler != 'default' }} @@ -152,23 +150,3 @@ jobs: run: | cmake --build . file ./RF24Mesh_Example - - # Apparently we need python3 for ARM installed (or at least the Python-C API for arm) - - name: provide python wrapper prerequisites - if: ${{ matrix.toolchain.compiler == 'default' }} - # python3-rpi.gpio is only required for physical hardware - run: sudo apt-get install python3-dev libboost-python-dev python3-setuptools - - - name: create alias symlink to libboost_python3*.so - if: ${{ matrix.toolchain.compiler == 'default' }} - run: sudo ln -s $(ls /usr/lib/$(ls /usr/lib/gcc | tail -1)/libboost_python3*.so | tail -1) /usr/lib/$(ls /usr/lib/gcc | tail -1)/libboost_python3.so - - - name: build python wrapper - if: ${{ matrix.toolchain.compiler == 'default' }} - working-directory: ${{ github.workspace }}/pyRF24Mesh - run: python3 setup.py build - - - name: install python wrapper - if: ${{ matrix.toolchain.compiler == 'default' }} - working-directory: ${{ github.workspace }}/pyRF24Mesh - run: sudo python3 setup.py install \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7e95313..1a88a2c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ # ignore docs folder docs/html/ -docs/xml/ \ No newline at end of file +docs/xml/ + +# ignore build folders +build/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 943b9ee..3a91363 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,12 @@ include(cmake/GetLibInfo.cmake) # sets the variable LibTargetName # setup CPack options include(cmake/CPackInfo.cmake) +find_library(RF24 rf24 REQUIRED) +message(STATUS "using RF24 library: ${RF24}") + +find_library(RF24Network rf24network REQUIRED) +message(STATUS "using RF24Network library: ${RF24Network}") + ########################### # create target for bulding the RF24Log lib ########################### @@ -60,6 +66,8 @@ target_include_directories(${LibTargetName} PUBLIC target_link_libraries(${LibTargetName} INTERFACE project_options project_warnings + SHARED ${RF24} + SHARED ${RF24Network} ) set_target_properties( diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..23cb790 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/cmake/CPackInfo.cmake b/cmake/CPackInfo.cmake index be698eb..f5c9e3a 100644 --- a/cmake/CPackInfo.cmake +++ b/cmake/CPackInfo.cmake @@ -26,7 +26,7 @@ endif() # assemble a debian package filename from known info include(InstallRequiredSystemLibraries) set(CPACK_PACKAGE_FILE_NAME "lib${LibTargetName}_${${LibName}_VERSION_STRING}-${PKG_REV}_${TARGET_ARCH}") -set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_PACKAGE_VERSION_MAJOR "${${LibName}_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${${LibName}_VERSION_MINOR}") set(CPACK_PACKAGE_VERSION_PATCH "${${LibName}_VERSION_PATCH}") diff --git a/cmake/GetLibInfo.cmake b/cmake/GetLibInfo.cmake index 99ee0ba..2613f18 100644 --- a/cmake/GetLibInfo.cmake +++ b/cmake/GetLibInfo.cmake @@ -1,5 +1,5 @@ # get lib info from the maintained library.properties required by the Arduino IDE -file(STRINGS ${CMAKE_SOURCE_DIR}/../library.properties LibInfo) +file(STRINGS ${CMAKE_SOURCE_DIR}/library.properties LibInfo) foreach(line ${LibInfo}) string(FIND ${line} "=" label_delimiter) if(${label_delimiter} GREATER 0) diff --git a/examples_RPi/CMakeLists.txt b/examples_RPi/CMakeLists.txt index dff3704..cab8352 100644 --- a/examples_RPi/CMakeLists.txt +++ b/examples_RPi/CMakeLists.txt @@ -15,10 +15,10 @@ include(../cmake/detectCPU.cmake) # sets the variable SOC accordingly find_library(RF24 rf24 REQUIRED) message(STATUS "using RF24 library: ${RF24}") -find_library(RF24Network rf24 REQUIRED) +find_library(RF24Network rf24network REQUIRED) message(STATUS "using RF24Network library: ${RF24Network}") -find_library(RF24Mesh rf24 REQUIRED) +find_library(RF24Mesh rf24mesh REQUIRED) message(STATUS "using RF24Mesh library: ${RF24Mesh}") foreach(example ${EXAMPLES_LIST}) From 2fa51c544590c826575cfa9f12712c9b006054d4 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 5 Jun 2021 12:16:24 -0700 Subject: [PATCH 03/72] avoid duplicate triggers when PR is open --- .github/workflows/build_linux.yml | 2 +- .github/workflows/doxygen.yml | 1 + .gitignore | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index b84dfdb..931060e 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -2,7 +2,7 @@ name: Linux build on: pull_request: - types: [opened, synchronize, reopened] + types: [opened, reopened] push: release: types: [published, edited] diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index 62fa304..7e3fe55 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -3,6 +3,7 @@ name: DoxyGen build on: pull_request: branches: [master] + types: [opened, reopened] push: branches: [master] release: diff --git a/.gitignore b/.gitignore index 1a88a2c..ef3fc86 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ docs/html/ docs/xml/ # ignore build folders -build/ \ No newline at end of file +build/ + +# ignore local vscode folder +.vscode/ \ No newline at end of file From b10b4b5bf18f2cf984ca8c81ee6db1b80d9095d0 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 6 Jun 2021 20:50:59 -0700 Subject: [PATCH 04/72] add CMake user preset json to ignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ef3fc86..32d7d10 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,9 @@ docs/html/ docs/xml/ -# ignore build folders +# ignore CMake stuff build/ +*CMakeUserPresets.json # ignore local vscode folder .vscode/ \ No newline at end of file From 20bb3206f5ad5f01423147ab25da22aa62dea94f Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 02:40:47 -0700 Subject: [PATCH 05/72] fix code fmt; add ncurses example to cmake targets --- .github/workflows/build_linux.yml | 54 +++++++++--- .github/workflows/doxygen.yml | 29 +++++-- CMakeLists.txt | 16 ++-- cmake/StandardProjectSettings.cmake | 12 ++- cmake/detectCPU.cmake | 87 +++++++++++++------ cmake/toolchains/arm64.cmake | 1 - cmake/toolchains/armhf.cmake | 1 - cmake/toolchains/i686.cmake | 1 - cmake/toolchains/x86_64.cmake | 1 - examples_RPi/CMakeLists.txt | 14 +-- examples_RPi/RF24Mesh_Example.py | 20 ++--- examples_RPi/RF24Mesh_Example_Master.py | 10 +-- examples_RPi/ncurses/CMakeLists.txt | 17 ++++ .../ncurses/RF24Mesh_Ncurses_Master.cpp | 52 +++++------ pyRF24Mesh/pyRF24Mesh.cpp | 4 +- pyRF24Mesh/setup.py | 36 +++++--- 16 files changed, 231 insertions(+), 124 deletions(-) create mode 100644 examples_RPi/ncurses/CMakeLists.txt diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 931060e..34905e9 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -3,7 +3,29 @@ name: Linux build on: pull_request: types: [opened, reopened] + paths: + - '*.h' + - '*.cpp' + - 'CMakeLists.txt' + - 'cmake/**' + - 'library.properties' # CMake gets lib info from here + - 'examples_RPi/**' + - '!examples_RPi/*.py' + - '!**Makefile' # old build system is not tested in this workflow + - 'pyRF24Mesh/*' + - '.github/workflows/build_linux.yml' push: + paths: + - '*.h' + - '*.cpp' + - 'CMakeLists.txt' + - 'cmake/**' + - 'library.properties' # CMake gets lib info from here + - 'examples_RPi/**' + - '!examples_RPi/*.py' + - '!**Makefile' # old build system is not tested in this workflow + - 'pyRF24Mesh/*' + - '.github/workflows/build_linux.yml' release: types: [published, edited] @@ -32,9 +54,6 @@ jobs: usr_dir: "local" steps: - - name: install rpmbuild - run: sudo apt-get install rpm - # - name: provide toolchain (for x86_64) # if: ${{ matrix.toolchain.compiler == 'x86_64' }} # run: | @@ -96,12 +115,6 @@ jobs: run: cmake -E make_directory ${{ github.workspace }}/build - name: configure lib - if: ${{ matrix.toolchain.compiler == 'default' }} - working-directory: ${{ github.workspace }}/build - run: cmake .. -D CMAKE_BUILD_TYPE=$BUILD_TYPE - - - name: configure lib (with toolchain compilers) - if: ${{ matrix.toolchain.compiler != 'default' }} working-directory: ${{ github.workspace }}/build run: | cmake .. -D CMAKE_BUILD_TYPE=$BUILD_TYPE \ @@ -147,6 +160,27 @@ jobs: - name: build examples working-directory: ${{ github.workspace }}/build + # doesn't build the RF24Mesh_Ncurses_Master example because we haven't cross-compiled it in this workflow run: | - cmake --build . + make --target RF24Mesh_Example --target RF24Mesh_Example_Master file ./RF24Mesh_Example + + # cross-compiling a python C extension is better done with pypa/cibuildwheel action + - name: provide python wrapper prerequisites + if: ${{ matrix.toolchain.compiler == 'default' }} + # python3-rpi.gpio is only required for physical hardware (namely the IRQ example) + run: sudo apt-get install python3-dev libboost-python-dev python3-setuptools + + - name: create alias symlink to libboost_python3*.so + if: ${{ matrix.toolchain.compiler == 'default' }} + run: sudo ln -s $(ls /usr/lib/$(ls /usr/lib/gcc | tail -1)/libboost_python3*.so | tail -1) /usr/lib/$(ls /usr/lib/gcc | tail -1)/libboost_python3.so + + - name: build python wrapper + if: ${{ matrix.toolchain.compiler == 'default' }} + working-directory: ${{ github.workspace }}/pyRF24Mesh + run: python3 setup.py build + + - name: install python wrapper + if: ${{ matrix.toolchain.compiler == 'default' }} + working-directory: ${{ github.workspace }}/pyRF24Mesh + run: sudo python3 setup.py install diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index 7e3fe55..9706429 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -4,8 +4,30 @@ on: pull_request: branches: [master] types: [opened, reopened] + paths: + - '*.h' + - 'docs/**' + - '!docs/README.md' + - '*.md' + - 'examples**.cpp' + - 'examples**.ino' + - 'images/**' + - '.github/workflows/doxygen.yml' + - 'Doxyfile' + - 'library.properties' # get lib version from here push: branches: [master] + paths: + - '*.h' + - 'docs/**' + - '!docs/README.md' + - '*.md' + - 'examples**.cpp' + - 'examples**.ino' + - 'images/**' + - '.github/workflows/doxygen.yml' + - 'Doxyfile' + - 'library.properties' # get lib version from here release: branches: [master] types: [published, edited] @@ -15,13 +37,10 @@ jobs: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 - name: get latest release version number id: latest_ver - uses: pozetroninc/github-action-get-latest-release@master - with: - repository: nRF24/RF24Mesh - - name: checkout - uses: actions/checkout@v2 + run: echo "::set-output name=release::$(awk -F "=" '/version/ {print $2}' library.properties)" - name: overwrite doxygen tags run: | touch doxygenAction diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a91363..c36c1bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,39 +56,35 @@ message(STATUS "using RF24Network library: ${RF24Network}") ########################### # create target for bulding the RF24Log lib ########################### -add_library(${LibTargetName} SHARED - RF24Mesh.cpp - ) -target_include_directories(${LibTargetName} PUBLIC - ${CMAKE_CURRENT_LIST_DIR} - ) +add_library(${LibTargetName} SHARED RF24Mesh.cpp) +target_include_directories(${LibTargetName} PUBLIC ${CMAKE_CURRENT_LIST_DIR}) target_link_libraries(${LibTargetName} INTERFACE project_options project_warnings SHARED ${RF24} SHARED ${RF24Network} - ) +) set_target_properties( ${LibTargetName} PROPERTIES SOVERSION ${${LibName}_VERSION_MAJOR} VERSION ${${LibName}_VERSION_STRING} - ) +) ########################### # target install rules for the RF24Log lib ########################### install(TARGETS ${LibTargetName} DESTINATION lib - ) +) install(FILES RF24Mesh.h RF24Mesh_config.h DESTINATION include/RF24Mesh - ) +) # CMAKE_CROSSCOMPILING is only TRUE when CMAKE_TOOLCHAIN_FILE is specified via CLI if(CMAKE_HOST_UNIX AND "${CMAKE_CROSSCOMPILING}" STREQUAL "FALSE") diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake index 0414e99..8524dcb 100644 --- a/cmake/StandardProjectSettings.cmake +++ b/cmake/StandardProjectSettings.cmake @@ -4,7 +4,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE - ) + ) # Set the possible values of build type for cmake-gui, ccmake set_property( CACHE CMAKE_BUILD_TYPE @@ -13,7 +13,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) "Release" "MinSizeRel" "RelWithDebInfo" - ) + ) endif() # Generate compile_commands.json to make it easier to work with clang based tools @@ -24,11 +24,9 @@ option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimizati if(ENABLE_IPO) include(CheckIPOSupported) check_ipo_supported( - RESULT - result - OUTPUT - output - ) + RESULT result + OUTPUT output + ) if(result) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) else() diff --git a/cmake/detectCPU.cmake b/cmake/detectCPU.cmake index ebd7fe7..bd702f9 100644 --- a/cmake/detectCPU.cmake +++ b/cmake/detectCPU.cmake @@ -1,43 +1,78 @@ # try to get the CPU model using a Linux bash command -execute_process(COMMAND cat /proc/cpuinfo - OUTPUT_VARIABLE CPU_MODEL - ) +if(NOT SOC) # if SOC variable not defined by user at CLI + if(EXISTS "/sys/class/sunxi_info/sys_info") + execute_process(COMMAND grep sunxi_platform /sys/class/sunxi_info/sys_info + OUTPUT_VARIABLE CPU_MODEL + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + else() + execute_process(COMMAND grep Hardware /proc/cpuinfo + OUTPUT_VARIABLE CPU_MODEL + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() -# If above command is not executed on an actual SOC board (& compatible OS), then -# there won't be a "Hardware" field to describe the CPU model -string(FIND ${CPU_MODEL} "Hardware" cpu_info_has_hw_field) -if(${cpu_info_has_hw_field} GREATER 0) # Hardware field does exist - string(SUBSTRING ${CPU_MODEL} ${cpu_info_has_hw_field} -1 CPU_MODEL) - string(REGEX MATCH "[ ]+([A-Za-z0-9_])+" SOC ${CPU_MODEL}) - string(STRIP ${SOC} SOC) -else() # Hardware field does not exist - set(SOC "UNKNOWN") # use this string as a sentinel + string(FIND "${CPU_MODEL}" ":" cpu_is_described) + if(${cpu_is_described} GREATER 0) # Hardware field does exist + math(EXPR cpu_is_described "${cpu_is_described} + 1") + string(SUBSTRING "${CPU_MODEL}" ${cpu_is_described} -1 SOC) + string(STRIP "${SOC}" SOC) + else() # Hardware field does not exist + set(SOC "UNKNOWN") # use this string as a sentinel + endif() + message(STATUS "detected SoC: ${SOC}") +else() + message(STATUS "SOC set to ${SOC}") endif() -# detect machine hardware name +string(FIND "${SOC}" "Generic AM33XX" is_AM33XX) + +#[[ detect machine hardware name +This CPU_TYPE variable is not used anywhere. +It remains as useful prompt info & to be consistent with old build system ]] execute_process(COMMAND uname -m - OUTPUT_VARIABLE CPU_TYPE) -string(STRIP "${CPU_TYPE}" CPU_TYPE) + OUTPUT_VARIABLE CPU_TYPE + OUTPUT_STRIP_TRAILING_WHITESPACE +) message(STATUS "detected CPU type: ${CPU_TYPE}") +# identify the compiler base name for customizing flags +# THIS ONLY WORKS/TESTED FOR GNU COMPILERS +if(NOT CMAKE_CROSSCOMPILING) # need to use /usr/lib/gcc soft symlink + # NOTE the following command doesn't work with " | tail -1" appended + execute_process(COMMAND ls /usr/lib/gcc + OUTPUT_VARIABLE tool_name + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # use only last entry if multiple entries are returned + string(FIND "${tool_name}" "\n" last_list_delimiter REVERSE) + if(last_list_delimiter GREATER -1) + math(EXPR last_list_delimiter "${last_list_delimiter} + 1") + string(SUBSTRING "${tool_name}" ${last_list_delimiter} -1 tool_name) + endif() + +else() # we can use the compiler's name of the path set in the toolchain file + string(REGEX REPLACE "^\/usr\/bin\/(.*)-gcc.*" "\\1" tool_name "${CMAKE_C_COMPILER}") +endif() + +message(STATUS "tool name being used is ${tool_name}") + # add compiler flags to optomize builds with arm-linux-gnueabihf-g* compilers -if("${CMAKE_C_COMPILER}" STREQUAL "/usr/bin/arm-linux-gnueabihf-gcc" AND - "${CMAKE_CXX_COMPILER}" STREQUAL "/usr/bin/arm-linux-gnueabihf-g++") - if("${SOC}" STREQUAL "BCM2835") +if("${tool_name}" STREQUAL "arm-linux-gnueabihf") + if("${SOC}" STREQUAL "BCM2835" OR "${SOC}" STREQUAL "BCM2708") add_compile_options(-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard) - elseif("$SOC" STREQUAL "BCM2836") + elseif("$SOC" STREQUAL "BCM2836" OR "${SOC}" STREQUAL "BCM2709") add_compile_options(-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard) - elseif("$SOC" STREQUAL "AM33XX") + elseif(${is_AM33XX} GREATER -1) add_compile_options(-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard) - elseif("$SOC" STREQUAL "A10") + elseif("$SOC" STREQUAL "sun4i" OR "${SOC}" STREQUAL "Sun4iw1p1") # A10 add_compile_options(-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard) - elseif("$SOC" STREQUAL "A13") + elseif("$SOC" STREQUAL "sun5i" OR "${SOC}" STREQUAL "Sun4iw2p1") # A13 add_compile_options(-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard) - elseif("$SOC" STREQUAL "A20") + elseif("$SOC" STREQUAL "sun7i" OR "${SOC}" STREQUAL "Sun8iw2p1") # A20 add_compile_options(-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard) - elseif("$SOC" STREQUAL "H3") + elseif("$SOC" STREQUAL "sun8i" OR "${SOC}" STREQUAL "Sun8iw7p1") # H3 add_compile_options(-march=armv8-a -mtune=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard) endif() endif() - -message(STATUS "detected SoC: ${SOC}") \ No newline at end of file diff --git a/cmake/toolchains/arm64.cmake b/cmake/toolchains/arm64.cmake index add14e1..23e41ca 100644 --- a/cmake/toolchains/arm64.cmake +++ b/cmake/toolchains/arm64.cmake @@ -27,7 +27,6 @@ example using MRAA: (for RF24/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/arm64.cmake ]] list(APPEND CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu) -# message("CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search CMAKE_SYSROOT when find_program() is called set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_library() is called set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_file() is called diff --git a/cmake/toolchains/armhf.cmake b/cmake/toolchains/armhf.cmake index f1611f2..488b81a 100644 --- a/cmake/toolchains/armhf.cmake +++ b/cmake/toolchains/armhf.cmake @@ -27,7 +27,6 @@ example using MRAA: (for RF24/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/arm.cmake ]] list(APPEND CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf) -# message("CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search CMAKE_SYSROOT when find_program() is called set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_library() is called set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_file() is called diff --git a/cmake/toolchains/i686.cmake b/cmake/toolchains/i686.cmake index 344fd8b..f3c42be 100644 --- a/cmake/toolchains/i686.cmake +++ b/cmake/toolchains/i686.cmake @@ -27,7 +27,6 @@ example using MRAA: (for RF24/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/i686.cmake ]] list(APPEND CMAKE_FIND_ROOT_PATH /usr/i686-linux-gnu) -# message("CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search CMAKE_SYSROOT when find_program() is called set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_library() is called set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_file() is called diff --git a/cmake/toolchains/x86_64.cmake b/cmake/toolchains/x86_64.cmake index b75ab3f..e4ff737 100644 --- a/cmake/toolchains/x86_64.cmake +++ b/cmake/toolchains/x86_64.cmake @@ -27,7 +27,6 @@ example using MRAA: (for RF24/build dir) `cmake .. -D CMAKE_TOOLCHAIN_FILE=cmake/toolchains/x86_64.cmake ]] list(APPEND CMAKE_FIND_ROOT_PATH /usr/x86_64-linux-gnux32) -# message("CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search CMAKE_SYSROOT when find_program() is called set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_library() is called set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # search CMAKE_FIND_ROOT_PATH entries when find_file() is called diff --git a/examples_RPi/CMakeLists.txt b/examples_RPi/CMakeLists.txt index cab8352..b775fc9 100644 --- a/examples_RPi/CMakeLists.txt +++ b/examples_RPi/CMakeLists.txt @@ -1,11 +1,5 @@ cmake_minimum_required(VERSION 3.12) -# iterate over a list of examples by filename -set(EXAMPLES_LIST - RF24Mesh_Example - RF24Mesh_Example_Master - ) - project(RF24LogExamples) add_compile_options(-Ofast -Wall) # passing the compiler a `-pthread` flag doesn't work here @@ -21,6 +15,12 @@ message(STATUS "using RF24Network library: ${RF24Network}") find_library(RF24Mesh rf24mesh REQUIRED) message(STATUS "using RF24Mesh library: ${RF24Mesh}") +# iterate over a list of examples by filename +set(EXAMPLES_LIST + RF24Mesh_Example + RF24Mesh_Example_Master +) + foreach(example ${EXAMPLES_LIST}) # make a target add_executable(${example} ${example}.cpp) @@ -28,3 +28,5 @@ foreach(example ${EXAMPLES_LIST}) # link the RF24 lib to the target. Notice we specify pthread as a linked lib here target_link_libraries(${example} PUBLIC ${RF24} pthread ${RF24Network} ${RF24Mesh}) endforeach() + +add_subdirectory(ncurses) diff --git a/examples_RPi/RF24Mesh_Example.py b/examples_RPi/RF24Mesh_Example.py index ae41944..43d395e 100755 --- a/examples_RPi/RF24Mesh_Example.py +++ b/examples_RPi/RF24Mesh_Example.py @@ -1,29 +1,28 @@ #!/usr/bin/env python3 -from RF24 import * -from RF24Network import * -from RF24Mesh import * from time import sleep, time from struct import pack +from RF24 import * +from RF24Network import * +from RF24Mesh import * start = time() def millis(): - - return int((time()-start)*1000) % (2 ** 32) + return int((time() - start) * 1000) % (2 ** 32) def delay(ms): - ms = ms % (2**32) - sleep(ms/1000.0) + ms = ms % (2 ** 32) + sleep(ms / 1000.0) # radio setup for RPi B Rev2: CS0=Pin 24 -radio = RF24(22,0); +radio = RF24(22,0) network = RF24Network(radio) mesh = RF24Mesh(radio, network) mesh.setNodeID(4) -print("start nodeID", mesh.getNodeID()); +print("start nodeID", mesh.getNodeID()) mesh.begin() radio.setPALevel(RF24_PA_MAX) # Power Amplifier radio.printDetails() @@ -47,5 +46,4 @@ def delay(ms): print("Send fail, Test OK") else: print("Send OK:", displayTimer) - delay(1); - \ No newline at end of file + delay(1) diff --git a/examples_RPi/RF24Mesh_Example_Master.py b/examples_RPi/RF24Mesh_Example_Master.py index 963269b..300effa 100755 --- a/examples_RPi/RF24Mesh_Example_Master.py +++ b/examples_RPi/RF24Mesh_Example_Master.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 + +from struct import unpack from RF24 import * from RF24Network import * from RF24Mesh import * -from struct import unpack - # radio setup for RPi B Rev2: CS0=Pin 24 -radio = RF24(22,0); +radio = RF24(22, 0) network = RF24Network(radio) mesh = RF24Mesh(radio, network) @@ -24,6 +24,6 @@ while network.available(): header, payload = network.read(10) if chr(header.type) == 'M': - print("Rcv {} from 0{:o}".format(unpack("L",payload)[0], header.from_node)) + print("Rcv {} from 0{:o}".format(unpack("L", payload)[0], header.from_node)) else: - print("Rcv bad type {} from 0{:o}".format(header.type,header.from_node)); \ No newline at end of file + print("Rcv bad type {} from 0{:o}".format(header.type, header.from_node)) \ No newline at end of file diff --git a/examples_RPi/ncurses/CMakeLists.txt b/examples_RPi/ncurses/CMakeLists.txt new file mode 100644 index 0000000..e621453 --- /dev/null +++ b/examples_RPi/ncurses/CMakeLists.txt @@ -0,0 +1,17 @@ +# this example needs the ncurses package installed +find_package(Curses REQUIRED) +include_directories(${CURSES_INCLUDE_DIR}) + +set(example RF24Mesh_Ncurses_Master) + +# make a target +add_executable(${example} ${example}.cpp) + +# link the RF24 lib to the target. Notice we specify pthread as a linked lib here +target_link_libraries(${example} PUBLIC + ${RF24} + pthread + ${RF24Network} + ${RF24Mesh} + ${CURSES_LIBRARIES} +) diff --git a/examples_RPi/ncurses/RF24Mesh_Ncurses_Master.cpp b/examples_RPi/ncurses/RF24Mesh_Ncurses_Master.cpp index 0c6664f..e16d538 100644 --- a/examples_RPi/ncurses/RF24Mesh_Ncurses_Master.cpp +++ b/examples_RPi/ncurses/RF24Mesh_Ncurses_Master.cpp @@ -5,15 +5,15 @@ * assignments, and information regarding incoming data, regardless of the specific * configuration details. * -* Requirements: NCurses +* Requirements: NCurses * Install NCurses: apt-get install libncurses5-dev * Setup: * 1: make * 2: sudo ./RF24Mesh_Ncurses_Master -* +* * NOTE: DEBUG MUST BE DISABLED IN RF24Mesh_config.h * -* Once configured and running, the interface will display the header information, data rate, +* Once configured and running, the interface will display the header information, data rate, * and address assignments for all connected nodes.* * The master node will also continuously ping each of the child nodes, one per second, while indicating * the results. @@ -21,11 +21,11 @@ */ #include -#include "RF24Mesh/RF24Mesh.h" +#include "RF24Mesh/RF24Mesh.h" #include #include -RF24 radio(RPI_V2_GPIO_P1_15, BCM2835_SPI_CS0, BCM2835_SPI_SPEED_8MHZ); +RF24 radio(22, 0); RF24Network network(radio); RF24Mesh mesh(radio,network); @@ -37,7 +37,7 @@ uint8_t nodeCounter; uint16_t failID = 0; int main() -{ +{ printf("Establishing mesh...\n"); mesh.setNodeID(0); @@ -54,8 +54,8 @@ int main() printw("RF24Mesh Master Node Monitoring Interface by TMRh20 - 2014\n"); attroff(COLOR_PAIR(1)); refresh(); /* Print it on to the real screen */ - - uint32_t kbTimer = 0,kbCount = 0, pingTimer=millis(); + + uint32_t kbTimer = 0,kbCount = 0, pingTimer=millis(); //std::map::iterator it = mesh.addrMap.begin(); unsigned long totalPayloads = 0; @@ -69,26 +69,26 @@ while(1) mesh.DHCP(); // Wait until a sensor node is connected if(sizeof(mesh.addrList) < 1){continue; } - + // Check for incoming data from the sensors - while(network.available()){ + while(network.available()){ RF24NetworkHeader header; network.peek(header); - + uint8_t boldID = 0; - + // Print the total number of received payloads mvprintw(9,0," Total: %lu\n",totalPayloads++); kbCount++; - + attron(A_BOLD | COLOR_PAIR(1)); mvprintw(2,0,"[Last Payload Info]\n"); - attroff(A_BOLD | COLOR_PAIR(1)); - + attroff(A_BOLD | COLOR_PAIR(1)); + // Read the network payload network.read(header,0,0); - + // Display the header info mvprintw(3,0," HeaderID: %u \n Type: %d \n From: 0%o \n ",header.id,header.type,header.from_node); @@ -99,11 +99,11 @@ while(1) boldID = mesh.addrList[i].nodeID; } } - printNodes(boldID); + printNodes(boldID); } //refresh(); - + if(millis()-kbTimer > 1000 && kbCount > 0){ kbTimer = millis(); attron(A_BOLD | COLOR_PAIR(1)); @@ -111,9 +111,9 @@ while(1) attroff(A_BOLD | COLOR_PAIR(1)); mvprintw(8,0," Kbps: %.2f",(kbCount * 32 * 8)/1000.00); kbCount = 0; - + } - + // Ping each connected node, one per second if(millis()-pingTimer>1003 && sizeof(mesh.addrList) > 0){ pingTimer=millis(); @@ -121,9 +121,9 @@ while(1) nodeCounter = 0; } pingNode(nodeCounter); - nodeCounter++; + nodeCounter++; } - + /*uint32_t nOK,nFails; network.failures(&nFails,&nOK); attron(A_BOLD | COLOR_PAIR(1)); @@ -131,12 +131,12 @@ while(1) attroff(A_BOLD | COLOR_PAIR(1)); mvprintw(3,25," #OK: %u ",nOK); mvprintw(4,25," #Fail: %u ",nFails);*/ - - + + refresh(); delay(2); }//while 1 - + endwin(); /* End curses mode */ return 0; } @@ -184,7 +184,7 @@ void pingNode(uint8_t listNo){ mvprintw(12,0," ID:%d ",mesh.addrList[listNo].nodeID); mvprintw(13,0," Net:0%o ",mesh.addrList[listNo].address); mvprintw(14,0," Time:%ums ",pingtime); - + if(ok || !headers.to_node){ mvprintw(15,0," OK "); } else{ attron(A_BOLD); mvprintw(15,0," FAIL"); attron(A_BOLD); } } diff --git a/pyRF24Mesh/pyRF24Mesh.cpp b/pyRF24Mesh/pyRF24Mesh.cpp index 26f6afc..468194f 100644 --- a/pyRF24Mesh/pyRF24Mesh.cpp +++ b/pyRF24Mesh/pyRF24Mesh.cpp @@ -25,6 +25,7 @@ char *get_bytes_or_bytearray_str(bp::object buf) return PyBytes_AsString(py_ba); else throw_ba_exception(); + return NULL; } @@ -38,6 +39,7 @@ int get_bytes_or_bytearray_ln(bp::object buf) return PyBytes_Size(py_ba); else throw_ba_exception(); + return 0; } @@ -74,7 +76,7 @@ BOOST_PYTHON_MODULE(RF24Mesh) .def("write", &write_wrap1, (bp::arg("data"), bp::arg("msg_type"))) .def("write", &write_wrap2, (bp::arg("data"), bp::arg("msg_type"), bp::arg("nodeID"))) //bool write(uint16_t to_node, const void* data, uint8_t msg_type, size_t size ); - .def("write", &write_to_node_wrap, (bp::arg("to_node"), bp::arg("data"), bp::arg("msg_type"), bp::arg("size"))) + .def("write", &write_to_node_wrap, (bp::arg("to_node"), bp::arg("data"), bp::arg("msg_type"))) //void setNodeID(uint8_t nodeID); .def("setNodeID", &RF24Mesh::setNodeID, (bp::arg("nodeID"))) //void DHCP(); diff --git a/pyRF24Mesh/setup.py b/pyRF24Mesh/setup.py index 8d5eb72..de47185 100755 --- a/pyRF24Mesh/setup.py +++ b/pyRF24Mesh/setup.py @@ -1,17 +1,27 @@ #!/usr/bin/env python -from distutils.core import setup, Extension -import sys +from setuptools import setup, Extension +from sys import version_info -if sys.version_info >= (3,): - BOOST_LIB = 'boost_python3' -else: - BOOST_LIB = 'boost_python' +if version_info >= (3,): + BOOST_LIB = "boost_python3" +else: + BOOST_LIB = "boost_python" -module_RF24Mesh = Extension('RF24Mesh', - libraries = ['rf24mesh', 'rf24network', BOOST_LIB], - sources = ['pyRF24Mesh.cpp']) - -setup(name='RF24Mesh', - version='1.0', - ext_modules=[module_RF24Mesh]) +setup( + name="RF24Mesh", + version="1.0", + classifiers = [ + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + "Programming Language :: C++", + "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + ], + ext_modules = [ + Extension( + "RF24Mesh", + libraries=["rf24mesh", "rf24network", BOOST_LIB], + sources=["pyRF24Mesh.cpp"], + ) + ] +) From 4ee2e51d474020d032cb3834690949113524128a Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 02:43:51 -0700 Subject: [PATCH 06/72] no need for --target with make cmd --- .github/workflows/build_linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 34905e9..f43dc28 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -162,7 +162,7 @@ jobs: working-directory: ${{ github.workspace }}/build # doesn't build the RF24Mesh_Ncurses_Master example because we haven't cross-compiled it in this workflow run: | - make --target RF24Mesh_Example --target RF24Mesh_Example_Master + make RF24Mesh_Example RF24Mesh_Example_Master file ./RF24Mesh_Example # cross-compiling a python C extension is better done with pypa/cibuildwheel action From d1c321755a09f2df525303e1387a351dd14d312e Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 03:05:56 -0700 Subject: [PATCH 07/72] automate building of ncurses example --- .github/workflows/build_linux.yml | 2 +- cmake/enableNcursesExample.cmake | 15 +++++++++++++++ examples_RPi/CMakeLists.txt | 5 ++++- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 cmake/enableNcursesExample.cmake diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index f43dc28..20e7606 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -162,7 +162,7 @@ jobs: working-directory: ${{ github.workspace }}/build # doesn't build the RF24Mesh_Ncurses_Master example because we haven't cross-compiled it in this workflow run: | - make RF24Mesh_Example RF24Mesh_Example_Master + cmake --build . file ./RF24Mesh_Example # cross-compiling a python C extension is better done with pypa/cibuildwheel action diff --git a/cmake/enableNcursesExample.cmake b/cmake/enableNcursesExample.cmake new file mode 100644 index 0000000..b016787 --- /dev/null +++ b/cmake/enableNcursesExample.cmake @@ -0,0 +1,15 @@ +find_package(Curses) +if(Curses_FOUND) + include_directories(${CURSES_INCLUDE_DIR}) + option(BUILD_NCURSES_EXAMPLE + "Enable/Disable building the ncurses example (requires libncurses5-dev installed)" + ON + ) +else() + message(STATUS "libncurses5-dev not found. Skipping ncurses example") + option(BUILD_NCURSES_EXAMPLE + "Enable/Disable building the ncurses example (requires libncurses5-dev installed)" + OFF + ) +endif() +message(STATUS "BUILD_NCURSES_EXAMPLE set to ${BUILD_NCURSES_EXAMPLE}") diff --git a/examples_RPi/CMakeLists.txt b/examples_RPi/CMakeLists.txt index b775fc9..a31e5e2 100644 --- a/examples_RPi/CMakeLists.txt +++ b/examples_RPi/CMakeLists.txt @@ -29,4 +29,7 @@ foreach(example ${EXAMPLES_LIST}) target_link_libraries(${example} PUBLIC ${RF24} pthread ${RF24Network} ${RF24Mesh}) endforeach() -add_subdirectory(ncurses) +include(../cmake/enableNcursesExample.cmake) +if(BUILD_NCURSES_EXAMPLE) + add_subdirectory(ncurses) +endif() From a1ecabc25bd809a8fc11635506331818190e6c5b Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 04:26:42 -0700 Subject: [PATCH 08/72] default BUILD_TYPE to Release --- cmake/StandardProjectSettings.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake index 8524dcb..75a379f 100644 --- a/cmake/StandardProjectSettings.cmake +++ b/cmake/StandardProjectSettings.cmake @@ -1,8 +1,8 @@ # Set a default build type if none was specified if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") + message(STATUS "Setting build type to 'Release' as none was specified.") set(CMAKE_BUILD_TYPE - RelWithDebInfo + Release CACHE STRING "Choose the type of build." FORCE ) # Set the possible values of build type for cmake-gui, ccmake From 14520623c2254dc461b50019b5326a0888391626 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 14:48:50 -0700 Subject: [PATCH 09/72] Update CONTRIBUTING.md --- CONTRIBUTING.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e3c5cc..9d99c0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,13 @@ These are the current requirements for getting your code included in RF24Mesh: * Try your best to follow the rest of the code, if you're unsure then the NASA C style can help as it's closest to the current style: https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19950022400.pdf -* Definetly follow PEP-8 if it's Python code. +* Definetly follow [PEP-8](https://www.python.org/dev/peps/pep-0008/) if it's Python code. -* Follow the Arduino IDE formatting style for Arduino examples +* Follow the [Arduino IDE formatting style](https://www.arduino.cc/en/Reference/StyleGuide) for Arduino examples -* Add doxygen-compatible documentation to any new functions you add, or update existing documentation if you change behaviour +* Add [doxygen-compatible documentation](https://www.doxygen.nl/manual/docblocks.html) to any new functions you add, or update existing documentation if you change behaviour + +* CMake modules and CMakeLists.txt files should also have a uniform syntax. + - Indentation is a mandatory 4 spaces (not a `\t` character). + - Closing parenthesis for multi-line commands should have the same indentation as the line that opened the parenthesis. + - For other useful CMake syntax convention, please see [CMake docs for developers](https://cmake.org/cmake/help/v3.20/manual/cmake-developer.7.html) and [this useful best CMake practices article](https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1). The qiBuild project has some [well-reasoned "Dos & Don'ts" guideline](http://doc.aldebaran.com/qibuild/hacking/contrib/cmake/coding_guide.html), but beware that the nRF24 organization is not related to the qiBuild project in any way. From 903c6d798c81985785f8016d4d34e01d44d9f94d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 15:11:00 -0700 Subject: [PATCH 10/72] manually trigger CI workflows --- .github/workflows/build_linux.yml | 40 +++++++++++++++---------------- .github/workflows/doxygen.yml | 40 +++++++++++++++---------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 20e7606..b74c532 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -4,28 +4,28 @@ on: pull_request: types: [opened, reopened] paths: - - '*.h' - - '*.cpp' - - 'CMakeLists.txt' - - 'cmake/**' - - 'library.properties' # CMake gets lib info from here - - 'examples_RPi/**' - - '!examples_RPi/*.py' - - '!**Makefile' # old build system is not tested in this workflow - - 'pyRF24Mesh/*' - - '.github/workflows/build_linux.yml' + - "*.h" + - "*.cpp" + - "CMakeLists.txt" + - "cmake/**" + - "library.properties" # CMake gets lib info from here + - "examples_RPi/**" + - "!examples_RPi/*.py" + - "!**Makefile" # old build system is not tested in this workflow + - "pyRF24Mesh/*" + - ".github/workflows/build_linux.yml" push: paths: - - '*.h' - - '*.cpp' - - 'CMakeLists.txt' - - 'cmake/**' - - 'library.properties' # CMake gets lib info from here - - 'examples_RPi/**' - - '!examples_RPi/*.py' - - '!**Makefile' # old build system is not tested in this workflow - - 'pyRF24Mesh/*' - - '.github/workflows/build_linux.yml' + - "*.h" + - "*.cpp" + - "CMakeLists.txt" + - "cmake/**" + - "library.properties" # CMake gets lib info from here + - "examples_RPi/**" + - "!examples_RPi/*.py" + - "!**Makefile" # old build system is not tested in this workflow + - "pyRF24Mesh/*" + - ".github/workflows/build_linux.yml" release: types: [published, edited] diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index 9706429..cd6196f 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -5,29 +5,29 @@ on: branches: [master] types: [opened, reopened] paths: - - '*.h' - - 'docs/**' - - '!docs/README.md' - - '*.md' - - 'examples**.cpp' - - 'examples**.ino' - - 'images/**' - - '.github/workflows/doxygen.yml' - - 'Doxyfile' - - 'library.properties' # get lib version from here + - "*.h" + - "docs/**" + - "!docs/README.md" + - "*.md" + - "examples**.cpp" + - "examples**.ino" + - "images/**" + - ".github/workflows/doxygen.yml" + - "Doxyfile" + - "library.properties" # get lib version from here push: branches: [master] paths: - - '*.h' - - 'docs/**' - - '!docs/README.md' - - '*.md' - - 'examples**.cpp' - - 'examples**.ino' - - 'images/**' - - '.github/workflows/doxygen.yml' - - 'Doxyfile' - - 'library.properties' # get lib version from here + - "*.h" + - "docs/**" + - "!docs/README.md" + - "*.md" + - "examples**.cpp" + - "examples**.ino" + - "images/**" + - ".github/workflows/doxygen.yml" + - "Doxyfile" + - "library.properties" # get lib version from here release: branches: [master] types: [published, edited] From ff92d8e8c8105c39f0177cfadb4b843bf8a8c636 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 15:12:31 -0700 Subject: [PATCH 11/72] doxygen CI triggers on push & PR for all branches --- .github/workflows/doxygen.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index cd6196f..cef8692 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -2,7 +2,6 @@ name: DoxyGen build on: pull_request: - branches: [master] types: [opened, reopened] paths: - "*.h" @@ -16,7 +15,6 @@ on: - "Doxyfile" - "library.properties" # get lib version from here push: - branches: [master] paths: - "*.h" - "docs/**" From 0902b4932ded951858a116963139eb9022659b10 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 16:03:17 -0700 Subject: [PATCH 12/72] try new Arduino CI workflow --- .github/workflows/build_arduino.yml | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 .github/workflows/build_arduino.yml diff --git a/.github/workflows/build_arduino.yml b/.github/workflows/build_arduino.yml new file mode 100644 index 0000000..e6df6f5 --- /dev/null +++ b/.github/workflows/build_arduino.yml @@ -0,0 +1,94 @@ +name: Arduino CLI build + +on: + pull_request: + types: [opened, reopened] + paths: + - ".github/workflows/build_arduino.yml" + - "examples/**" + + push: + paths: + - ".github/workflows/build_arduino.yml" + - "examples/**" + +jobs: + check_formatting: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Check code formatting + uses: per1234/artistic-style-action@main + with: + options-file-path: ./examples/examples_formatter.conf + name-patterns: | + - '*.ino' + - '*.cpp' + - '*.hpp' + - '*.h' + target-paths: | + - examples + build: + needs: check_formatting + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + fqbn: + - "arduino:avr:yun" + - "arduino:avr:uno" + - "arduino:avr:diecimila" + - "arduino:avr:nano" + - "arduino:avr:mega" + - "arduino:avr:megaADK" + - "arduino:avr:leonardo" + - "arduino:avr:micro" + - "arduino:avr:esplora" + - "arduino:avr:mini" + - "arduino:avr:ethernet" + - "arduino:avr:fio" + - "arduino:avr:bt" + # - "arduino:avr:LilyPad" # board not found + - "arduino:avr:LilyPadUSB" + - "arduino:avr:pro" + - "arduino:avr:atmegang" + - "arduino:avr:robotControl" + # - "arduino:avr:gemma" # does not support SPI + - "arduino:avr:circuitplay32u4cat" + - "arduino:avr:yunmini" + - "arduino:avr:chiwawa" + - "arduino:avr:one" + - "arduino:avr:unowifi" + - "arduino:mbed:nano33ble" + - "arduino:samd:mkr1000" + - "arduino:samd:mkrzero" + - "arduino:samd:mkrwifi1010" + - "arduino:samd:nano_33_iot" + - "arduino:samd:mkrfox1200" + - "arduino:samd:mkrwan1300" + - "arduino:samd:mkrwan1310" + - "arduino:samd:mkrgsm1400" + - "arduino:samd:mkrnb1500" + - "arduino:samd:mkrvidor4000" + - "arduino:samd:adafruit_circuitplayground_m0" + - "arduino:samd:mzero_pro_bl" + - "arduino:samd:mzero_bl" + - "arduino:samd:tian" + - "arduino:megaavr:uno2018" + # - "arduino:megaavr:nano4809" # board not found + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Compile examples + uses: arduino/compile-sketches@main + with: + libraries: | + - name: RF24 + - name: RF24Network + - source-path: ./ + fqbn: ${{ matrix.fqbn }} From 4471b0575ee5682628e2ee095cd8e40eb86ee80b Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 16:11:12 -0700 Subject: [PATCH 13/72] forgot to add arduino examples fmt conf file --- .gitignore | 1 + .../RF24Mesh_Example/RF24Mesh_Example.ino | 2 +- .../RF24Mesh_Example_Master.ino | 70 ++++++++--------- .../RF24Mesh_Example_Node2Node.ino | 78 +++++++++---------- examples/examples_formatter.conf | 31 ++++++++ 5 files changed, 107 insertions(+), 75 deletions(-) create mode 100644 examples/examples_formatter.conf diff --git a/.gitignore b/.gitignore index 32d7d10..83ce07d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Executables from examples */bin/* +*.exe # Generated library files *.so diff --git a/examples/RF24Mesh_Example/RF24Mesh_Example.ino b/examples/RF24Mesh_Example/RF24Mesh_Example.ino index e5bf6d5..c7f2d4b 100644 --- a/examples/RF24Mesh_Example/RF24Mesh_Example.ino +++ b/examples/RF24Mesh_Example/RF24Mesh_Example.ino @@ -66,7 +66,7 @@ void loop() { if ( ! mesh.checkConnection() ) { //refresh the network address Serial.println("Renewing Address"); - if(!mesh.renewAddress()){ + if (!mesh.renewAddress()) { //If address renewal fails, reconfigure the radio and restart the mesh //This allows recovery from most if not all radio errors mesh.begin(); diff --git a/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino b/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino index 2d21735..fcfa829 100644 --- a/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino +++ b/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino @@ -1,18 +1,18 @@ - - - /** RF24Mesh_Example_Master.ino by TMRh20 - * - * - * This example sketch shows how to manually configure a node via RF24Mesh as a master node, which - * will receive all data from sensor nodes. - * - * The nodes can change physical or logical position in the network, and reconnect through different - * routing nodes as required. The master node manages the address assignments for the individual nodes - * in a manner similar to DHCP. - * - */ - - + + +/** RF24Mesh_Example_Master.ino by TMRh20 + * + * + * This example sketch shows how to manually configure a node via RF24Mesh as a master node, which + * will receive all data from sensor nodes. + * + * The nodes can change physical or logical position in the network, and reconnect through different + * routing nodes as required. The master node manages the address assignments for the individual nodes + * in a manner similar to DHCP. + * + */ + + #include "RF24Network.h" #include "RF24.h" #include "RF24Mesh.h" @@ -21,9 +21,9 @@ #include /***** Configure the chosen CE,CS pins *****/ -RF24 radio(7,8); +RF24 radio(7, 8); RF24Network network(radio); -RF24Mesh mesh(radio,network); +RF24Mesh mesh(radio, network); uint32_t displayTimer = 0; @@ -39,39 +39,39 @@ void setup() { } -void loop() { +void loop() { // Call mesh.update to keep the network updated mesh.update(); - + // In addition, keep the 'DHCP service' running on the master node so addresses will // be assigned to the sensor nodes mesh.DHCP(); - - + + // Check for incoming data from the sensors - if(network.available()){ + if (network.available()) { RF24NetworkHeader header; network.peek(header); - - uint32_t dat=0; - switch(header.type){ + + uint32_t dat = 0; + switch (header.type) { // Display the incoming millis() values from the sensor nodes - case 'M': network.read(header,&dat,sizeof(dat)); Serial.println(dat); break; - default: network.read(header,0,0); Serial.println(header.type);break; + case 'M': network.read(header, &dat, sizeof(dat)); Serial.println(dat); break; + default: network.read(header, 0, 0); Serial.println(header.type); break; } } - - if(millis() - displayTimer > 5000){ + + if (millis() - displayTimer > 5000) { displayTimer = millis(); Serial.println(" "); Serial.println(F("********Assigned Addresses********")); - for(int i=0; i 0){ - Serial.println(_ID); - }else{ - Serial.println("Mesh ID Lookup Failed"); - } + if (network.available()) { + RF24NetworkHeader header; + uint32_t mills; + network.read(header, &mills, sizeof(mills)); + Serial.print("Rcv "); Serial.print(mills); + Serial.print(" from nodeID "); + int _ID = mesh.getNodeID(header.from_node); + if ( _ID > 0) { + Serial.println(_ID); + } else { + Serial.println("Mesh ID Lookup Failed"); + } } - - + + // Send to the other node every second - if(millis() - millisTimer >= 1000){ + if (millis() - millisTimer >= 1000) { millisTimer = millis(); - + // Send an 'M' type to other Node containing the current millis() - if(!mesh.write(&millisTimer,'M',sizeof(millisTimer),otherNodeID)){ - if( ! mesh.checkConnection() ){ - Serial.println("Renewing Address"); - mesh.renewAddress(); - }else{ - Serial.println("Send fail, Test OK"); - } + if (!mesh.write(&millisTimer, 'M', sizeof(millisTimer), otherNodeID)) { + if ( ! mesh.checkConnection() ) { + Serial.println("Renewing Address"); + mesh.renewAddress(); + } else { + Serial.println("Send fail, Test OK"); + } } } - + } diff --git a/examples/examples_formatter.conf b/examples/examples_formatter.conf new file mode 100644 index 0000000..79fd9a6 --- /dev/null +++ b/examples/examples_formatter.conf @@ -0,0 +1,31 @@ +# This configuration file contains a selection of the available options provided by the formatting tool "Artistic Style" +# http://astyle.sourceforge.net/astyle.html +# +# If you wish to change them, don't edit this file. +# Instead, copy it in the same folder of file "preferences.txt" and modify the copy. This way, you won't lose your custom formatter settings when upgrading the IDE +# If you don't know where file preferences.txt is stored, open the IDE, File -> Preferences and you'll find a link + +mode=c + +# 2 spaces indentation +indent=spaces=2 + +# also indent macros +indent-preprocessor + +# indent classes, switches (and cases), comments starting at column 1 +indent-classes +indent-switches +indent-cases +indent-col1-comments + +# put a space around operators +pad-oper + +# put a space after if/for/while +pad-header + +# if you like one-liners, keep them +keep-one-line-statements + +# remove-comment-prefix From f0bc5e1fe5a89fd9c3f495bf61a8449f7614599e Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 16:25:37 -0700 Subject: [PATCH 14/72] RF24Mesh_Example_Master is incompatible w/ non-AVR --- .github/workflows/build_arduino.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/build_arduino.yml b/.github/workflows/build_arduino.yml index e6df6f5..b89c712 100644 --- a/.github/workflows/build_arduino.yml +++ b/.github/workflows/build_arduino.yml @@ -87,6 +87,15 @@ jobs: - name: Compile examples uses: arduino/compile-sketches@main with: + sketch-paths: | + - examples/RF24Mesh_Example + - examples/RF24Mesh_Example_Master_Statics + - examples/RF24Mesh_Example_Master_To_Nodes + - examples/RF24Mesh_Example_Node2Node + - examples/RF24Mesh_Example_Node2NodeExtra + - examples/RF24Mesh_SerialConfig + # The following is incompatible with non-AVR boards due to use of EEPROM.h + # - examples/RF24Mesh_Example_Master libraries: | - name: RF24 - name: RF24Network From f2e316777a3e7ef6d6ded7ce37bb13b0253c9ac3 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 16:30:56 -0700 Subject: [PATCH 15/72] gimme them badges --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d5004fe..f648120 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Linux build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_linux.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_linux.yml) +[![Arduino CLI build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml) + RF24Mesh ======== Mesh Networking for RF24Network From 69b154803507f7e41b6d363cd42ba4fd334fc120 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 22:46:39 -0700 Subject: [PATCH 16/72] try new PlatformIO CI --- .github/workflows/build_platformIO.yml | 93 ++++++++++++++++++++++++++ library.json | 15 +++-- 2 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/build_platformIO.yml diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml new file mode 100644 index 0000000..f5c1fe7 --- /dev/null +++ b/.github/workflows/build_platformIO.yml @@ -0,0 +1,93 @@ +name: PlatformIO build + +on: + pull_request: + types: [opened, reopened] + paths: + - ".github/workflows/build_platformIO.yml" + - "library.json" + - "examples/**" + - "!examples/old_backups/**" + - "!examples/rf24_ATTiny/**" + push: + paths: + - ".github/workflows/build_platformIO.yml" + - "library.json" + - "examples/**" + - "!examples/old_backups/**" + - "!examples/rf24_ATTiny/**" + +jobs: + check_formatting: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Check code formatting + uses: per1234/artistic-style-action@main + with: + options-file-path: ./examples/examples_formatter.conf + name-patterns: | + - '*.ino' + - '*.cpp' + - '*.hpp' + - '*.h' + target-paths: | + - examples + + build: + needs: check_formatting + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + example: + - "examples\RF24Mesh_Example\RF24Mesh_Example.ino" + - "examples\RF24Mesh_Example_Master_Statics\RF24Mesh_Example_Master_Statics.ino" + - "examples\RF24Mesh_Example_Master_To_Nodes\RF24Mesh_Example_Master_To_Nodes.ino" + - "examples\RF24Mesh_Example_Node2Node\RF24Mesh_Example_Node2Node.ino" + - "examples\RF24Mesh_Example_Node2NodeExtra\RF24Mesh_Example_Node2NodeExtra.ino" + - "examples\RF24Mesh_SerialConfig\RF24Mesh_SerialConfig.ino" + # ignore this last one because it only works on AVR chips (uses EEPROM.h) + # - "examples\RF24Mesh_Example_Master\RF24Mesh_Example_Master.ino" + board: + - "teensy31" + - "teensy35" + - "teensy36" + - "teensy40" + - "teensy41" + - "teensylc" + + steps: + - uses: actions/checkout@v2 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Cache PlatformIO + uses: actions/cache@v2 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + # "dependencies" field in JSON should automatically install RF24 & RF24Network. + # Because we run this CI test against the local repo, the JSON seems neglected + - name: Install library dependencies + run: | + pio lib -g install nrf24/RF24 + pio lib -g install nrf24/RF24Network + - name: Run PlatformIO + run: pio ci --lib="." --board=${{ matrix.board }} + env: + PLATFORMIO_CI_SRC: ${{ matrix.example }} diff --git a/library.json b/library.json index 7d0ee9e..2b0038e 100644 --- a/library.json +++ b/library.json @@ -5,18 +5,23 @@ "repository": { "type": "git", - "url": "https://github.com/TMRh20/RF24Mesh.git" + "url": "https://github.com/nRF24/RF24Mesh.git" }, "version": "1.1.6", "dependencies": [ - { "name": "RF24", "authors": "TMRh20", "frameworks": "arduino" }, - { "name": "RF24Network", "authors": "TMRh20", "frameworks": "arduino" } + { "name": "RF24", "authors": "nRF24", "frameworks": "arduino" }, + { "name": "RF24Network", "authors": "nRF24", "frameworks": "arduino" } ], - "exclude": "examples_RPi", + "export": { "exclude": ["examples_RPi/*"] }, "frameworks": "arduino", "platforms": [ "atmelavr", "atmelsam", - "teensy" + "teensy", + "atmelmegaavr", + "espressif32", + "espressif8266", + "linux_arm", + "ststm32" ] } From e16f6005c14bf7e9c6c009fdbc6e51cb1a717ef1 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 22:48:30 -0700 Subject: [PATCH 17/72] oops use linux FS delimiter --- .github/workflows/build_platformIO.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml index f5c1fe7..ee051fe 100644 --- a/.github/workflows/build_platformIO.yml +++ b/.github/workflows/build_platformIO.yml @@ -45,14 +45,14 @@ jobs: matrix: example: - - "examples\RF24Mesh_Example\RF24Mesh_Example.ino" - - "examples\RF24Mesh_Example_Master_Statics\RF24Mesh_Example_Master_Statics.ino" - - "examples\RF24Mesh_Example_Master_To_Nodes\RF24Mesh_Example_Master_To_Nodes.ino" - - "examples\RF24Mesh_Example_Node2Node\RF24Mesh_Example_Node2Node.ino" - - "examples\RF24Mesh_Example_Node2NodeExtra\RF24Mesh_Example_Node2NodeExtra.ino" - - "examples\RF24Mesh_SerialConfig\RF24Mesh_SerialConfig.ino" + - "examples/RF24Mesh_Example/RF24Mesh_Example.ino" + - "examples/RF24Mesh_Example_Master_Statics/RF24Mesh_Example_Master_Statics.ino" + - "examples/RF24Mesh_Example_Master_To_Nodes/RF24Mesh_Example_Master_To_Nodes.ino" + - "examples/RF24Mesh_Example_Node2Node/RF24Mesh_Example_Node2Node.ino" + - "examples/RF24Mesh_Example_Node2NodeExtra/RF24Mesh_Example_Node2NodeExtra.ino" + - "examples/RF24Mesh_SerialConfig/RF24Mesh_SerialConfig.ino" # ignore this last one because it only works on AVR chips (uses EEPROM.h) - # - "examples\RF24Mesh_Example_Master\RF24Mesh_Example_Master.ino" + # - "examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino" board: - "teensy31" - "teensy35" From a9f83131c7a7acb93cac16e9b9a2b8bf74df3724 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 22:51:23 -0700 Subject: [PATCH 18/72] gimme a badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f648120..a2e8261 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Linux build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_linux.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_linux.yml) [![Arduino CLI build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml) +[![PlatformIO build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_platformIO.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_platformIO.yml) RF24Mesh ======== From 0a988380f597dcb580d446e41fcb21ddf424381b Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 23:09:39 -0700 Subject: [PATCH 19/72] polish library.json --- library.json | 1 + 1 file changed, 1 insertion(+) diff --git a/library.json b/library.json index 2b0038e..8c90f6a 100644 --- a/library.json +++ b/library.json @@ -2,6 +2,7 @@ "name": "RF24Mesh", "keywords": "rf, radio, wireless, spi, mesh, network", "description": "A simple and seamless 'mesh' layer for sensor networks. Designed to interface directly with with the RF24Network Development library, an OSI Network Layer using nRF24L01(+) radios driven by the newly optimized RF24 library fork.", + "license": "GPL-2.0-only", "repository": { "type": "git", From 85ad20c769d5f7f2d9e0cb86ca55c59d9faf5bbc Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 12 Jun 2021 23:58:07 -0700 Subject: [PATCH 20/72] [PIO CI] validate library.json & save as artifact --- .github/workflows/build_platformIO.yml | 90 +++++++++++++++++--------- .gitignore | 5 +- library.json | 60 +++++++++-------- 3 files changed, 98 insertions(+), 57 deletions(-) diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml index ee051fe..d8ded56 100644 --- a/.github/workflows/build_platformIO.yml +++ b/.github/workflows/build_platformIO.yml @@ -16,8 +16,38 @@ on: - "examples/**" - "!examples/old_backups/**" - "!examples/rf24_ATTiny/**" + release: + types: [published, edited] jobs: + validate_lib_json: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: get latest release version number + id: latest_ver + run: echo "::set-output name=release::$(awk -F "=" '/version/ {print $2}' library.properties)" + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + - name: package lib + run: pio package pack -o PlatformIO-RF24Mesh-${{ steps.latest_ver.outputs.release }}.tar.gz + - name: Save artifact + uses: actions/upload-artifact@v2 + with: + name: "PIO_pkg_RF24Mesh" + path: | + ${{ github.workspace }}PlatformIO*.tar.gz + - name: Upload Release assets + if: github.event_name == 'release' + uses: csexton/release-asset-action@master + with: + pattern: "${{ github.workspace }}/build/pkgs/librf24*" + github-token: ${{ secrets.GITHUB_TOKEN }} + check_formatting: runs-on: ubuntu-latest @@ -37,7 +67,7 @@ jobs: - examples build: - needs: check_formatting + needs: [check_formatting, validate_lib_json] runs-on: ubuntu-latest strategy: @@ -62,32 +92,32 @@ jobs: - "teensylc" steps: - - uses: actions/checkout@v2 - - name: Cache pip - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Cache PlatformIO - uses: actions/cache@v2 - with: - path: ~/.platformio - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - - name: Set up Python - uses: actions/setup-python@v2 - - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - # "dependencies" field in JSON should automatically install RF24 & RF24Network. - # Because we run this CI test against the local repo, the JSON seems neglected - - name: Install library dependencies - run: | - pio lib -g install nrf24/RF24 - pio lib -g install nrf24/RF24Network - - name: Run PlatformIO - run: pio ci --lib="." --board=${{ matrix.board }} - env: - PLATFORMIO_CI_SRC: ${{ matrix.example }} + - uses: actions/checkout@v2 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Cache PlatformIO + uses: actions/cache@v2 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + # "dependencies" field in JSON should automatically install RF24 & RF24Network. + # Because we run this CI test against the local repo, the JSON seems neglected + - name: Install library dependencies + run: | + pio lib -g install nrf24/RF24 + pio lib -g install nrf24/RF24Network + - name: Run PlatformIO + run: pio ci --lib="." --board=${{ matrix.board }} + env: + PLATFORMIO_CI_SRC: ${{ matrix.example }} diff --git a/.gitignore b/.gitignore index 83ce07d..e2e32ed 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,7 @@ build/ *CMakeUserPresets.json # ignore local vscode folder -.vscode/ \ No newline at end of file +.vscode/ + +# ignore PlatformIO packages +*.tar.gz diff --git a/library.json b/library.json index 8c90f6a..5f30fb0 100644 --- a/library.json +++ b/library.json @@ -1,28 +1,36 @@ { - "name": "RF24Mesh", - "keywords": "rf, radio, wireless, spi, mesh, network", - "description": "A simple and seamless 'mesh' layer for sensor networks. Designed to interface directly with with the RF24Network Development library, an OSI Network Layer using nRF24L01(+) radios driven by the newly optimized RF24 library fork.", - "license": "GPL-2.0-only", - "repository": - { - "type": "git", - "url": "https://github.com/nRF24/RF24Mesh.git" - }, - "version": "1.1.6", - "dependencies": [ - { "name": "RF24", "authors": "nRF24", "frameworks": "arduino" }, - { "name": "RF24Network", "authors": "nRF24", "frameworks": "arduino" } - ], - "export": { "exclude": ["examples_RPi/*"] }, - "frameworks": "arduino", - "platforms": [ - "atmelavr", - "atmelsam", - "teensy", - "atmelmegaavr", - "espressif32", - "espressif8266", - "linux_arm", - "ststm32" - ] + "name": "RF24Mesh", + "keywords": "rf, radio, wireless, spi, mesh, network", + "description": "A simple and seamless 'mesh' layer for sensor networks. Designed to interface directly with with the RF24Network Development library, an OSI Network Layer using nRF24L01(+) radios driven by the newly optimized RF24 library fork.", + "license": "GPL-2.0-only", + "repository": { + "type": "git", + "url": "https://github.com/nRF24/RF24Mesh.git" + }, + "version": "1.1.6", + "dependencies": [{ + "name": "RF24", + "authors": "nRF24", + "frameworks": "arduino" + }, + { + "name": "RF24Network", + "authors": "nRF24", + "frameworks": "arduino" + } + ], + "export": { + "exclude": ["examples_RPi/*", "build/*"] + }, + "frameworks": "arduino", + "platforms": [ + "atmelavr", + "atmelsam", + "teensy", + "atmelmegaavr", + "espressif32", + "espressif8266", + "linux_arm", + "ststm32" + ] } From 29f7294b7e26198e18a5490036206e72507f5deb Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 13 Jun 2021 00:02:24 -0700 Subject: [PATCH 21/72] [PIO CI] fix path to pio pkg --- .github/workflows/build_platformIO.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml index d8ded56..4d70109 100644 --- a/.github/workflows/build_platformIO.yml +++ b/.github/workflows/build_platformIO.yml @@ -39,13 +39,12 @@ jobs: uses: actions/upload-artifact@v2 with: name: "PIO_pkg_RF24Mesh" - path: | - ${{ github.workspace }}PlatformIO*.tar.gz + path: PlatformIO*.tar.gz - name: Upload Release assets if: github.event_name == 'release' uses: csexton/release-asset-action@master with: - pattern: "${{ github.workspace }}/build/pkgs/librf24*" + pattern: "PlatformIO*.tar.gz" github-token: ${{ secrets.GITHUB_TOKEN }} check_formatting: From 31d7ecebba93332321589f88a3fb302f82c31f1f Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 13 Jun 2021 01:11:14 -0700 Subject: [PATCH 22/72] exclude linux stuff from PIO --- .github/workflows/build_platformIO.yml | 1 + library.json | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml index 4d70109..e6ca6ee 100644 --- a/.github/workflows/build_platformIO.yml +++ b/.github/workflows/build_platformIO.yml @@ -22,6 +22,7 @@ on: jobs: validate_lib_json: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v2 - name: get latest release version number diff --git a/library.json b/library.json index 5f30fb0..73b2378 100644 --- a/library.json +++ b/library.json @@ -8,7 +8,9 @@ "url": "https://github.com/nRF24/RF24Mesh.git" }, "version": "1.1.6", - "dependencies": [{ + "dependencies": + [ + { "name": "RF24", "authors": "nRF24", "frameworks": "arduino" @@ -19,8 +21,15 @@ "frameworks": "arduino" } ], - "export": { - "exclude": ["examples_RPi/*", "build/*"] + "export": + { + "exclude": [ + "examples_RPi/*", + "pyRF24Mesh/*", + "cmake/*", + "CMakeLists.txt", + "Makefile" + ] }, "frameworks": "arduino", "platforms": [ @@ -30,7 +39,6 @@ "atmelmegaavr", "espressif32", "espressif8266", - "linux_arm", "ststm32" ] } From 76f99b0ecf361a8238ea4f499b01be3f7e024f3a Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 13 Jun 2021 02:48:01 -0700 Subject: [PATCH 23/72] exclude more useless stuff from PIO pkg --- library.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library.json b/library.json index 73b2378..d9b18f3 100644 --- a/library.json +++ b/library.json @@ -24,6 +24,8 @@ "export": { "exclude": [ + ".github/*", + "keywords.txt", "examples_RPi/*", "pyRF24Mesh/*", "cmake/*", From 2e76b7995f1327ea6fc467a8db48affb39e5ea25 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 14 Jun 2021 03:28:43 -0700 Subject: [PATCH 24/72] add while(!Serial) and if(!mesh.begin()) --- .../RF24Mesh_Example/RF24Mesh_Example.ino | 19 +++++++++++-------- .../RF24Mesh_Example_Master.ino | 10 +++++++++- .../RF24Mesh_Example_Master_Statics.ino | 10 +++++++++- .../RF24Mesh_Example_Master_To_Nodes.ino | 10 +++++++++- .../RF24Mesh_Example_Node2Node.ino | 13 +++++++++++-- .../RF24Mesh_Example_Node2NodeExtra.ino | 13 +++++++++++-- .../RF24Mesh_SerialConfig.ino | 11 +++++++++-- examples_RPi/RF24Mesh_Example.cpp | 5 ++++- examples_RPi/RF24Mesh_Example.py | 6 ++++-- examples_RPi/RF24Mesh_Example_Master.cpp | 5 ++++- examples_RPi/RF24Mesh_Example_Master.py | 4 +++- .../ncurses/RF24Mesh_Ncurses_Master.cpp | 7 +++++-- 12 files changed, 89 insertions(+), 24 deletions(-) diff --git a/examples/RF24Mesh_Example/RF24Mesh_Example.ino b/examples/RF24Mesh_Example/RF24Mesh_Example.ino index c7f2d4b..9e31eab 100644 --- a/examples/RF24Mesh_Example/RF24Mesh_Example.ino +++ b/examples/RF24Mesh_Example/RF24Mesh_Example.ino @@ -41,12 +41,21 @@ struct payload_t { void setup() { Serial.begin(115200); - //printf_begin(); + while (!Serial) { + // some boards need this because of native USB capability + } + // Set the nodeID manually mesh.setNodeID(nodeID); + // Connect to the mesh Serial.println(F("Connecting to the mesh...")); - mesh.begin(); + if (!mesh.begin()) { + Serial.println(F("Radio hardware not responding or could not connect to network.")); + while (1) { + // hold in an infinite loop + } + } } @@ -89,9 +98,3 @@ void loop() { Serial.println(payload.ms); } } - - - - - - diff --git a/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino b/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino index fcfa829..6035313 100644 --- a/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino +++ b/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino @@ -29,12 +29,20 @@ uint32_t displayTimer = 0; void setup() { Serial.begin(115200); + while (!Serial) { + // some boards need this because of native USB capability + } // Set the nodeID to 0 for the master node mesh.setNodeID(0); Serial.println(mesh.getNodeID()); // Connect to the mesh - mesh.begin(); + if (!mesh.begin()) { + Serial.println(F("Radio hardware not responding or could not connect to network.")); + while (1) { + // hold in an infinite loop + } + } } diff --git a/examples/RF24Mesh_Example_Master_Statics/RF24Mesh_Example_Master_Statics.ino b/examples/RF24Mesh_Example_Master_Statics/RF24Mesh_Example_Master_Statics.ino index 918baeb..7c4b5f5 100644 --- a/examples/RF24Mesh_Example_Master_Statics/RF24Mesh_Example_Master_Statics.ino +++ b/examples/RF24Mesh_Example_Master_Statics/RF24Mesh_Example_Master_Statics.ino @@ -27,12 +27,20 @@ RF24Mesh mesh(radio, network); void setup() { Serial.begin(115200); + while (!Serial) { + // some boards need this because of native USB capability + } // Set the nodeID to 0 for the master node mesh.setNodeID(0); Serial.println(mesh.getNodeID()); // Connect to the mesh - mesh.begin(); + if (!mesh.begin()) { + Serial.println(F("Radio hardware not responding or could not connect to network.")); + while (1) { + // hold in an infinite loop + } + } // In this case, assign a static address to nodeIDs 23,24 at RF24Network address 02 && 03 // This will prevent this master node from assigning the address to another node diff --git a/examples/RF24Mesh_Example_Master_To_Nodes/RF24Mesh_Example_Master_To_Nodes.ino b/examples/RF24Mesh_Example_Master_To_Nodes/RF24Mesh_Example_Master_To_Nodes.ino index 9ae2503..51de278 100644 --- a/examples/RF24Mesh_Example_Master_To_Nodes/RF24Mesh_Example_Master_To_Nodes.ino +++ b/examples/RF24Mesh_Example_Master_To_Nodes/RF24Mesh_Example_Master_To_Nodes.ino @@ -31,12 +31,20 @@ uint32_t ctr = 0; void setup() { Serial.begin(115200); + while (!Serial) { + // some boards need this because of native USB capability + } // Set the nodeID to 0 for the master node mesh.setNodeID(0); Serial.println(mesh.getNodeID()); // Connect to the mesh - mesh.begin(); + if (!mesh.begin()) { + Serial.println(F("Radio hardware not responding or could not connect to network.")); + while (1) { + // hold in an infinite loop + } + } } diff --git a/examples/RF24Mesh_Example_Node2Node/RF24Mesh_Example_Node2Node.ino b/examples/RF24Mesh_Example_Node2Node/RF24Mesh_Example_Node2Node.ino index fdece75..f3ceb64 100644 --- a/examples/RF24Mesh_Example_Node2Node/RF24Mesh_Example_Node2Node.ino +++ b/examples/RF24Mesh_Example_Node2Node/RF24Mesh_Example_Node2Node.ino @@ -35,11 +35,21 @@ uint32_t millisTimer = 0; void setup() { Serial.begin(115200); + while (!Serial) { + // some boards need this because of native USB capability + } + // Set the nodeID mesh.setNodeID(nodeID); + // Connect to the mesh Serial.println(F("Connecting to the mesh...")); - mesh.begin(); + if (!mesh.begin()) { + Serial.println(F("Radio hardware not responding or could not connect to network.")); + while (1) { + // hold in an infinite loop + } + } } @@ -78,4 +88,3 @@ void loop() { } } - diff --git a/examples/RF24Mesh_Example_Node2NodeExtra/RF24Mesh_Example_Node2NodeExtra.ino b/examples/RF24Mesh_Example_Node2NodeExtra/RF24Mesh_Example_Node2NodeExtra.ino index 0c8d04c..98e5cf8 100644 --- a/examples/RF24Mesh_Example_Node2NodeExtra/RF24Mesh_Example_Node2NodeExtra.ino +++ b/examples/RF24Mesh_Example_Node2NodeExtra/RF24Mesh_Example_Node2NodeExtra.ino @@ -44,12 +44,21 @@ uint32_t delayTime = 120; void setup() { Serial.begin(115200); - //printf_begin(); + while (!Serial) { + // some boards need this because of native USB capability + } + // Set the nodeID manually mesh.setNodeID(nodeID); + // Connect to the mesh Serial.println(F("Connecting to the mesh...")); - mesh.begin(); + if (!mesh.begin()) { + Serial.println(F("Radio hardware not responding or could not connect to network.")); + while (1) { + // hold in an infinite loop + } + } } unsigned int sizeCtr = 2; diff --git a/examples/RF24Mesh_SerialConfig/RF24Mesh_SerialConfig.ino b/examples/RF24Mesh_SerialConfig/RF24Mesh_SerialConfig.ino index 5e8926b..df23d6c 100644 --- a/examples/RF24Mesh_SerialConfig/RF24Mesh_SerialConfig.ino +++ b/examples/RF24Mesh_SerialConfig/RF24Mesh_SerialConfig.ino @@ -32,7 +32,9 @@ RF24Mesh mesh(radio, network); void setup() { Serial.begin(115200); - printf_begin(); + while (!Serial) { + // some boards need this because of native USB capability + } // If this is a new node, the nodeID will return 0. Once the node is configured with an ID other than 0, this // bit will no longer run. @@ -46,7 +48,12 @@ void setup() { } // Now that this node has a unique ID, connect to the mesh - mesh.begin(); + if (!mesh.begin()) { + Serial.println(F("Radio hardware not responding or could not connect to network.")); + while (1) { + // hold in an infinite loop + } + } } diff --git a/examples_RPi/RF24Mesh_Example.cpp b/examples_RPi/RF24Mesh_Example.cpp index 27c295c..454a709 100644 --- a/examples_RPi/RF24Mesh_Example.cpp +++ b/examples_RPi/RF24Mesh_Example.cpp @@ -25,7 +25,10 @@ int main(int argc, char **argv) mesh.setNodeID(4); // Connect to the mesh printf("start nodeID %d\n", mesh.getNodeID()); - mesh.begin(); + if (!mesh.begin()) { + printf("Radio hardware not responding or could not connect to network.\n"); + return 0; + } radio.printDetails(); while (1) diff --git a/examples_RPi/RF24Mesh_Example.py b/examples_RPi/RF24Mesh_Example.py index 43d395e..14e5989 100755 --- a/examples_RPi/RF24Mesh_Example.py +++ b/examples_RPi/RF24Mesh_Example.py @@ -17,13 +17,15 @@ def delay(ms): sleep(ms / 1000.0) # radio setup for RPi B Rev2: CS0=Pin 24 -radio = RF24(22,0) +radio = RF24(22, 0) network = RF24Network(radio) mesh = RF24Mesh(radio, network) mesh.setNodeID(4) print("start nodeID", mesh.getNodeID()) -mesh.begin() +if not mesh.begin(): + print("Radio hardware not responding or could not connect to network.") + exit() radio.setPALevel(RF24_PA_MAX) # Power Amplifier radio.printDetails() diff --git a/examples_RPi/RF24Mesh_Example_Master.cpp b/examples_RPi/RF24Mesh_Example_Master.cpp index dd1d2df..42d1583 100644 --- a/examples_RPi/RF24Mesh_Example_Master.cpp +++ b/examples_RPi/RF24Mesh_Example_Master.cpp @@ -24,7 +24,10 @@ int main(int argc, char **argv) mesh.setNodeID(0); // Connect to the mesh printf("start\n"); - mesh.begin(); + if (!mesh.begin()) { + printf("Radio hardware not responding or could not connect to network.\n"); + return 0; + } radio.printDetails(); while (1) diff --git a/examples_RPi/RF24Mesh_Example_Master.py b/examples_RPi/RF24Mesh_Example_Master.py index 300effa..7f58508 100755 --- a/examples_RPi/RF24Mesh_Example_Master.py +++ b/examples_RPi/RF24Mesh_Example_Master.py @@ -13,7 +13,9 @@ mesh = RF24Mesh(radio, network) mesh.setNodeID(0) -mesh.begin() +if not mesh.begin(): + print("Radio hardware not responding or could not connect to network.") + exit() radio.setPALevel(RF24_PA_MAX) # Power Amplifier radio.printDetails() diff --git a/examples_RPi/ncurses/RF24Mesh_Ncurses_Master.cpp b/examples_RPi/ncurses/RF24Mesh_Ncurses_Master.cpp index e16d538..414b452 100644 --- a/examples_RPi/ncurses/RF24Mesh_Ncurses_Master.cpp +++ b/examples_RPi/ncurses/RF24Mesh_Ncurses_Master.cpp @@ -27,7 +27,7 @@ RF24 radio(22, 0); RF24Network network(radio); -RF24Mesh mesh(radio,network); +RF24Mesh mesh(radio, network); void printNodes(uint8_t boldID); void pingNode(uint8_t listNo); @@ -41,7 +41,10 @@ int main() printf("Establishing mesh...\n"); mesh.setNodeID(0); - mesh.begin(); + if (!mesh.begin()) { + printf("Radio hardware not responding or could not connect to network.\n"); + return 0; + } radio.printDetails(); initscr(); /* Start curses mode */ From f0dc4536a91a995de8da2e23ee692628582fa0a5 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 18 Jun 2021 14:56:45 -0700 Subject: [PATCH 25/72] use CMake CLI to change RF24Mesh_config.h macros --- CMakeLists.txt | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c36c1bf..92bc0af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,11 @@ project(RF24Mesh C CXX) include(cmake/StandardProjectSettings.cmake) include(cmake/PreventInSourceBuilds.cmake) +# allow using CMake options to adjust RF24Network_config.h without modiying source code +option(MESH_NOMASTER "exclude compiling code that is strictly for master nodes" OFF) +option(MESH_DEBUG "enable/disable debugging output" OFF) +option(MESH_DEBUG_MINIMAL "enable/disable minimal debugging output" OFF) + # Link this 'library' to set the c++ standard / compile-time options requested add_library(project_options INTERFACE) target_compile_features(project_options INTERFACE cxx_std_17) @@ -72,6 +77,49 @@ set_target_properties( SOVERSION ${${LibName}_VERSION_MAJOR} VERSION ${${LibName}_VERSION_STRING} ) +# assert the appropriate preprocessor macros for RF24Network_config.h +if(MESH_NOMASTER) + message(STATUS "MESH_NOMASTER asserted") + target_compile_definitions(${LibTargetName} PUBLIC MESH_NOMASTER) +endif() +if(MESH_DEBUG) + message(STATUS "MESH_DEBUG asserted") + target_compile_definitions(${LibTargetName} PUBLIC MESH_DEBUG) +endif() +if(MESH_DEBUG_MINIMAL) + message(STATUS "MESH_DEBUG_MINIMAL asserted") + target_compile_definitions(${LibTargetName} PUBLIC MESH_DEBUG_MINIMAL) +endif() +# for the following, we let the default be configured in source code +if(DEFINED MESH_MAX_CHILDREN) + message(STATUS "MESH_MAX_CHILDREN set to ${MESH_MAX_CHILDREN}") + target_compile_definitions(${LibTargetName} PUBLIC MESH_MAX_CHILDREN=${MESH_MAX_CHILDREN}) +endif() +if(DEFINED MESH_DEFAULT_CHANNEL) + message(STATUS "MESH_DEFAULT_CHANNEL set to ${MESH_DEFAULT_CHANNEL}") + target_compile_definitions(${LibTargetName} PUBLIC MESH_DEFAULT_CHANNEL=${MESH_DEFAULT_CHANNEL}) +endif() +if(DEFINED MESH_RENEWAL_TIMEOUT) + message(STATUS "MESH_RENEWAL_TIMEOUT set to ${MESH_RENEWAL_TIMEOUT}") + target_compile_definitions(${LibTargetName} PUBLIC MESH_RENEWAL_TIMEOUT=${MESH_RENEWAL_TIMEOUT}) +endif() +if(DEFINED MESH_MEM_ALLOC_SIZE) + message(STATUS "MESH_MEM_ALLOC_SIZE set to ${MESH_MEM_ALLOC_SIZE}") + target_compile_definitions(${LibTargetName} PUBLIC MESH_MEM_ALLOC_SIZE=${MESH_MEM_ALLOC_SIZE}) +endif() +if(DEFINED MESH_LOOKUP_TIMEOUT) + message(STATUS "MESH_LOOKUP_TIMEOUT set to ${MESH_LOOKUP_TIMEOUT}") + target_compile_definitions(${LibTargetName} PUBLIC MESH_LOOKUP_TIMEOUT=${MESH_LOOKUP_TIMEOUT}) +endif() +if(DEFINED MESH_WRITE_TIMEOUT) + message(STATUS "MESH_WRITE_TIMEOUT set to ${MESH_WRITE_TIMEOUT}") + target_compile_definitions(${LibTargetName} PUBLIC MESH_WRITE_TIMEOUT=${MESH_WRITE_TIMEOUT}) +endif() +if(DEFINED MESH_DEFAULT_ADDRESS) + message(STATUS "MESH_DEFAULT_ADDRESS set to ${MESH_DEFAULT_ADDRESS}") + target_compile_definitions(${LibTargetName} PUBLIC MESH_DEFAULT_ADDRESS=${MESH_DEFAULT_ADDRESS}) +endif() + ########################### # target install rules for the RF24Log lib From ea7746c9d1a5081c308507c0b8d856004331690d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 28 Jun 2021 14:26:27 -0700 Subject: [PATCH 26/72] [no ci] adjust some whitespace --- RF24Mesh.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 533d5a9..629b4f8 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -98,8 +98,8 @@ bool RF24Mesh::write(uint16_t to_node, const void* data, uint8_t msg_type, size_ { if (mesh_address == MESH_DEFAULT_ADDRESS) { return 0; } - RF24NetworkHeader header(to_node,msg_type); - return network.write(header,data,size); + RF24NetworkHeader header(to_node, msg_type); + return network.write(header, data, size); } /*****************************************************/ @@ -208,8 +208,8 @@ int16_t RF24Mesh::getNodeID(uint16_t address) } #endif - RF24NetworkHeader header( 00, MESH_ID_LOOKUP ); - if (network.write(header,&address,sizeof(address)) ) { + RF24NetworkHeader header(00, MESH_ID_LOOKUP); + if (network.write(header, &address, sizeof(address)) ) { uint32_t timer = millis(); while(network.update() != MESH_ID_LOOKUP) { MESH_CALLBACK @@ -249,8 +249,8 @@ bool RF24Mesh::releaseAddress() { if (mesh_address == MESH_DEFAULT_ADDRESS) { return 0; } - RF24NetworkHeader header(00,MESH_ADDR_RELEASE); - if (network.write(header,0,0)) { + RF24NetworkHeader header(00, MESH_ADDR_RELEASE); + if (network.write(header, 0, 0)) { beginDefault(); return 1; } @@ -285,7 +285,7 @@ uint16_t RF24Mesh::renewAddress(uint32_t timeout) bool RF24Mesh::requestAddress(uint8_t level) { - RF24NetworkHeader header( 0100, NETWORK_POLL ); + RF24NetworkHeader header(0100, NETWORK_POLL); //Find another radio, starting with level 0 multicast #if defined (MESH_DEBUG_SERIAL) Serial.print(millis()); Serial.println(F(" MSH: Poll ")); @@ -325,7 +325,7 @@ bool RF24Mesh::requestAddress(uint8_t level) #if defined (MESH_DEBUG_SERIAL) Serial.print( millis() ); Serial.println(F(" MSH: Poll < -64dbm ")); #elif defined (MESH_DEBUG_PRINTF) - printf( "%u MSH: Poll < -64dbm\n", millis() ); + printf("%u MSH: Poll < -64dbm\n", millis() ); #endif } #endif // defined (MESH_DEBUG_SERIAL) || defined (MESH_DEBUG_PRINTF) @@ -336,7 +336,7 @@ bool RF24Mesh::requestAddress(uint8_t level) #if defined (MESH_DEBUG_SERIAL) Serial.print( millis() ); Serial.print(F(" MSH: No poll from level ")); Serial.println(level); #elif defined (MESH_DEBUG_PRINTF) - printf( "%u MSH: No poll from level %d\n", millis(), level); + printf("%u MSH: No poll from level %d\n", millis(), level); #endif return 0; } @@ -500,12 +500,12 @@ void RF24Mesh::setAddress(uint8_t nodeID, uint16_t address, bool searchBy) void RF24Mesh::loadDHCP() { #if defined (__linux) && !defined(__ARDUINO_X86__) - std::ifstream infile ("dhcplist.txt",std::ifstream::binary); + std::ifstream infile ("dhcplist.txt", std::ifstream::binary); if (!infile) { return; } - infile.seekg(0,infile.end); + infile.seekg(0, infile.end); int length = infile.tellg(); - infile.seekg(0,infile.beg); + infile.seekg(0, infile.beg); addrListStruct tmpNode; @@ -524,7 +524,7 @@ void RF24Mesh::saveDHCP() { std::ofstream outfile("dhcplist.txt", std::ofstream::binary | std::ofstream::trunc); for (int i=0; i< addrListTop; i++) { - outfile.write( (char*)&addrList[i],sizeof(addrListStruct)); + outfile.write((char*)&addrList[i], sizeof(addrListStruct)); } outfile.close(); #endif // __linux & not X86 @@ -621,11 +621,11 @@ void RF24Mesh::DHCP() } else { //delay(2); - network.write(header,&newAddress,sizeof(newAddress),header.to_node); + network.write(header, &newAddress, sizeof(newAddress), header.to_node); } #ifdef MESH_DEBUG_PRINTF - printf("Sent to 0%o phys: 0%o new: 0%o id: %d\n", header.to_node,MESH_DEFAULT_ADDRESS,newAddress,header.reserved); + printf("Sent to 0%o phys: 0%o new: 0%o id: %d\n", header.to_node, MESH_DEFAULT_ADDRESS, newAddress, header.reserved); #endif break; } From a47b242d7f3f12a4b3003947605d5305dc71c36d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 28 Jun 2021 18:07:21 -0700 Subject: [PATCH 27/72] use new macro propsed in nRF24/RF24Network#179 --- RF24Mesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 629b4f8..eb1e3e3 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -285,7 +285,7 @@ uint16_t RF24Mesh::renewAddress(uint32_t timeout) bool RF24Mesh::requestAddress(uint8_t level) { - RF24NetworkHeader header(0100, NETWORK_POLL); + RF24NetworkHeader header(NETWORK_MULTICAST_ADDRESS, NETWORK_POLL); //Find another radio, starting with level 0 multicast #if defined (MESH_DEBUG_SERIAL) Serial.print(millis()); Serial.println(F(" MSH: Poll ")); From 819eb30650da51bb6d16395c1cde8d46684ee965 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 28 Jun 2021 20:11:56 -0700 Subject: [PATCH 28/72] fix docs about addrList --- CMakeLists.txt | 5 +- Doxyfile | 194 ++++++++++++++++++++++++++++++------------- RF24Mesh.h | 33 +++++--- RF24Mesh_config.h | 8 +- docs/setup_config.md | 2 +- 5 files changed, 158 insertions(+), 84 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92bc0af..006b046 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,10 +115,7 @@ if(DEFINED MESH_WRITE_TIMEOUT) message(STATUS "MESH_WRITE_TIMEOUT set to ${MESH_WRITE_TIMEOUT}") target_compile_definitions(${LibTargetName} PUBLIC MESH_WRITE_TIMEOUT=${MESH_WRITE_TIMEOUT}) endif() -if(DEFINED MESH_DEFAULT_ADDRESS) - message(STATUS "MESH_DEFAULT_ADDRESS set to ${MESH_DEFAULT_ADDRESS}") - target_compile_definitions(${LibTargetName} PUBLIC MESH_DEFAULT_ADDRESS=${MESH_DEFAULT_ADDRESS}) -endif() +# MESH_DEFAULT_ADDRESS is an alias of the RF24Network's NETWORK_DEFAULT_ADDRESS definition ########################### diff --git a/Doxyfile b/Doxyfile index c352cd0..91d12cf 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.18 +# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -227,6 +227,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -315,7 +323,10 @@ OPTIMIZE_OUTPUT_SLICE = NO # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. EXTENSION_MAPPING = ino=c \ pde=c @@ -450,6 +461,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -513,6 +537,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -550,11 +581,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# (including Cygwin) ands Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = YES @@ -793,10 +831,13 @@ WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. -WARN_AS_ERROR = NO +WARN_AS_ERROR = YES # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which @@ -830,8 +871,8 @@ INPUT = ./ \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 @@ -844,13 +885,15 @@ INPUT_ENCODING = UTF-8 # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen -# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -1108,16 +1151,22 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories @@ -1127,10 +1176,13 @@ CLANG_ASSISTED_PARSING = NO CLANG_OPTIONS = # If clang assisted parsing is enabled you can provide the clang parser with the -# path to the compilation database (see: -# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files -# were built. This is equivalent to specifying the "-p" option to a clang tool, -# such as clang-check. These options will then be passed to the parser. +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse_libclang=ON option for CMake. @@ -1147,13 +1199,6 @@ CLANG_DATABASE_PATH = ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 10 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored @@ -1324,10 +1369,11 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. @@ -1369,8 +1415,8 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1400,7 +1446,7 @@ CHM_FILE = HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1445,7 +1491,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1453,8 +1500,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1462,16 +1509,16 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = @@ -1483,9 +1530,9 @@ QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1566,8 +1613,8 @@ EXT_LINKS_IN_WINDOW = NO # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png The default and svg Looks nicer but requires the -# pdf2svg tool. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1612,7 +1659,7 @@ USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. @@ -1642,7 +1689,8 @@ MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1689,7 +1737,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1702,8 +1751,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1867,9 +1917,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2380,10 +2432,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2573,9 +2647,11 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/RF24Mesh.h b/RF24Mesh.h index 2ba167b..20a54a2 100644 --- a/RF24Mesh.h +++ b/RF24Mesh.h @@ -257,29 +257,36 @@ class RF24Mesh void setStaticAddress(uint8_t nodeID, uint16_t address); #endif // !defined(MESH_NOMASTER) - - /**@}*/ - /** - * @name Address list struct - * - * See the list struct class reference - */ - /**@{*/ - /**@}*/ uint8_t _nodeID; #if !defined(MESH_NOMASTER) + /** @brief A struct for storing a NodeID and an address in a single element of the RF24Mesh::addrList array */ typedef struct { - uint8_t nodeID; /** NodeIDs and addresses are stored in the addrList array using this structure */ - uint16_t address; /** NodeIDs and addresses are stored in the addrList array using this structure */ + /** @brief the nodeID of an network node (child) */ + uint8_t nodeID; + /** @brief the address of an network node (child) */ + uint16_t address; } addrListStruct; + /** + * @name Address list struct + * @brief helping members for managing the list of assigned addresses + * @see the addrListStruct struct reference + */ + /**@{*/ + // Pointer used for dynamic memory allocation of address list - addrListStruct *addrList; /** See the addrListStruct class reference */ - uint8_t addrListTop; /** The number of entries in the assigned address list */ + /** + * @brief A array of addressListStruct elements for assigned addresses + * @see addrListStruct class reference + */ + addrListStruct *addrList; + /** @brief The number of entries in the addressListStruct of assigned addresses */ + uint8_t addrListTop; #endif + /**@}*/ private: RF24 &radio; diff --git a/RF24Mesh_config.h b/RF24Mesh_config.h index 7e4879c..acf1e35 100644 --- a/RF24Mesh_config.h +++ b/RF24Mesh_config.h @@ -16,13 +16,7 @@ #ifndef MESH_MAX_CHILDREN #define MESH_MAX_CHILDREN 4 #endif // MESH_MAX_CHILDREN -#if defined (RF24_TINY) || defined (DOXYGEN_FORCED) - /** - * @brief Save on resources for non-master nodes - * - * This can be optionally set to 0 for all nodes except the master (nodeID 0) to save program space. - * Enabled automatically on ATTiny devices - */ +#if defined (RF24_TINY) #define MESH_NOMASTER #endif diff --git a/docs/setup_config.md b/docs/setup_config.md index 25ab14e..30d3de8 100644 --- a/docs/setup_config.md +++ b/docs/setup_config.md @@ -48,6 +48,6 @@ The @ref MESH_MAX_CHILDREN option restricts the maximum number of child nodes/no ```cpp #define MESH_NOMASTER ``` -The @ref MESH_NOMASTER option reduces program space and memory usage. Can be used on any node except for the master (nodeID 0) +The MESH_NOMASTER macro optionally reduces program space and memory usage. Can be used on any node except for the master (nodeID 0) @see [General Usage](md_docs_general_usage.html) for information on how to work with the mesh once connected From d79fec3861cebf7ae1e1d2cbe079bc393d0557b9 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 3 Jul 2021 19:38:13 -0700 Subject: [PATCH 29/72] following NASA C style guide --- RF24Mesh.cpp | 69 +++++++++++++++++++++++----------------------------- RF24Mesh.h | 16 ++++++------ 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index eb1e3e3..407859a 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -67,20 +67,20 @@ uint8_t RF24Mesh::update() header->to_node = header->from_node; int16_t returnAddr = 0; - if (type==MESH_ADDR_LOOKUP) { + if (type == MESH_ADDR_LOOKUP) { returnAddr = getAddress(network.frame_buffer[sizeof(RF24NetworkHeader)]); network.write(*header, &returnAddr, sizeof(returnAddr)); } else { int16_t addr = 0; - memcpy(&addr,&network.frame_buffer[sizeof(RF24NetworkHeader)], sizeof(addr)); + memcpy(&addr, &network.frame_buffer[sizeof(RF24NetworkHeader)], sizeof(addr)); returnAddr = getNodeID(addr); network.write(*header, &returnAddr, sizeof(returnAddr)); } } else if (type == MESH_ADDR_RELEASE) { uint16_t *fromAddr = (uint16_t*)network.frame_buffer; - for(uint8_t i = 0; i lookupTimeout || toNode == -2) { return 0; } - retryDelay+=10; + retryDelay += 10; delay(retryDelay); } } @@ -144,12 +144,7 @@ void RF24Mesh::setChild(bool allow) bool RF24Mesh::checkConnection() { - if (getAddress(_nodeID) < 1) { - if (getAddress(_nodeID) < 1) { - return false; - } - } - return true; + return !(getAddress(_nodeID) < 1 && getAddress(_nodeID) < 1); } /*****************************************************/ @@ -199,7 +194,7 @@ int16_t RF24Mesh::getNodeID(uint16_t address) #if !defined(MESH_NOMASTER) if (!mesh_address) { //Master Node - for (uint8_t i = 0; i 0 && contactNode[pollCount] != contactNode[pollCount - 1]) { //Drop duplicate polls to help prevent dupliacate requests + if (pollCount > 0 && contactNode[pollCount] != contactNode[pollCount - 1]) { //Drop duplicate polls to help prevent duplicate requests ++pollCount; } else{ @@ -356,7 +351,7 @@ bool RF24Mesh::requestAddress(uint8_t level) #ifdef MESH_DEBUG_SERIAL Serial.print(millis()); Serial.print(F(" MSH: Got poll from level ")); Serial.print(level); Serial.print(F(" count ")); Serial.print(pollCount); - Serial.print(F(" node ")); Serial.println(contactNode[pollCount-1], OCT); // #ML# + Serial.print(F(" node ")); Serial.println(contactNode[pollCount - 1], OCT); // #ML# #elif defined MESH_DEBUG_PRINTF printf("%u MSH: Got poll from level %d count %d\n", millis(), level, pollCount); #endif @@ -371,9 +366,9 @@ bool RF24Mesh::requestAddress(uint8_t level) // Do a direct write (no ack) to the contact node. Include the nodeId and address. network.write(header, 0, 0, contactNode[i]); #ifdef MESH_DEBUG_SERIAL - Serial.print( millis() ); Serial.print(F(" MSH: Req addr from ")); Serial.println(contactNode[i],OCT); + Serial.print(millis()); Serial.print(F(" MSH: Req addr from ")); Serial.println(contactNode[i], OCT); #elif defined MESH_DEBUG_PRINTF - printf("%u MSH: Request address from: 0%o\n",millis(),contactNode[i]); + printf("%u MSH: Request address from: 0%o\n", millis(), contactNode[i]); #endif timr = millis(); @@ -381,15 +376,15 @@ bool RF24Mesh::requestAddress(uint8_t level) while (millis() - timr < 225) { if (network.update() == NETWORK_ADDR_RESPONSE) { if (network.frame_buffer[7] == _nodeID ) { - uint16_t newAddy = 0; - memcpy(&newAddy, &network.frame_buffer[sizeof(RF24NetworkHeader)], sizeof(newAddy)); - uint16_t mask = 0xFFFF; - newAddy &= ~(mask << (3 * getLevel(contactNode[i]))); // Get the level of contact node. Multiply by 3 to get the number of bits to shift (3 per digit) - if (newAddy == contactNode[i]) { // Then shift the mask by this much, and invert it bitwise. Apply the mask to the newly received - i=pollCount; // address to evalute whether 'subnet' of the assigned address matches the contact node address. - gotResponse = 1; - break; - } + uint16_t newAddy = 0; + memcpy(&newAddy, &network.frame_buffer[sizeof(RF24NetworkHeader)], sizeof(newAddy)); + uint16_t mask = 0xFFFF; + newAddy &= ~(mask << (3 * getLevel(contactNode[i]))); // Get the level of contact node. Multiply by 3 to get the number of bits to shift (3 per digit) + if (newAddy == contactNode[i]) { // Then shift the mask by this much, and invert it bitwise. Apply the mask to the newly received + i = pollCount; // address to evalute whether 'subnet' of the assigned address matches the contact node address. + gotResponse = 1; + break; + } } } MESH_CALLBACK @@ -421,11 +416,9 @@ bool RF24Mesh::requestAddress(uint8_t level) radio.stopListening(); network.begin(mesh_address); - if (getNodeID(mesh_address) != _nodeID) { - if (getNodeID(mesh_address) != _nodeID) { - beginDefault(); - return 0; - } + if (getNodeID(mesh_address) != _nodeID && getNodeID(mesh_address) != _nodeID) { + beginDefault(); + return 0; } return 1; } @@ -463,7 +456,7 @@ void RF24Mesh::setStaticAddress(uint8_t nodeID, uint16_t address) void RF24Mesh::setAddress(uint8_t nodeID, uint16_t address, bool searchBy) { //Look for the node in the list - for(uint8_t i=0; i 0; i--) { // For each of the possible addresses (5 max) @@ -586,21 +579,21 @@ void RF24Mesh::DHCP() for (uint8_t i = 0; i < addrListTop; i++) { #if defined (MESH_DEBUG_MINIMAL) #if !defined (__linux) && !defined ARDUINO_SAM_DUE || defined TEENSY || defined(__ARDUINO_X86__) - Serial.print("ID: ");Serial.print(addrList[i].nodeID,DEC);Serial.print(" ADDR: "); + Serial.print("ID: "); Serial.print(addrList[i].nodeID, DEC); Serial.print(" ADDR: "); uint16_t newAddr = addrList[i].address; char addr[5] = " "; - uint8_t count=3,mask=7; + uint8_t count=3, mask=7; while(newAddr) { - addr[count] = (newAddr & mask)+48; //get the individual Octal numbers, specified in chunks of 3 bits, convert to ASCII by adding 48 + addr[count] = (newAddr & mask) + 48; //get the individual Octal numbers, specified in chunks of 3 bits, convert to ASCII by adding 48 newAddr >>= 3; count--; } Serial.println(addr); #else - printf("ID: %d ADDR: 0%o\n", addrList[i].nodeID,addrList[i].address); + printf("ID: %d ADDR: 0%o\n", addrList[i].nodeID, addrList[i].address); #endif #endif - if ((addrList[i].address == newAddress) && (addrList[i].nodeID != header.reserved)) { + if (addrList[i].address == newAddress && addrList[i].nodeID != header.reserved) { found = true; break; } diff --git a/RF24Mesh.h b/RF24Mesh.h index 20a54a2..bc1b6c2 100644 --- a/RF24Mesh.h +++ b/RF24Mesh.h @@ -182,14 +182,12 @@ class RF24Mesh */ int16_t getAddress(uint8_t nodeID); - /** - * Write to a specific node by RF24Network address. - * - */ + /** @brief Write to a specific node by RF24Network address. */ bool write(uint16_t to_node, const void *data, uint8_t msg_type, size_t size); /** * Change the active radio channel after the mesh has been started. + * @param _channel The value passed to `RF24::setChannel()` */ void setChannel(uint8_t _channel); @@ -218,20 +216,20 @@ class RF24Mesh #if !defined(MESH_NOMASTER) /** - * Set/change a nodeID/RF24Network Address pair manually on the master node. + * Set or change a nodeID:RF24Network Address (key:value) pair manually on the master node. * * @code - * Set a static address for node 02, with nodeID 23, since it will just be a static routing node for example + * // Set a static address for node 02, with nodeID 23, since it will just be a static routing node for example * running on an ATTiny chip. * - * mesh.setAddress(23,02); + * mesh.setAddress(23, 02); * @endcode * * @code - * Change/set the nodeID for an existing address + * // Change or set the nodeID for an existing address * * uint16_t address = 012; - * mesh.setAddress(3,address,true); + * mesh.setAddress(3, address, true); * @endcode * * @param nodeID The nodeID to assign From 10ce90e72f75f168f9913994760039c227e2a006 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 10 Jul 2021 11:58:27 -0700 Subject: [PATCH 30/72] uniform MESH_DEBUG* calls --- RF24Mesh.cpp | 98 +++---------------- RF24Mesh.h | 6 +- RF24Mesh_config.h | 33 +++++-- .../RF24Mesh_Example/RF24Mesh_Example.ino | 17 ++-- 4 files changed, 49 insertions(+), 105 deletions(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 407859a..86692a4 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -282,9 +282,7 @@ bool RF24Mesh::requestAddress(uint8_t level) { RF24NetworkHeader header(NETWORK_MULTICAST_ADDRESS, NETWORK_POLL); //Find another radio, starting with level 0 multicast - #if defined (MESH_DEBUG_SERIAL) - Serial.print(millis()); Serial.println(F(" MSH: Poll ")); - #endif + IF_MESH_DEBUG(printf_P(PSTR("%ui: MSH Poll\n"), millis())); network.multicast(header, 0, 0, level); uint32_t timr = millis(); @@ -293,7 +291,7 @@ bool RF24Mesh::requestAddress(uint8_t level) uint8_t pollCount = 0; while (1) { - #if defined (MESH_DEBUG_SERIAL) || defined (MESH_DEBUG_PRINTF) + #if defined (MESH_DEBUG) bool goodSignal = radio.testRPD(); #endif @@ -307,40 +305,16 @@ bool RF24Mesh::requestAddress(uint8_t level) ++pollCount; } - #if defined (MESH_DEBUG_SERIAL) || defined (MESH_DEBUG_PRINTF) - if (goodSignal) { - // This response was better than -64dBm - #if defined (MESH_DEBUG_SERIAL) - Serial.print(millis()); Serial.println(F(" MSH: Poll > -64dbm ")); - #elif defined (MESH_DEBUG_PRINTF) - printf("%u MSH: Poll > -64dbm\n", millis()); - #endif - } - else{ - #if defined (MESH_DEBUG_SERIAL) - Serial.print( millis() ); Serial.println(F(" MSH: Poll < -64dbm ")); - #elif defined (MESH_DEBUG_PRINTF) - printf("%u MSH: Poll < -64dbm\n", millis() ); - #endif - } - #endif // defined (MESH_DEBUG_SERIAL) || defined (MESH_DEBUG_PRINTF) + IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Poll %c -64dbm\n"), millis(), (goodSignal ? '>' : '<'))); } // end if if (millis() - timr > 55 || pollCount >= MESH_MAXPOLLS ) { if (!pollCount) { - #if defined (MESH_DEBUG_SERIAL) - Serial.print( millis() ); Serial.print(F(" MSH: No poll from level ")); Serial.println(level); - #elif defined (MESH_DEBUG_PRINTF) - printf("%u MSH: No poll from level %d\n", millis(), level); - #endif + IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH No poll from level %d\n"), millis(), level)); return 0; } else { - #if defined (MESH_DEBUG_SERIAL) - Serial.print(millis()); Serial.println(F(" MSH: Poll OK ")); - #elif defined (MESH_DEBUG_PRINTF) - printf("%u MSH: Poll OK\n", millis()); - #endif + IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Poll OK\n"), millis())); break; } } @@ -348,13 +322,7 @@ bool RF24Mesh::requestAddress(uint8_t level) } // end while - #ifdef MESH_DEBUG_SERIAL - Serial.print(millis()); Serial.print(F(" MSH: Got poll from level ")); Serial.print(level); - Serial.print(F(" count ")); Serial.print(pollCount); - Serial.print(F(" node ")); Serial.println(contactNode[pollCount - 1], OCT); // #ML# - #elif defined MESH_DEBUG_PRINTF - printf("%u MSH: Got poll from level %d count %d\n", millis(), level, pollCount); - #endif + IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Got poll from level %d count %d\n"), millis(), level, pollCount)); bool gotResponse = 0; for (uint8_t i = 0; i < pollCount; i++) { @@ -365,11 +333,8 @@ bool RF24Mesh::requestAddress(uint8_t level) // Do a direct write (no ack) to the contact node. Include the nodeId and address. network.write(header, 0, 0, contactNode[i]); - #ifdef MESH_DEBUG_SERIAL - Serial.print(millis()); Serial.print(F(" MSH: Req addr from ")); Serial.println(contactNode[i], OCT); - #elif defined MESH_DEBUG_PRINTF - printf("%u MSH: Request address from: 0%o\n", millis(), contactNode[i]); - #endif + + IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Request address from: 0%o\n"), millis(), contactNode[i])); timr = millis(); @@ -395,22 +360,10 @@ bool RF24Mesh::requestAddress(uint8_t level) return 0; } - #ifdef MESH_DEBUG_SERIAL - uint8_t mask = 7, count=3; - char addrs[5] = " "; - uint16_t newAddr; - #endif - uint16_t newAddress=0; memcpy(&newAddress, network.frame_buffer + sizeof(RF24NetworkHeader), sizeof(newAddress)); - #ifdef MESH_DEBUG_SERIAL - Serial.print(millis()); - Serial.print(F(" Set address: ")); - Serial.println(newAddress, OCT); - #elif defined (MESH_DEBUG_PRINTF) - printf("Set address 0%o rcvd 0%o\n", mesh_address, newAddress); - #endif + IF_MESH_DEBUG(printf_P(PSTR("Set address 0%o rcvd 0%o\n"), mesh_address, newAddress)); mesh_address = newAddress; radio.stopListening(); @@ -539,9 +492,7 @@ void RF24Mesh::DHCP() // Get the unique id of the requester (ID is in header.reserved) if (!header.reserved || header.type != NETWORK_REQ_ADDRESS) { - #ifdef MESH_DEBUG_PRINTF - printf("MSH: Invalid id or type rcvd\n"); - #endif + IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Invalid id or type rcvd\n"), millis())); return; } @@ -565,9 +516,7 @@ void RF24Mesh::DHCP() extraChild = 1; } - #ifdef MESH_DEBUG_PRINTF - // printf("%u MSH: Rcv addr req from_id %d \n", millis(), header.reserved); - #endif + // IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Rcv addr req from_id %d\n"), millis(), header.reserved)); for (int i = MESH_MAX_CHILDREN + extraChild; i > 0; i--) { // For each of the possible addresses (5 max) @@ -577,22 +526,7 @@ void RF24Mesh::DHCP() if (newAddress == MESH_DEFAULT_ADDRESS) { continue; } for (uint8_t i = 0; i < addrListTop; i++) { - #if defined (MESH_DEBUG_MINIMAL) - #if !defined (__linux) && !defined ARDUINO_SAM_DUE || defined TEENSY || defined(__ARDUINO_X86__) - Serial.print("ID: "); Serial.print(addrList[i].nodeID, DEC); Serial.print(" ADDR: "); - uint16_t newAddr = addrList[i].address; - char addr[5] = " "; - uint8_t count=3, mask=7; - while(newAddr) { - addr[count] = (newAddr & mask) + 48; //get the individual Octal numbers, specified in chunks of 3 bits, convert to ASCII by adding 48 - newAddr >>= 3; - count--; - } - Serial.println(addr); - #else - printf("ID: %d ADDR: 0%o\n", addrList[i].nodeID, addrList[i].address); - #endif - #endif + IF_MESH_DEBUG_MINIMAL(printf_P(PSTR("ID: %d ADDR: 0%o\n"), addrList[i].nodeID, addrList[i].address)); if (addrList[i].address == newAddress && addrList[i].nodeID != header.reserved) { found = true; break; @@ -617,15 +551,11 @@ void RF24Mesh::DHCP() network.write(header, &newAddress, sizeof(newAddress), header.to_node); } - #ifdef MESH_DEBUG_PRINTF - printf("Sent to 0%o phys: 0%o new: 0%o id: %d\n", header.to_node, MESH_DEFAULT_ADDRESS, newAddress, header.reserved); - #endif + IF_MESH_DEBUG(printf_P(PSTR("Sent to 0%o phys: 0%o new: 0%o id: %d\n"), header.to_node, MESH_DEFAULT_ADDRESS, newAddress, header.reserved)); break; } else { - #if defined (MESH_DEBUG_PRINTF) - printf("not allocated\n"); - #endif + IF_MESH_DEBUG(printf_P(PSTR("not allocated\n"))); } } // end for } diff --git a/RF24Mesh.h b/RF24Mesh.h index bc1b6c2..9117636 100644 --- a/RF24Mesh.h +++ b/RF24Mesh.h @@ -54,7 +54,7 @@ class RF24Mesh /** * @name RF24Mesh * - * The mesh library and class documentation is currently in active development and usage may change. + * The mesh library and class documentation is currently in active development and usage may change. */ /**@{*/ public: @@ -62,9 +62,9 @@ class RF24Mesh * Construct the mesh: * * @code - * RF24 radio(7,8); + * RF24 radio(7, 8); * RF24Network network(radio); - * RF24Mesh mesh(radio,network); + * RF24Mesh mesh(radio, network); * @endcode * @param _radio The underlying radio driver instance * @param _network The underlying network instance diff --git a/RF24Mesh_config.h b/RF24Mesh_config.h index acf1e35..7031689 100644 --- a/RF24Mesh_config.h +++ b/RF24Mesh_config.h @@ -74,7 +74,7 @@ #define MESH_LOOKUP_TIMEOUT 135 #endif // MESH_LOOKUP_TIMEOUT -/** @brief How long RF24Mesh::write retries address lookups before timing out. Allows multiple attempts */ +/** @brief How long RF24Mesh::write() retries address lookups before timing out. Allows multiple attempts */ #ifndef MESH_WRITE_TIMEOUT #define MESH_WRITE_TIMEOUT 115 #endif // MESH_WRITE_TIMEOUT @@ -83,15 +83,30 @@ #define MESH_DEFAULT_ADDRESS NETWORK_DEFAULT_ADDRESS #endif // MESH_DEFAULT_ADDRESS -//#define MESH_MAX_ADDRESSES 255 /** UNUSED Determines the max size of the array used for storing addresses on the Master Node */ -//#define MESH_ADDRESS_HOLD_TIME 30000 /**UNUSED How long before a released address becomes available */ +//#define MESH_MAX_ADDRESSES 255 /* UNUSED Determines the max size of the array used for storing addresses on the Master Node */ +//#define MESH_ADDRESS_HOLD_TIME 30000 /* UNUSED How long before a released address becomes available */ + +#if (defined (__linux) || defined (linux)) && !defined (__ARDUINO_X86__) + #include + +//ATXMega +#elif defined(XMEGA) + #include "../../rf24lib/rf24lib/RF24_config.h" +#else + #include +#endif + +#if defined (MESH_DEBUG_MINIMAL) + #define IF_MESH_DEBUG_MINIMAL(x) ({x;}) +#else + #define IF_MESH_DEBUG_MINIMAL(x) +#endif + #if defined (MESH_DEBUG) - #if !defined (__linux) && !defined ARDUINO_SAM_DUE || defined TEENSY || defined(__ARDUINO_X86__) - #define MESH_DEBUG_SERIAL - #else - #define MESH_DEBUG_PRINTF - #endif // platform ostream def -#endif // defined (MESH_DEBUG) + #define IF_MESH_DEBUG(x) ({x;}) +#else + #define IF_MESH_DEBUG(x) +#endif #endif // __RF24MESH_CONFIG_H__ diff --git a/examples/RF24Mesh_Example/RF24Mesh_Example.ino b/examples/RF24Mesh_Example/RF24Mesh_Example.ino index 9e31eab..d72cd6a 100644 --- a/examples/RF24Mesh_Example/RF24Mesh_Example.ino +++ b/examples/RF24Mesh_Example/RF24Mesh_Example.ino @@ -19,15 +19,14 @@ RF24 radio(7, 8); RF24Network network(radio); RF24Mesh mesh(radio, network); -/** - User Configuration: nodeID - A unique identifier for each radio. Allows addressing - to change dynamically with physical changes to the mesh. - - In this example, configuration takes place below, prior to uploading the sketch to the device - A unique value from 1-255 must be configured for each node. - This will be stored in EEPROM on AVR devices, so remains persistent between further uploads, loss of power, etc. - - **/ +/* + * User Configuration: nodeID - A unique identifier for each radio. Allows addressing + * to change dynamically with physical changes to the mesh. + * + * In this example, configuration takes place below, prior to uploading the sketch to the device + * A unique value from 1-255 must be configured for each node. + * This will be stored in EEPROM on AVR devices, so remains persistent between further uploads, loss of power, etc. + */ #define nodeID 1 From 6d130d80f08fe8184e0c0d5cbab7ece38e4d5fe9 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 10 Jul 2021 12:12:03 -0700 Subject: [PATCH 31/72] alias NET_MULTICAST_ADDR --- RF24Mesh.cpp | 2 +- RF24Mesh_config.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 86692a4..3a7e059 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -280,7 +280,7 @@ uint16_t RF24Mesh::renewAddress(uint32_t timeout) bool RF24Mesh::requestAddress(uint8_t level) { - RF24NetworkHeader header(NETWORK_MULTICAST_ADDRESS, NETWORK_POLL); + RF24NetworkHeader header(MESH_MULTICAST_ADDRESS, NETWORK_POLL); //Find another radio, starting with level 0 multicast IF_MESH_DEBUG(printf_P(PSTR("%ui: MSH Poll\n"), millis())); network.multicast(header, 0, 0, level); diff --git a/RF24Mesh_config.h b/RF24Mesh_config.h index 7031689..cf52d7e 100644 --- a/RF24Mesh_config.h +++ b/RF24Mesh_config.h @@ -83,6 +83,8 @@ #define MESH_DEFAULT_ADDRESS NETWORK_DEFAULT_ADDRESS #endif // MESH_DEFAULT_ADDRESS +#define MESH_MULTICAST_ADDRESS NETWORK_MULTICAST_ADDRESS + //#define MESH_MAX_ADDRESSES 255 /* UNUSED Determines the max size of the array used for storing addresses on the Master Node */ //#define MESH_ADDRESS_HOLD_TIME 30000 /* UNUSED How long before a released address becomes available */ From 057a2e58f452356ba9f9bdb3676da78867799e05 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 10 Jul 2021 12:13:15 -0700 Subject: [PATCH 32/72] trigger Arduino CI workflow --- .github/workflows/build_arduino.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_arduino.yml b/.github/workflows/build_arduino.yml index b89c712..8c79e9a 100644 --- a/.github/workflows/build_arduino.yml +++ b/.github/workflows/build_arduino.yml @@ -57,7 +57,7 @@ jobs: - "arduino:avr:pro" - "arduino:avr:atmegang" - "arduino:avr:robotControl" - # - "arduino:avr:gemma" # does not support SPI + # - "arduino:avr:gemma" # does not support SPI - "arduino:avr:circuitplay32u4cat" - "arduino:avr:yunmini" - "arduino:avr:chiwawa" From 31039cf62e14e96588759f58402cd4b36c3c460c Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 10 Jul 2021 12:15:41 -0700 Subject: [PATCH 33/72] trigger PlatformIO CI --- .github/workflows/build_platformIO.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml index e6ca6ee..a710ede 100644 --- a/.github/workflows/build_platformIO.yml +++ b/.github/workflows/build_platformIO.yml @@ -25,22 +25,28 @@ jobs: steps: - uses: actions/checkout@v2 + - name: get latest release version number id: latest_ver run: echo "::set-output name=release::$(awk -F "=" '/version/ {print $2}' library.properties)" + - name: Set up Python uses: actions/setup-python@v2 + - name: Install PlatformIO run: | python -m pip install --upgrade pip pip install --upgrade platformio + - name: package lib run: pio package pack -o PlatformIO-RF24Mesh-${{ steps.latest_ver.outputs.release }}.tar.gz + - name: Save artifact uses: actions/upload-artifact@v2 with: name: "PIO_pkg_RF24Mesh" path: PlatformIO*.tar.gz + - name: Upload Release assets if: github.event_name == 'release' uses: csexton/release-asset-action@master @@ -93,6 +99,7 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Cache pip uses: actions/cache@v2 with: @@ -100,23 +107,28 @@ jobs: key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- + - name: Cache PlatformIO uses: actions/cache@v2 with: path: ~/.platformio key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + - name: Set up Python uses: actions/setup-python@v2 + - name: Install PlatformIO run: | python -m pip install --upgrade pip pip install --upgrade platformio + # "dependencies" field in JSON should automatically install RF24 & RF24Network. # Because we run this CI test against the local repo, the JSON seems neglected - name: Install library dependencies run: | pio lib -g install nrf24/RF24 pio lib -g install nrf24/RF24Network + - name: Run PlatformIO run: pio ci --lib="." --board=${{ matrix.board }} env: From 656bca17e8b92157abf74c160b5175654c859a5b Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 10 Jul 2021 16:55:27 -0700 Subject: [PATCH 34/72] remove artifact; resolves #190 --- .github/workflows/build_arduino.yml | 7 ++++--- .github/workflows/build_platformIO.yml | 6 +++--- .../RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino | 2 -- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build_arduino.yml b/.github/workflows/build_arduino.yml index 8c79e9a..16301ed 100644 --- a/.github/workflows/build_arduino.yml +++ b/.github/workflows/build_arduino.yml @@ -94,10 +94,11 @@ jobs: - examples/RF24Mesh_Example_Node2Node - examples/RF24Mesh_Example_Node2NodeExtra - examples/RF24Mesh_SerialConfig - # The following is incompatible with non-AVR boards due to use of EEPROM.h - # - examples/RF24Mesh_Example_Master + - examples/RF24Mesh_Example_Master libraries: | - name: RF24 - - name: RF24Network + - source-url: https://github.com/nRF24/RF24Network + version: CMake-4-Linux - source-path: ./ + # - name: RF24Network fqbn: ${{ matrix.fqbn }} diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml index a710ede..78699f5 100644 --- a/.github/workflows/build_platformIO.yml +++ b/.github/workflows/build_platformIO.yml @@ -87,8 +87,7 @@ jobs: - "examples/RF24Mesh_Example_Node2Node/RF24Mesh_Example_Node2Node.ino" - "examples/RF24Mesh_Example_Node2NodeExtra/RF24Mesh_Example_Node2NodeExtra.ino" - "examples/RF24Mesh_SerialConfig/RF24Mesh_SerialConfig.ino" - # ignore this last one because it only works on AVR chips (uses EEPROM.h) - # - "examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino" + - "examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino" board: - "teensy31" - "teensy35" @@ -127,7 +126,8 @@ jobs: - name: Install library dependencies run: | pio lib -g install nrf24/RF24 - pio lib -g install nrf24/RF24Network + pio lib -g install https://github.com/nrf24/RF24Network.git#CMake-4-Linux + # pio lib -g install nrf24/RF24Network - name: Run PlatformIO run: pio ci --lib="." --board=${{ matrix.board }} diff --git a/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino b/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino index 6035313..e626e70 100644 --- a/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino +++ b/examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino @@ -17,8 +17,6 @@ #include "RF24.h" #include "RF24Mesh.h" #include -//Include eeprom.h for AVR (Uno, Nano) etc. except ATTiny -#include /***** Configure the chosen CE,CS pins *****/ RF24 radio(7, 8); From ce300b64f7bc8a82db134ede064a56df3675884e Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 10 Jul 2021 17:01:57 -0700 Subject: [PATCH 35/72] [Arduino CI] url option must end in .git --- .github/workflows/build_arduino.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_arduino.yml b/.github/workflows/build_arduino.yml index 16301ed..f854dbb 100644 --- a/.github/workflows/build_arduino.yml +++ b/.github/workflows/build_arduino.yml @@ -97,7 +97,7 @@ jobs: - examples/RF24Mesh_Example_Master libraries: | - name: RF24 - - source-url: https://github.com/nRF24/RF24Network + - source-url: https://github.com/nRF24/RF24Network.git version: CMake-4-Linux - source-path: ./ # - name: RF24Network From 9af78584adb14dedc8711011962ff6c0419afa05 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sat, 17 Jul 2021 06:06:18 -0700 Subject: [PATCH 36/72] use %iu instead of %ui --- RF24Mesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 3a7e059..3405c40 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -282,7 +282,7 @@ bool RF24Mesh::requestAddress(uint8_t level) { RF24NetworkHeader header(MESH_MULTICAST_ADDRESS, NETWORK_POLL); //Find another radio, starting with level 0 multicast - IF_MESH_DEBUG(printf_P(PSTR("%ui: MSH Poll\n"), millis())); + IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Poll\n"), millis())); network.multicast(header, 0, 0, level); uint32_t timr = millis(); From aaed824be7e70f39c592639efdaeafb264f89ef9 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sat, 17 Jul 2021 06:11:24 -0700 Subject: [PATCH 37/72] use just %u --- RF24Mesh.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 3405c40..b1417c2 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -282,7 +282,7 @@ bool RF24Mesh::requestAddress(uint8_t level) { RF24NetworkHeader header(MESH_MULTICAST_ADDRESS, NETWORK_POLL); //Find another radio, starting with level 0 multicast - IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Poll\n"), millis())); + IF_MESH_DEBUG(printf_P(PSTR("%u: MSH Poll\n"), millis())); network.multicast(header, 0, 0, level); uint32_t timr = millis(); @@ -305,16 +305,16 @@ bool RF24Mesh::requestAddress(uint8_t level) ++pollCount; } - IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Poll %c -64dbm\n"), millis(), (goodSignal ? '>' : '<'))); + IF_MESH_DEBUG(printf_P(PSTR("%u: MSH Poll %c -64dbm\n"), millis(), (goodSignal ? '>' : '<'))); } // end if if (millis() - timr > 55 || pollCount >= MESH_MAXPOLLS ) { if (!pollCount) { - IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH No poll from level %d\n"), millis(), level)); + IF_MESH_DEBUG(printf_P(PSTR("%u: MSH No poll from level %d\n"), millis(), level)); return 0; } else { - IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Poll OK\n"), millis())); + IF_MESH_DEBUG(printf_P(PSTR("%u: MSH Poll OK\n"), millis())); break; } } @@ -322,7 +322,7 @@ bool RF24Mesh::requestAddress(uint8_t level) } // end while - IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Got poll from level %d count %d\n"), millis(), level, pollCount)); + IF_MESH_DEBUG(printf_P(PSTR("%u: MSH Got poll from level %d count %d\n"), millis(), level, pollCount)); bool gotResponse = 0; for (uint8_t i = 0; i < pollCount; i++) { @@ -334,7 +334,7 @@ bool RF24Mesh::requestAddress(uint8_t level) // Do a direct write (no ack) to the contact node. Include the nodeId and address. network.write(header, 0, 0, contactNode[i]); - IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Request address from: 0%o\n"), millis(), contactNode[i])); + IF_MESH_DEBUG(printf_P(PSTR("%u: MSH Request address from: 0%o\n"), millis(), contactNode[i])); timr = millis(); @@ -492,7 +492,7 @@ void RF24Mesh::DHCP() // Get the unique id of the requester (ID is in header.reserved) if (!header.reserved || header.type != NETWORK_REQ_ADDRESS) { - IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Invalid id or type rcvd\n"), millis())); + IF_MESH_DEBUG(printf_P(PSTR("%u: MSH Invalid id or type rcvd\n"), millis())); return; } @@ -516,7 +516,7 @@ void RF24Mesh::DHCP() extraChild = 1; } - // IF_MESH_DEBUG(printf_P(PSTR("%iu: MSH Rcv addr req from_id %d\n"), millis(), header.reserved)); + // IF_MESH_DEBUG(printf_P(PSTR("%u: MSH Rcv addr req from_id %d\n"), millis(), header.reserved)); for (int i = MESH_MAX_CHILDREN + extraChild; i > 0; i--) { // For each of the possible addresses (5 max) From 6f8285f890a7cfc1497f2669f8dc33e69a72244f Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 18 Jul 2021 03:26:14 -0700 Subject: [PATCH 38/72] add comment about double check --- RF24Mesh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index b1417c2..8427ff3 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -144,6 +144,7 @@ void RF24Mesh::setChild(bool allow) bool RF24Mesh::checkConnection() { + // getAddress() doesn't use auto-ack; do a double-check to manually retry 1 more time return !(getAddress(_nodeID) < 1 && getAddress(_nodeID) < 1); } @@ -369,6 +370,7 @@ bool RF24Mesh::requestAddress(uint8_t level) radio.stopListening(); network.begin(mesh_address); + // getNodeID() doesn't use auto-ack; do a double-check to manually retry 1 more time if (getNodeID(mesh_address) != _nodeID && getNodeID(mesh_address) != _nodeID) { beginDefault(); return 0; From be5d66c03b2b4b89b90c02e61bd86ee1c0ac5e5d Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 18 Jul 2021 18:53:18 -0700 Subject: [PATCH 39/72] use an OR condition instead --- RF24Mesh.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 8427ff3..eac537a 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -145,7 +145,7 @@ void RF24Mesh::setChild(bool allow) bool RF24Mesh::checkConnection() { // getAddress() doesn't use auto-ack; do a double-check to manually retry 1 more time - return !(getAddress(_nodeID) < 1 && getAddress(_nodeID) < 1); + return !(getAddress(_nodeID) < 1) || !(getAddress(_nodeID) < 1); } /*****************************************************/ @@ -371,7 +371,7 @@ bool RF24Mesh::requestAddress(uint8_t level) network.begin(mesh_address); // getNodeID() doesn't use auto-ack; do a double-check to manually retry 1 more time - if (getNodeID(mesh_address) != _nodeID && getNodeID(mesh_address) != _nodeID) { + if (getNodeID(mesh_address) != _nodeID || getNodeID(mesh_address) != _nodeID) { beginDefault(); return 0; } @@ -509,7 +509,7 @@ void RF24Mesh::DHCP() while(m) { //Octal addresses convert nicely to binary in threes. Address 03 = B011 Address 033 = B011011 m >>= 3; //Find out how many digits are in the octal address - count+=3; + count += 3; } shiftVal = count; //Now we know how many bits to shift when adding a child node 1-5 (B001 to B101) to any address } From 3a776de12e8b87ae8107b06e166795083446a8f0 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 18 Jul 2021 19:31:35 -0700 Subject: [PATCH 40/72] revert to nested if statements --- RF24Mesh.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index eac537a..d3606fc 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -145,7 +145,12 @@ void RF24Mesh::setChild(bool allow) bool RF24Mesh::checkConnection() { // getAddress() doesn't use auto-ack; do a double-check to manually retry 1 more time - return !(getAddress(_nodeID) < 1) || !(getAddress(_nodeID) < 1); + if (getAddress(_nodeID) < 1) { + if (getAddress(_nodeID) < 1) { + return false; + } + } + return true; } /*****************************************************/ @@ -371,9 +376,11 @@ bool RF24Mesh::requestAddress(uint8_t level) network.begin(mesh_address); // getNodeID() doesn't use auto-ack; do a double-check to manually retry 1 more time - if (getNodeID(mesh_address) != _nodeID || getNodeID(mesh_address) != _nodeID) { - beginDefault(); - return 0; + if (getNodeID(mesh_address) != _nodeID) { + if (getNodeID(mesh_address) != _nodeID) { + beginDefault(); + return 0; + } } return 1; } From fcf70ab38680651cb59988d3b699aaeec94c20fc Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Wed, 21 Jul 2021 22:12:39 -0700 Subject: [PATCH 41/72] [py wrapper] augment setup.py --- pyRF24Mesh/setup.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/pyRF24Mesh/setup.py b/pyRF24Mesh/setup.py index de47185..063b4ea 100755 --- a/pyRF24Mesh/setup.py +++ b/pyRF24Mesh/setup.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import os from setuptools import setup, Extension from sys import version_info @@ -8,20 +9,39 @@ else: BOOST_LIB = "boost_python" +git_dir = os.path.split(os.path.abspath(os.getcwd()))[0] + +# get LIB_VERSION from library.properties file for Arduino IDE +version = "1.0" +with open(os.path.join(git_dir, "library.properties"), "r") as f: + for line in f.read().splitlines(): + if line.startswith("version"): + version = line.split("=")[1] + + +long_description = """ +.. warning:: This python wrapper for the RF24Mesh C++ library was not intended + for distribution on pypi.org. If you're reading this, then this package + is likely unauthorized or unofficial. +""" + setup( name="RF24Mesh", - version="1.0", - classifiers = [ + version=version, + license_files=os.path.join(git_dir, "LICENSE"), + long_description=long_description, + long_description_content_type="text/x-rst", + classifiers=[ "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Programming Language :: C++", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", ], - ext_modules = [ + ext_modules=[ Extension( "RF24Mesh", - libraries=["rf24mesh", "rf24network", BOOST_LIB], sources=["pyRF24Mesh.cpp"], + libraries=["rf24", "rf24network", "rf24mesh", BOOST_LIB], ) - ] + ], ) From 61a7f913ea86f40ba6b510daa9ea537770dc3a87 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Thu, 22 Jul 2021 02:23:30 -0700 Subject: [PATCH 42/72] py pkgs from apt are very out-of-date --- .github/workflows/build_linux.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index b74c532..23d6621 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -166,6 +166,12 @@ jobs: file ./RF24Mesh_Example # cross-compiling a python C extension is better done with pypa/cibuildwheel action + - name: Set up Python 3.7 + if: ${{ matrix.toolchain.compiler == 'default' }} + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: provide python wrapper prerequisites if: ${{ matrix.toolchain.compiler == 'default' }} # python3-rpi.gpio is only required for physical hardware (namely the IRQ example) From b78d5cd1c81dc9377c7de0c9d17d590a0ccf8248 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Fri, 23 Jul 2021 04:42:03 -0700 Subject: [PATCH 43/72] [py wrapper] ammend license info --- pyRF24Mesh/setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyRF24Mesh/setup.py b/pyRF24Mesh/setup.py index 063b4ea..4c9590c 100755 --- a/pyRF24Mesh/setup.py +++ b/pyRF24Mesh/setup.py @@ -28,7 +28,8 @@ setup( name="RF24Mesh", version=version, - license_files=os.path.join(git_dir, "LICENSE"), + license="GPLv2", + license_files=(os.path.join(git_dir, "LICENSE"),), long_description=long_description, long_description_content_type="text/x-rst", classifiers=[ From 2d6c1caa7bcd67c2cc5f357d5aab580ab8d1fd2d Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Mon, 26 Jul 2021 13:46:22 -0700 Subject: [PATCH 44/72] add MESH_SLOW_ADDRESS_RESPONSE; fix an indent --- CMakeLists.txt | 4 ++++ RF24Mesh.cpp | 15 +++++++++------ RF24Mesh_config.h | 13 +++++++++++++ pyRF24Mesh/setup.py | 2 ++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 006b046..7b8becf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,10 @@ if(DEFINED MESH_WRITE_TIMEOUT) message(STATUS "MESH_WRITE_TIMEOUT set to ${MESH_WRITE_TIMEOUT}") target_compile_definitions(${LibTargetName} PUBLIC MESH_WRITE_TIMEOUT=${MESH_WRITE_TIMEOUT}) endif() +if(DEFINED MESH_SLOW_ADDR_RESPONSE) + message(STATUS "MESH_SLOW_ADDR_RESPONSE set to ${MESH_SLOW_ADDR_RESPONSE}") + target_compile_definitions(${LibTargetName} PUBLIC MESH_SLOW_ADDR_RESPONSE=${MESH_SLOW_ADDR_RESPONSE}) +endif() # MESH_DEFAULT_ADDRESS is an alias of the RF24Network's NETWORK_DEFAULT_ADDRESS definition diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index d3606fc..747c012 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -351,11 +351,11 @@ bool RF24Mesh::requestAddress(uint8_t level) memcpy(&newAddy, &network.frame_buffer[sizeof(RF24NetworkHeader)], sizeof(newAddy)); uint16_t mask = 0xFFFF; newAddy &= ~(mask << (3 * getLevel(contactNode[i]))); // Get the level of contact node. Multiply by 3 to get the number of bits to shift (3 per digit) - if (newAddy == contactNode[i]) { // Then shift the mask by this much, and invert it bitwise. Apply the mask to the newly received - i = pollCount; // address to evalute whether 'subnet' of the assigned address matches the contact node address. - gotResponse = 1; - break; - } + if (newAddy == contactNode[i]) { // Then shift the mask by this much, and invert it bitwise. Apply the mask to the newly received + i = pollCount; // address to evalute whether 'subnet' of the assigned address matches the contact node address. + gotResponse = 1; + break; + } } } MESH_CALLBACK @@ -548,7 +548,10 @@ void RF24Mesh::DHCP() //This is a routed request to 00 setAddress(header.reserved, newAddress); - //delay(10); // ML: without this delay, address renewal fails + // without this delay, address renewal fails for children with slower execution speed + #if defined (MESH_SLOW_ADDR_RESPONSE) + delay(MESH_SLOW_ADDR_RESPONSE); + #endif // defined (MESH_SLOW_ADDR_RESPONSE) if (header.from_node != MESH_DEFAULT_ADDRESS) { //Is NOT node 01 to 05 //delay(2); if (!network.write(header, &newAddress, sizeof(newAddress))) { diff --git a/RF24Mesh_config.h b/RF24Mesh_config.h index cf52d7e..b368686 100644 --- a/RF24Mesh_config.h +++ b/RF24Mesh_config.h @@ -20,6 +20,19 @@ #define MESH_NOMASTER #endif +#ifdef DOXYGEN_FORCED + /** + * @brief Adds a delay to node prior to transmitting NETWORK_ADDR_RESPONSE messages + * + * By default this is undefined for speed. This defined number of milliseconds is + * only applied to the master node when replying to a child trying to connect to the + * mesh network. + * @note It advisable to define this if any child node is running CircuitPython because + * the execution speed in pure python is inherently slower than it is in C++. + */ + #define MESH_SLOW_ADDR_RESPONSE 10 +#endif // defined DOXYGEN_FORCED + // un-comment for non-master nodes not running on ATTiny MCUs //#define MESH_NOMASTER diff --git a/pyRF24Mesh/setup.py b/pyRF24Mesh/setup.py index 4c9590c..48c2362 100755 --- a/pyRF24Mesh/setup.py +++ b/pyRF24Mesh/setup.py @@ -9,6 +9,8 @@ else: BOOST_LIB = "boost_python" +# NOTE can't access "../../LICENSE.inc" from working dir because +# it's relative. Brute force absolute path dynamically. git_dir = os.path.split(os.path.abspath(os.getcwd()))[0] # get LIB_VERSION from library.properties file for Arduino IDE From 95c75b151643f50675acbf7abcf64a6a38b2fe54 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Mon, 26 Jul 2021 14:42:43 -0700 Subject: [PATCH 45/72] move new mcro up 1 OSI lvl --- CMakeLists.txt | 4 ---- RF24Mesh.cpp | 6 +++--- RF24Mesh_config.h | 13 ------------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b8becf..006b046 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,10 +115,6 @@ if(DEFINED MESH_WRITE_TIMEOUT) message(STATUS "MESH_WRITE_TIMEOUT set to ${MESH_WRITE_TIMEOUT}") target_compile_definitions(${LibTargetName} PUBLIC MESH_WRITE_TIMEOUT=${MESH_WRITE_TIMEOUT}) endif() -if(DEFINED MESH_SLOW_ADDR_RESPONSE) - message(STATUS "MESH_SLOW_ADDR_RESPONSE set to ${MESH_SLOW_ADDR_RESPONSE}") - target_compile_definitions(${LibTargetName} PUBLIC MESH_SLOW_ADDR_RESPONSE=${MESH_SLOW_ADDR_RESPONSE}) -endif() # MESH_DEFAULT_ADDRESS is an alias of the RF24Network's NETWORK_DEFAULT_ADDRESS definition diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 747c012..58693b0 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -549,9 +549,9 @@ void RF24Mesh::DHCP() setAddress(header.reserved, newAddress); // without this delay, address renewal fails for children with slower execution speed - #if defined (MESH_SLOW_ADDR_RESPONSE) - delay(MESH_SLOW_ADDR_RESPONSE); - #endif // defined (MESH_SLOW_ADDR_RESPONSE) + #if defined (SLOW_ADDR_POLL_RESPONSE) + delay(SLOW_ADDR_POLL_RESPONSE); + #endif // defined (SLOW_ADDR_POLL_RESPONSE) if (header.from_node != MESH_DEFAULT_ADDRESS) { //Is NOT node 01 to 05 //delay(2); if (!network.write(header, &newAddress, sizeof(newAddress))) { diff --git a/RF24Mesh_config.h b/RF24Mesh_config.h index b368686..cf52d7e 100644 --- a/RF24Mesh_config.h +++ b/RF24Mesh_config.h @@ -20,19 +20,6 @@ #define MESH_NOMASTER #endif -#ifdef DOXYGEN_FORCED - /** - * @brief Adds a delay to node prior to transmitting NETWORK_ADDR_RESPONSE messages - * - * By default this is undefined for speed. This defined number of milliseconds is - * only applied to the master node when replying to a child trying to connect to the - * mesh network. - * @note It advisable to define this if any child node is running CircuitPython because - * the execution speed in pure python is inherently slower than it is in C++. - */ - #define MESH_SLOW_ADDR_RESPONSE 10 -#endif // defined DOXYGEN_FORCED - // un-comment for non-master nodes not running on ATTiny MCUs //#define MESH_NOMASTER From 4afe8c8059fbb52a46ae45a981b179fdd126e65c Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Mon, 26 Jul 2021 19:01:38 -0700 Subject: [PATCH 46/72] revert SLOW_ADDR_POLL_RESPONSE --- RF24Mesh.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 58693b0..9145bf6 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -548,10 +548,7 @@ void RF24Mesh::DHCP() //This is a routed request to 00 setAddress(header.reserved, newAddress); - // without this delay, address renewal fails for children with slower execution speed - #if defined (SLOW_ADDR_POLL_RESPONSE) - delay(SLOW_ADDR_POLL_RESPONSE); - #endif // defined (SLOW_ADDR_POLL_RESPONSE) + //delay(10); // ML: without this delay, address renewal fails if (header.from_node != MESH_DEFAULT_ADDRESS) { //Is NOT node 01 to 05 //delay(2); if (!network.write(header, &newAddress, sizeof(newAddress))) { From 3b69930bceed9ad1f7ed599b68807594c8f58115 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Tue, 27 Jul 2021 15:21:23 -0700 Subject: [PATCH 47/72] Revert "revert SLOW_ADDR_POLL_RESPONSE" This reverts commit 4afe8c8059fbb52a46ae45a981b179fdd126e65c. --- RF24Mesh.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 9145bf6..58693b0 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -548,7 +548,10 @@ void RF24Mesh::DHCP() //This is a routed request to 00 setAddress(header.reserved, newAddress); - //delay(10); // ML: without this delay, address renewal fails + // without this delay, address renewal fails for children with slower execution speed + #if defined (SLOW_ADDR_POLL_RESPONSE) + delay(SLOW_ADDR_POLL_RESPONSE); + #endif // defined (SLOW_ADDR_POLL_RESPONSE) if (header.from_node != MESH_DEFAULT_ADDRESS) { //Is NOT node 01 to 05 //delay(2); if (!network.write(header, &newAddress, sizeof(newAddress))) { From af43cb229a0895f16ba6644f858c2a7752815ba5 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Thu, 29 Jul 2021 21:56:25 -0700 Subject: [PATCH 48/72] address #195 --- RF24Mesh.cpp | 13 ------------- RF24Mesh.h | 1 - 2 files changed, 14 deletions(-) diff --git a/RF24Mesh.cpp b/RF24Mesh.cpp index 58693b0..77c3690 100644 --- a/RF24Mesh.cpp +++ b/RF24Mesh.cpp @@ -385,19 +385,6 @@ bool RF24Mesh::requestAddress(uint8_t level) return 1; } -/*****************************************************/ -/* -bool RF24Mesh::waitForAvailable(uint32_t timeout) { - - unsigned long timer = millis(); - while(millis()-timer < timeout) { - network.update(); - if (network.available()) { return 1; } - } - if (network.available()) { return 1; } - else{ return 0; } -} -*/ /*****************************************************/ void RF24Mesh::setNodeID(uint8_t nodeID) diff --git a/RF24Mesh.h b/RF24Mesh.h index 9117636..69c104e 100644 --- a/RF24Mesh.h +++ b/RF24Mesh.h @@ -292,7 +292,6 @@ class RF24Mesh void (*meshCallback)(void); bool requestAddress(uint8_t level); /** Actual requesting of the address once a contact node is discovered or supplied **/ - bool waitForAvailable(uint32_t timeout); /** Waits for data to become available */ #if !defined(MESH_NOMASTER) bool doDHCP; /** Indicator that an address request is available */ From 0160dd2fd0b9bdb9e65459cd9263f1256af1f756 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Wed, 11 Aug 2021 04:25:25 -0700 Subject: [PATCH 49/72] relative CMakeLists.txt --- CMakeLists.txt | 15 +++++++++------ cmake/Cache.cmake | 6 +++--- cmake/GetLibInfo.cmake | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 006b046..4f06b58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,11 @@ cmake_minimum_required(VERSION 3.15) # Set the project name to your project name project(RF24Mesh C CXX) -include(cmake/StandardProjectSettings.cmake) -include(cmake/PreventInSourceBuilds.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/cmake/StandardProjectSettings.cmake) +if(not "${CMAKE_CURRENT_LIST_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + # python wrapper builds in source, but not in submodules' source + include(${CMAKE_CURRENT_LIST_DIR}/cmake/PreventInSourceBuilds.cmake) +endif() # allow using CMake options to adjust RF24Network_config.h without modiying source code option(MESH_NOMASTER "exclude compiling code that is strictly for master nodes" OFF) @@ -40,17 +43,17 @@ endif() add_library(project_warnings INTERFACE) # enable cache system -include(cmake/Cache.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/cmake/Cache.cmake) # standard compiler warnings -include(cmake/CompilerWarnings.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/cmake/CompilerWarnings.cmake) set_project_warnings(project_warnings) # get library info from Arduino IDE's required library.properties file -include(cmake/GetLibInfo.cmake) # sets the variable LibTargetName +include(${CMAKE_CURRENT_LIST_DIR}/cmake/GetLibInfo.cmake) # sets the variable LibTargetName # setup CPack options -include(cmake/CPackInfo.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/cmake/CPackInfo.cmake) find_library(RF24 rf24 REQUIRED) message(STATUS "using RF24 library: ${RF24}") diff --git a/cmake/Cache.cmake b/cmake/Cache.cmake index 4cc2e8c..d02f4b5 100644 --- a/cmake/Cache.cmake +++ b/cmake/Cache.cmake @@ -6,7 +6,7 @@ endif() set(CACHE_OPTION "ccache" CACHE STRING "Compiler cache to be used" - ) +) set(CACHE_OPTION_VALUES "ccache" "sccache") set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) list( @@ -14,12 +14,12 @@ list( CACHE_OPTION_VALUES ${CACHE_OPTION} CACHE_OPTION_INDEX - ) +) if(${CACHE_OPTION_INDEX} EQUAL -1) message(STATUS "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}" - ) + ) endif() find_program(CACHE_BINARY ${CACHE_OPTION}) diff --git a/cmake/GetLibInfo.cmake b/cmake/GetLibInfo.cmake index 2613f18..1a9d9e4 100644 --- a/cmake/GetLibInfo.cmake +++ b/cmake/GetLibInfo.cmake @@ -1,5 +1,5 @@ # get lib info from the maintained library.properties required by the Arduino IDE -file(STRINGS ${CMAKE_SOURCE_DIR}/library.properties LibInfo) +file(STRINGS ${CMAKE_CURRENT_LIST_DIR}/library.properties LibInfo) foreach(line ${LibInfo}) string(FIND ${line} "=" label_delimiter) if(${label_delimiter} GREATER 0) From fc4898454d34005afcd3e0c286c027dce26f764b Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Wed, 11 Aug 2021 04:46:16 -0700 Subject: [PATCH 50/72] fix problem in last commit --- CMakeLists.txt | 2 +- cmake/GetLibInfo.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f06b58..b691708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ cmake_minimum_required(VERSION 3.15) # Set the project name to your project name project(RF24Mesh C CXX) include(${CMAKE_CURRENT_LIST_DIR}/cmake/StandardProjectSettings.cmake) -if(not "${CMAKE_CURRENT_LIST_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") +if(NOT "${CMAKE_CURRENT_LIST_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") # python wrapper builds in source, but not in submodules' source include(${CMAKE_CURRENT_LIST_DIR}/cmake/PreventInSourceBuilds.cmake) endif() diff --git a/cmake/GetLibInfo.cmake b/cmake/GetLibInfo.cmake index 1a9d9e4..ae0e40d 100644 --- a/cmake/GetLibInfo.cmake +++ b/cmake/GetLibInfo.cmake @@ -1,5 +1,5 @@ # get lib info from the maintained library.properties required by the Arduino IDE -file(STRINGS ${CMAKE_CURRENT_LIST_DIR}/library.properties LibInfo) +file(STRINGS ${CMAKE_CURRENT_LIST_DIR}/../library.properties LibInfo) foreach(line ${LibInfo}) string(FIND ${line} "=" label_delimiter) if(${label_delimiter} GREATER 0) From 951bbde8e0597931075b0c482b5dee4690049376 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Wed, 11 Aug 2021 15:10:15 -0700 Subject: [PATCH 51/72] [CMake] distinguish project_option/warnings --- CMakeLists.txt | 16 ++++++++-------- cmake/GetLibInfo.cmake | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b691708..c2b50a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,14 +19,17 @@ if(NOT "${CMAKE_CURRENT_LIST_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") include(${CMAKE_CURRENT_LIST_DIR}/cmake/PreventInSourceBuilds.cmake) endif() +# get library info from Arduino IDE's required library.properties file +include(${CMAKE_CURRENT_LIST_DIR}/cmake/GetLibInfo.cmake) # sets the variable LibTargetName + # allow using CMake options to adjust RF24Network_config.h without modiying source code option(MESH_NOMASTER "exclude compiling code that is strictly for master nodes" OFF) option(MESH_DEBUG "enable/disable debugging output" OFF) option(MESH_DEBUG_MINIMAL "enable/disable minimal debugging output" OFF) # Link this 'library' to set the c++ standard / compile-time options requested -add_library(project_options INTERFACE) -target_compile_features(project_options INTERFACE cxx_std_17) +add_library(${LibTargetName}_project_options INTERFACE) +target_compile_features(${LibTargetName}_project_options INTERFACE cxx_std_17) add_compile_options(-Ofast -Wall) # detect CPU and add compiler flags accordingly @@ -35,22 +38,19 @@ include(cmake/detectCPU.cmake) if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") option(ENABLE_BUILD_WITH_TIME_TRACE "Enable -ftime-trace to generate time tracing .json files on clang" OFF) if(ENABLE_BUILD_WITH_TIME_TRACE) - add_compile_definitions(project_options INTERFACE -ftime-trace) + add_compile_definitions(${LibTargetName}_project_options INTERFACE -ftime-trace) endif() endif() # Link this 'library' to use the warnings specified in CompilerWarnings.cmake -add_library(project_warnings INTERFACE) +add_library(${LibTargetName}_project_warnings INTERFACE) # enable cache system include(${CMAKE_CURRENT_LIST_DIR}/cmake/Cache.cmake) # standard compiler warnings include(${CMAKE_CURRENT_LIST_DIR}/cmake/CompilerWarnings.cmake) -set_project_warnings(project_warnings) - -# get library info from Arduino IDE's required library.properties file -include(${CMAKE_CURRENT_LIST_DIR}/cmake/GetLibInfo.cmake) # sets the variable LibTargetName +set_project_warnings(${LibTargetName}_project_warnings) # setup CPack options include(${CMAKE_CURRENT_LIST_DIR}/cmake/CPackInfo.cmake) diff --git a/cmake/GetLibInfo.cmake b/cmake/GetLibInfo.cmake index ae0e40d..3871395 100644 --- a/cmake/GetLibInfo.cmake +++ b/cmake/GetLibInfo.cmake @@ -26,7 +26,7 @@ foreach(line ${LibInfo}) endif() endforeach() -# convert the LibName to lower case & strip surrounding whitespace +# convert the LibName to lower case string(TOLOWER ${LibName} LibTargetName) #parse the version information into pieces. From f01e3bbb8db82fada688b9cfd1b5abd93d820c0e Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Wed, 11 Aug 2021 15:51:57 -0700 Subject: [PATCH 52/72] allow pybind11 to build in source --- .gitignore | 11 +++++++++++ CMakeLists.txt | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e2e32ed..2833b1f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,14 @@ build/ # ignore PlatformIO packages *.tar.gz + +# Cmake build-in-source generated stuff +CMakeCache.txt +CPackConfig.cmake +CPackSourceConfig.cmake +CMakeFiles +DEBIAN +cmake_install.cmake +compile_commands.json +# Makefile is modified when `cmake .` is run +# Makefile # preserve old/traditional build system diff --git a/CMakeLists.txt b/CMakeLists.txt index c2b50a0..92a6c6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,8 @@ cmake_minimum_required(VERSION 3.15) # Set the project name to your project name project(RF24Mesh C CXX) include(${CMAKE_CURRENT_LIST_DIR}/cmake/StandardProjectSettings.cmake) -if(NOT "${CMAKE_CURRENT_LIST_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") - # python wrapper builds in source, but not in submodules' source + +if(NOT pybind11_FOUND) # python wrapper builds in source include(${CMAKE_CURRENT_LIST_DIR}/cmake/PreventInSourceBuilds.cmake) endif() From 2ee51d37ea5b7047cfd7c7f5003192942a43903f Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Fri, 13 Aug 2021 03:31:41 -0700 Subject: [PATCH 53/72] add PicoSDK CI workflow --- .github/workflows/build_rp2xxx.yml | 112 ++++++++++++++++++++++ CMakeLists.txt | 4 +- README.md | 3 +- cmake/usePicoSDK.cmake | 4 +- examples_pico/CMakeLists.txt | 57 +++++++++++ examples_pico/RF24Mesh_Example.cpp | 80 ++++++++++++++++ examples_pico/RF24Mesh_Example_Master.cpp | 83 ++++++++++++++++ examples_pico/defaultPins.h | 29 ++++++ 8 files changed, 367 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/build_rp2xxx.yml create mode 100644 examples_pico/CMakeLists.txt create mode 100644 examples_pico/RF24Mesh_Example.cpp create mode 100644 examples_pico/RF24Mesh_Example_Master.cpp create mode 100644 examples_pico/defaultPins.h diff --git a/.github/workflows/build_rp2xxx.yml b/.github/workflows/build_rp2xxx.yml new file mode 100644 index 0000000..27378f0 --- /dev/null +++ b/.github/workflows/build_rp2xxx.yml @@ -0,0 +1,112 @@ +name: Pico SDK build + +on: + push: + paths: + - ".github/workflows/build_rp2xxx.yml" + - "*.h" + - "*.cpp" + - "CMakeLists.txt" + - "cmake/" + - "examples_pico/*" + pull_request: + types: [opened, reopened] + paths: + - ".github/workflows/build_rp2xxx.yml" + - "*.h" + - "*.cpp" + - "CMakeLists.txt" + - "cmake/**" + - "examples_pico/*" + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + board: + - "pico" + - "adafruit_feather_rp2040" + - "adafruit_itsybitsy_rp2040" + - "adafruit_qtpy_rp2040" + - "pimoroni_tiny2040" # examples require PicoSDK v1.2.0 + - "sparkfun_micromod" # examples require PicoSDK v1.2.0 + - "sparkfun_promicro" # examples require PicoSDK v1.2.0 + - "sparkfun_thingplus" # examples require PicoSDK v1.2.0 + # - "vgaboard" # examples require PicoSDK v1.2.0 (this can be enabled on request) + - "arduino_nano_rp2040_connect" # requires PicoSDK v1.2.0 + - "pimoroni_picolipo_4mb" # requires PicoSDK v1.2.0 + - "pimoroni_picolipo_16mb" # requires PicoSDK v1.2.0 + - "pimoroni_pga2040" # requires PicoSDK v1.2.0 + # - "pimoroni_keybow2040" # no SPI bus exposed + # - "pimoroni_picosystem" # SPI is reserved for LCD + + steps: + - name: checkout RF24Mesh lib + uses: actions/checkout@v2 + with: + path: RF24Mesh + + - name: checkout RF24Network lib + uses: actions/checkout@v2 + with: + repository: nRF24/RF24Network + path: RF24Network + ref: CMake-4-Linux + + - name: checkout RF24 lib + uses: actions/checkout@v2 + with: + repository: nRF24/RF24 + path: RF24 + ref: rp2xxx + + - name: Install toolchain + run: sudo apt update && sudo apt install gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential + + - name: Clone pico-sdk + uses: actions/checkout@v2 + with: + repository: raspberrypi/pico-sdk + # master branch is latest stable release + path: pico-sdk + clean: false + submodules: true + + - name: Checkout pico-sdk submodules + working-directory: ${{ github.workspace }}/pico-sdk + run: git submodule update --init + + - name: Create Build Environment + working-directory: ${{ github.workspace }}/RF24Mesh + env: + PICO_SDK_PATH: ${{ github.workspace }}/pico-sdk + run: cmake -E make_directory ${{ github.workspace }}/RF24Mesh/build + + - name: Configure CMake + working-directory: ${{ github.workspace }}/RF24Mesh/build + env: + PICO_SDK_PATH: ${{ github.workspace }}/pico-sdk + run: cmake ../examples_pico -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPICO_BOARD=${{ matrix.board }} + + - name: Build + working-directory: ${{ github.workspace }}/RF24Mesh/build + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE + + - name: Save artifact + uses: actions/upload-artifact@v2 + with: + name: examples_pico_${{ matrix.board }} + path: | + ${{ github.workspace }}/RF24Mesh/build/*.uf2 + ${{ github.workspace }}/RF24Mesh/build/*.elf + # ${{ github.workspace }}/RF24Mesh/build/*.hex + # ${{ github.workspace }}/RF24Mesh/build/*.bin diff --git a/CMakeLists.txt b/CMakeLists.txt index 92a6c6b..7fffd0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,8 +68,8 @@ add_library(${LibTargetName} SHARED RF24Mesh.cpp) target_include_directories(${LibTargetName} PUBLIC ${CMAKE_CURRENT_LIST_DIR}) target_link_libraries(${LibTargetName} INTERFACE - project_options - project_warnings + ${LibTargetName}_project_options + ${LibTargetName}_project_warnings SHARED ${RF24} SHARED ${RF24Network} ) diff --git a/README.md b/README.md index a2e8261..28987af 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ [![Linux build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_linux.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_linux.yml) [![Arduino CLI build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml) [![PlatformIO build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_platformIO.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_platformIO.yml) +[![Pico SDK build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_rp2xxx.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_rp2xxx.yml) RF24Mesh ======== Mesh Networking for RF24Network -https://nRF24.github.io/RF24Mesh \ No newline at end of file +https://nRF24.github.io/RF24Mesh diff --git a/cmake/usePicoSDK.cmake b/cmake/usePicoSDK.cmake index 9eeb9b1..bab2b49 100644 --- a/cmake/usePicoSDK.cmake +++ b/cmake/usePicoSDK.cmake @@ -10,9 +10,9 @@ set(CMAKE_CXX_STANDARD 17) add_library(RF24Mesh INTERFACE) target_sources(RF24Mesh INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/RF24Mesh.cpp + ${CMAKE_CURRENT_LIST_DIR}/../RF24Mesh.cpp ) target_include_directories(RF24Mesh INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/ + ${CMAKE_CURRENT_LIST_DIR}/../ ) diff --git a/examples_pico/CMakeLists.txt b/examples_pico/CMakeLists.txt new file mode 100644 index 0000000..903e16d --- /dev/null +++ b/examples_pico/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.12) + +#[[ + for these examples we are assuming the following structure: + path/to/github/repos/ + RF24/ + RF24Network/ + RF24Mesh/ + examples_pico/ <-- you are here + pico-sdk/ + + Be sure to set the PICO_SDK_PATH environment variable + `export PICO_SDK_PATH=/path/to/github/repos/pico-sdk/` +]] + +# Pull in SDK (must be before project) +include(../../RF24/cmake/pico_sdk_import.cmake) + +project(pico_examples C CXX ASM) + +# Initialize the Pico SDK +pico_sdk_init() + +# In YOUR project, include RF24's CMakeLists.txt +# giving the path depending on where the library +# is cloned to in your project +include(../../RF24/CMakeLists.txt) # for RF24 lib +include(../../RF24Network/CMakeLists.txt) # for RF24Network lib +include(../CMakeLists.txt) # for RF24Mesh lib + +# iterate over a list of examples by filename +set(EXAMPLES_LIST + RF24Mesh_Example + RF24Mesh_Example_Master +) + +foreach(example ${EXAMPLES_LIST}) + # make a target + add_executable(${example} ${example}.cpp defaultPins.h) + + # link the necessary libs to the target + target_link_libraries(${example} PUBLIC + RF24 + RF24Network + RF24Mesh + pico_stdlib + hardware_spi + hardware_gpio + ) + + # specify USB port as default serial communication's interface (not UART RX/TX pins) + pico_enable_stdio_usb(${example} 1) + pico_enable_stdio_uart(${example} 0) + + # create map/bin/hex file etc. + pico_add_extra_outputs(${example}) +endforeach() diff --git a/examples_pico/RF24Mesh_Example.cpp b/examples_pico/RF24Mesh_Example.cpp new file mode 100644 index 0000000..f1c3254 --- /dev/null +++ b/examples_pico/RF24Mesh_Example.cpp @@ -0,0 +1,80 @@ +/** RF24Mesh_Example.cpp by TMRh20 + * + * Note: This sketch only functions on -RPi- + * + * This example sketch shows how to manually configure a node via RF24Mesh, and send data to the + * master node. + * In this sketch, the nodes will refresh their network address as soon as a single write fails. This allows the + * nodes to change position in relation to each other and the master node. + * + */ +#include "pico/stdlib.h" // printf(), sleep_ms(), getchar_timeout_us(), to_us_since_boot(), get_absolute_time() +#include "pico/bootrom.h" // reset_usb_boot() +#include // tud_cdc_connected() +#include // RF24 radio object +#include // RF24Network network object +#include // RF24Mesh mesh object +#include "defaultPins.h" // board presumptive default pin numbers for CE_PIN and CSN_PIN + +// instantiate an object for the nRF24L01 transceiver +RF24 radio(CE_PIN, CSN_PIN); + +RF24Network network(radio); + +RF24Mesh mesh(radio, network); + +uint32_t displayTimer = 0; + +bool setup() +{ + // Set the nodeID to 0 for the master node + mesh.setNodeID(4); + + // Connect to the mesh + printf("start nodeID %d\n", mesh.getNodeID()); + if (!mesh.begin()) + { + printf("Radio hardware not responding or could not connect to network.\n"); + return 0; + } + return true; +} + +void loop() +{ + // Call mesh.update to keep the network updated + mesh.update(); + + // Send the current millis() to the master node every second + if (millis() - displayTimer >= 1000) { + displayTimer = millis(); + + if (!mesh.write(&displayTimer, 'M', sizeof(displayTimer))) { + // If a write fails, check connectivity to the mesh network + if (!mesh.checkConnection()) { + // The address could be refreshed per a specified timeframe or only when sequential writes fail, etc. + printf("Renewing Address\n"); + mesh.renewAddress(); + } + else { + printf("Send fail, Test OK\n"); + } + } + else { + printf("Send OK: %u\n", displayTimer); + } + } +} + +int main() +{ + stdio_init_all(); // init necessary IO for the RP2040 + + while (!setup()) { // if radio.begin() failed + // hold program in infinite attempts to initialize radio + } + while (true) { + loop(); + } + return 0; // we will never reach this +} diff --git a/examples_pico/RF24Mesh_Example_Master.cpp b/examples_pico/RF24Mesh_Example_Master.cpp new file mode 100644 index 0000000..ebab136 --- /dev/null +++ b/examples_pico/RF24Mesh_Example_Master.cpp @@ -0,0 +1,83 @@ +/** + * This example sketch shows how to manually configure a node via RF24Mesh as a master node, which + * will receive all data from sensor nodes. + * + * The nodes can change physical or logical position in the network, and reconnect through different + * routing nodes as required. The master node manages the address assignments for the individual nodes + * in a manner similar to DHCP. + */ +#include "pico/stdlib.h" // printf(), sleep_ms(), getchar_timeout_us(), to_us_since_boot(), get_absolute_time() +#include "pico/bootrom.h" // reset_usb_boot() +#include // tud_cdc_connected() +#include // RF24 radio object +#include // RF24Network network object +#include // RF24Mesh mesh object +#include "defaultPins.h" // board presumptive default pin numbers for CE_PIN and CSN_PIN + +// instantiate an object for the nRF24L01 transceiver +RF24 radio(CE_PIN, CSN_PIN); + +RF24Network network(radio); + +RF24Mesh mesh(radio, network); + +bool setup() +{ + // Set the nodeID to 0 for the master node + mesh.setNodeID(0); + + // Connect to the mesh + printf("start\n"); + if (!mesh.begin()) { + printf("Radio hardware not responding or could not connect to network.\n"); + return 0; + } + + radio.printDetails(); + return true; +} + + +void loop() +{ + // Call network.update as usual to keep the network updated + mesh.update(); + + // In addition, keep the 'DHCP service' running on the master node so addresses will + // be assigned to the sensor nodes + mesh.DHCP(); + + // Check for incoming data from the sensors + while (network.available()) { + // printf("rcv\n"); + RF24NetworkHeader header; + network.peek(header); + + uint32_t dat = 0; + switch (header.type) + { // Display the incoming millis() values from the sensor nodes + case 'M': + network.read(header, &dat, sizeof(dat)); + printf("Rcv %u from 0%o\n", dat, header.from_node); + break; + default: + network.read(header, 0, 0); + printf("Rcv bad type %d from 0%o\n", header.type, header.from_node); + break; + } + } +} + + +int main() +{ + stdio_init_all(); // init necessary IO for the RP2040 + + while (!setup()) { // if radio.begin() failed + // hold program in infinite attempts to initialize radio + } + while (true) { + loop(); + } + return 0; // we will never reach this +} diff --git a/examples_pico/defaultPins.h b/examples_pico/defaultPins.h new file mode 100644 index 0000000..e6819ca --- /dev/null +++ b/examples_pico/defaultPins.h @@ -0,0 +1,29 @@ +// pre-chossen pins for different boards +#ifndef DEFAULTPINS_H +#define DEFAULTPINS_H + +#if defined (ADAFRUIT_QTPY_RP2040) +// for this board, you can still use the Stemma QT connector as a separate I2C bus (`i2c1`) +#define CE_PIN PICO_DEFAULT_I2C_SDA_PIN // the pin labeled SDA +#define CSN_PIN PICO_DEFAULT_I2C_SCL_PIN // the pin labeled SCL + +#elif defined (PIMORONI_TINY2040) +// default SPI_SCK_PIN = 6 +// default SPI_TX_PIN = 7 +// default SPI_RX_PIN = 4 +#define CE_PIN PICO_DEFAULT_I2C_SCL_PIN // pin 3 +#define CSN_PIN PICO_DEFAULT_SPI_CSN_PIN // pin 5 + + +#elif defined (SPARFUN_THINGPLUS) +#define CSN_PIN 16 // the pin labeled 16 +#define CE_PIN 7 // the pin labeled SCL + +#else +// pins available on (ADAFRUIT_ITSYBITSY_RP2040 || ADAFRUIT_FEATHER_RP2040 || Pico_board || Sparkfun_ProMicro || SparkFun MicroMod) + +#define CE_PIN 7 +#define CSN_PIN 8 +#endif // board detection macro defs + +#endif // DEFAULTPINS_H From c88e79dcb4550f8d8e8a4a0bcccccce955b24a73 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Fri, 13 Aug 2021 16:35:33 -0700 Subject: [PATCH 54/72] introduce undefined macro USE_RF24_LIB_SRC --- RF24Mesh.h | 2 +- RF24Mesh_config.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RF24Mesh.h b/RF24Mesh.h index 69c104e..0a68d54 100644 --- a/RF24Mesh.h +++ b/RF24Mesh.h @@ -34,7 +34,7 @@ #include "RF24Mesh_config.h" -#if defined(__linux) && !defined(__ARDUINO_X86__) +#if defined(__linux) && !defined(__ARDUINO_X86__) && !defined(USE_RF24_LIB_SRC) #include #include #define RF24_LINUX diff --git a/RF24Mesh_config.h b/RF24Mesh_config.h index cf52d7e..193141b 100644 --- a/RF24Mesh_config.h +++ b/RF24Mesh_config.h @@ -88,7 +88,7 @@ //#define MESH_MAX_ADDRESSES 255 /* UNUSED Determines the max size of the array used for storing addresses on the Master Node */ //#define MESH_ADDRESS_HOLD_TIME 30000 /* UNUSED How long before a released address becomes available */ -#if (defined (__linux) || defined (linux)) && !defined (__ARDUINO_X86__) +#if (defined (__linux) || defined (linux)) && !defined (__ARDUINO_X86__) && !defined (USE_RF24_LIB_SRC) #include //ATXMega From 0e9af6790e4bb96d982eaf4b31ac215626689cfc Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sat, 14 Aug 2021 22:39:59 -0700 Subject: [PATCH 55/72] further the utility of USE_RF24_LIB_SRC macro --- CMakeLists.txt | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7fffd0b..d349e5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,7 @@ cmake_minimum_required(VERSION 3.15) # Set the project name to your project name project(RF24Mesh C CXX) include(${CMAKE_CURRENT_LIST_DIR}/cmake/StandardProjectSettings.cmake) - -if(NOT pybind11_FOUND) # python wrapper builds in source - include(${CMAKE_CURRENT_LIST_DIR}/cmake/PreventInSourceBuilds.cmake) -endif() +include(${CMAKE_CURRENT_LIST_DIR}/cmake/PreventInSourceBuilds.cmake) # get library info from Arduino IDE's required library.properties file include(${CMAKE_CURRENT_LIST_DIR}/cmake/GetLibInfo.cmake) # sets the variable LibTargetName @@ -70,10 +67,32 @@ target_include_directories(${LibTargetName} PUBLIC ${CMAKE_CURRENT_LIST_DIR}) target_link_libraries(${LibTargetName} INTERFACE ${LibTargetName}_project_options ${LibTargetName}_project_warnings - SHARED ${RF24} - SHARED ${RF24Network} ) +# python wrapper builds from source +if(DEFINED USE_RF24_LIB_SRC OR pybind11_FOUND OR SKBUILD) + message(STATUS "Building lib from RF24 source") + target_compile_definitions(${LibTargetName} PUBLIC USE_RF24_LIB_SRC) + target_link_libraries(${LibTargetName} INTERFACE rf24 rf24network) + + if(NOT DEFINED RF24_LIB_PATH) + target_include_directories(${LibTargetName} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../RF24) + else() + target_include_directories(${LibTargetName} PUBLIC ${RF24_LIB_PATH}) + endif() + + if(NOT DEFINED RF24NETWORK_LIB_PATH) + target_include_directories(${LibTargetName} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../RF24) + else() + target_include_directories(${LibTargetName} PUBLIC ${RF24NETWORK_LIB_PATH}) + endif() +else() + target_link_libraries(${LibTargetName} + SHARED ${RF24} + SHARED ${RF24Network} + ) +endif() + set_target_properties( ${LibTargetName} PROPERTIES From 42cd43012ee9f11a703f916e9faabe63a15c787d Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sat, 14 Aug 2021 22:59:45 -0700 Subject: [PATCH 56/72] [CMakeLists.txt] fix last commit --- CMakeLists.txt | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d349e5c..a241604 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,12 +51,13 @@ set_project_warnings(${LibTargetName}_project_warnings) # setup CPack options include(${CMAKE_CURRENT_LIST_DIR}/cmake/CPackInfo.cmake) +if(NOT DEFINED USE_RF24_LIB_SRC) + find_library(RF24 rf24 REQUIRED) + message(STATUS "using RF24 library: ${RF24}") -find_library(RF24 rf24 REQUIRED) -message(STATUS "using RF24 library: ${RF24}") - -find_library(RF24Network rf24network REQUIRED) -message(STATUS "using RF24Network library: ${RF24Network}") + find_library(RF24Network rf24network REQUIRED) + message(STATUS "using RF24Network library: ${RF24Network}") +endif() ########################### # create target for bulding the RF24Log lib @@ -64,16 +65,10 @@ message(STATUS "using RF24Network library: ${RF24Network}") add_library(${LibTargetName} SHARED RF24Mesh.cpp) target_include_directories(${LibTargetName} PUBLIC ${CMAKE_CURRENT_LIST_DIR}) -target_link_libraries(${LibTargetName} INTERFACE - ${LibTargetName}_project_options - ${LibTargetName}_project_warnings -) - # python wrapper builds from source if(DEFINED USE_RF24_LIB_SRC OR pybind11_FOUND OR SKBUILD) message(STATUS "Building lib from RF24 source") target_compile_definitions(${LibTargetName} PUBLIC USE_RF24_LIB_SRC) - target_link_libraries(${LibTargetName} INTERFACE rf24 rf24network) if(NOT DEFINED RF24_LIB_PATH) target_include_directories(${LibTargetName} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../RF24) @@ -86,8 +81,16 @@ if(DEFINED USE_RF24_LIB_SRC OR pybind11_FOUND OR SKBUILD) else() target_include_directories(${LibTargetName} PUBLIC ${RF24NETWORK_LIB_PATH}) endif() + + target_link_libraries(${LibTargetName} + INTERFACE ${LibTargetName}_project_options + INTERFACE ${LibTargetName}_project_warnings + ) else() + target_link_libraries(${LibTargetName} + INTERFACE ${LibTargetName}_project_options + INTERFACE ${LibTargetName}_project_warnings SHARED ${RF24} SHARED ${RF24Network} ) From 51e9e8bdb32a817e2cc2a2b47c1a7d8772546522 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 22 Aug 2021 20:14:47 -0700 Subject: [PATCH 57/72] auto-publish new releases to PIO --- .github/workflows/build_platformIO.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml index 78699f5..cd82546 100644 --- a/.github/workflows/build_platformIO.yml +++ b/.github/workflows/build_platformIO.yml @@ -54,6 +54,13 @@ jobs: pattern: "PlatformIO*.tar.gz" github-token: ${{ secrets.GITHUB_TOKEN }} + - name: upload package to PlatformIO Registry + if: github.event_name == 'release' && github.event_type != 'edited' + # PIO lib packages cannot be re-published under the same tag + env: + PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }} + run: pio package publish --owner nrf24 + check_formatting: runs-on: ubuntu-latest From 91198dbc2a2128167d1eb52d86efeb134cacbd81 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sat, 25 Sep 2021 00:45:41 -0700 Subject: [PATCH 58/72] use --non-interactive option in PIO CI --- .github/workflows/build_platformIO.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml index cd82546..016a34a 100644 --- a/.github/workflows/build_platformIO.yml +++ b/.github/workflows/build_platformIO.yml @@ -59,7 +59,7 @@ jobs: # PIO lib packages cannot be re-published under the same tag env: PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }} - run: pio package publish --owner nrf24 + run: pio package publish --owner nrf24 --non-interactive check_formatting: runs-on: ubuntu-latest From f19b503bbc3b7c4191ca755fc2b0110673dec401 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 24 Oct 2021 06:42:34 -0700 Subject: [PATCH 59/72] [linux CI] use RF24 master branch --- .github/workflows/build_linux.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 23d6621..7889613 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -82,7 +82,6 @@ jobs: uses: actions/checkout@v2 with: repository: nRF24/RF24 - ref: rp2xxx - name: build & install RF24 run: | From a20bf32067b589902fbd8163c5602e525d2b7383 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 24 Oct 2021 07:19:16 -0700 Subject: [PATCH 60/72] review pico examples --- examples_pico/RF24Mesh_Example.cpp | 10 ++++------ examples_pico/RF24Mesh_Example_Master.cpp | 16 +++++++--------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/examples_pico/RF24Mesh_Example.cpp b/examples_pico/RF24Mesh_Example.cpp index f1c3254..37998f9 100644 --- a/examples_pico/RF24Mesh_Example.cpp +++ b/examples_pico/RF24Mesh_Example.cpp @@ -1,6 +1,6 @@ /** RF24Mesh_Example.cpp by TMRh20 * - * Note: This sketch only functions on -RPi- + * Note: This sketch only functions on RP2040 boards * * This example sketch shows how to manually configure a node via RF24Mesh, and send data to the * master node. @@ -8,9 +8,7 @@ * nodes to change position in relation to each other and the master node. * */ -#include "pico/stdlib.h" // printf(), sleep_ms(), getchar_timeout_us(), to_us_since_boot(), get_absolute_time() -#include "pico/bootrom.h" // reset_usb_boot() -#include // tud_cdc_connected() +#include "pico/stdlib.h" // printf(), to_us_since_boot(), get_absolute_time() #include // RF24 radio object #include // RF24Network network object #include // RF24Mesh mesh object @@ -46,8 +44,8 @@ void loop() mesh.update(); // Send the current millis() to the master node every second - if (millis() - displayTimer >= 1000) { - displayTimer = millis(); + if (to_us_since_boot(get_absolute_time()) - displayTimer >= 1000) { + displayTimer = to_us_since_boot(get_absolute_time()); if (!mesh.write(&displayTimer, 'M', sizeof(displayTimer))) { // If a write fails, check connectivity to the mesh network diff --git a/examples_pico/RF24Mesh_Example_Master.cpp b/examples_pico/RF24Mesh_Example_Master.cpp index ebab136..fdc9e2b 100644 --- a/examples_pico/RF24Mesh_Example_Master.cpp +++ b/examples_pico/RF24Mesh_Example_Master.cpp @@ -6,9 +6,7 @@ * routing nodes as required. The master node manages the address assignments for the individual nodes * in a manner similar to DHCP. */ -#include "pico/stdlib.h" // printf(), sleep_ms(), getchar_timeout_us(), to_us_since_boot(), get_absolute_time() -#include "pico/bootrom.h" // reset_usb_boot() -#include // tud_cdc_connected() +#include "pico/stdlib.h" // printf(), to_us_since_boot(), get_absolute_time() #include // RF24 radio object #include // RF24Network network object #include // RF24Mesh mesh object @@ -57,13 +55,13 @@ void loop() switch (header.type) { // Display the incoming millis() values from the sensor nodes case 'M': - network.read(header, &dat, sizeof(dat)); - printf("Rcv %u from 0%o\n", dat, header.from_node); - break; + network.read(header, &dat, sizeof(dat)); + printf("Rcv %u from 0%o\n", dat, header.from_node); + break; default: - network.read(header, 0, 0); - printf("Rcv bad type %d from 0%o\n", header.type, header.from_node); - break; + network.read(header, 0, 0); + printf("Rcv bad type %d from 0%o\n", header.type, header.from_node); + break; } } } From e38cc31628cd54e541dd56be153dd525d5973ac4 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 24 Oct 2021 07:22:28 -0700 Subject: [PATCH 61/72] pico examples should wait until Serial is active --- examples_pico/RF24Mesh_Example.cpp | 8 +++++++- examples_pico/RF24Mesh_Example_Master.cpp | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/examples_pico/RF24Mesh_Example.cpp b/examples_pico/RF24Mesh_Example.cpp index 37998f9..bbb008d 100644 --- a/examples_pico/RF24Mesh_Example.cpp +++ b/examples_pico/RF24Mesh_Example.cpp @@ -8,7 +8,8 @@ * nodes to change position in relation to each other and the master node. * */ -#include "pico/stdlib.h" // printf(), to_us_since_boot(), get_absolute_time() +#include "pico/stdlib.h" // printf(), sleep_ms(), to_us_since_boot(), get_absolute_time() +#include // tud_cdc_connected() #include // RF24 radio object #include // RF24Network network object #include // RF24Mesh mesh object @@ -25,6 +26,11 @@ uint32_t displayTimer = 0; bool setup() { + // wait here until the CDC ACM (serial port emulation) is connected + while (!tud_cdc_connected()) { + sleep_ms(10); + } + // Set the nodeID to 0 for the master node mesh.setNodeID(4); diff --git a/examples_pico/RF24Mesh_Example_Master.cpp b/examples_pico/RF24Mesh_Example_Master.cpp index fdc9e2b..5804144 100644 --- a/examples_pico/RF24Mesh_Example_Master.cpp +++ b/examples_pico/RF24Mesh_Example_Master.cpp @@ -6,7 +6,8 @@ * routing nodes as required. The master node manages the address assignments for the individual nodes * in a manner similar to DHCP. */ -#include "pico/stdlib.h" // printf(), to_us_since_boot(), get_absolute_time() +#include "pico/stdlib.h" // printf(), sleep_ms(), to_us_since_boot(), get_absolute_time() +#include // tud_cdc_connected() #include // RF24 radio object #include // RF24Network network object #include // RF24Mesh mesh object @@ -21,6 +22,11 @@ RF24Mesh mesh(radio, network); bool setup() { + // wait here until the CDC ACM (serial port emulation) is connected + while (!tud_cdc_connected()) { + sleep_ms(10); + } + // Set the nodeID to 0 for the master node mesh.setNodeID(0); From 4db1ab47774bf31c3df9f69210fbff982c8d9eb6 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 24 Oct 2021 10:13:19 -0700 Subject: [PATCH 62/72] [pico examples] use milliseconds not microseconds --- examples_pico/RF24Mesh_Example.cpp | 6 +++--- examples_pico/RF24Mesh_Example_Master.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples_pico/RF24Mesh_Example.cpp b/examples_pico/RF24Mesh_Example.cpp index bbb008d..f205596 100644 --- a/examples_pico/RF24Mesh_Example.cpp +++ b/examples_pico/RF24Mesh_Example.cpp @@ -8,7 +8,7 @@ * nodes to change position in relation to each other and the master node. * */ -#include "pico/stdlib.h" // printf(), sleep_ms(), to_us_since_boot(), get_absolute_time() +#include "pico/stdlib.h" // printf(), sleep_ms(), to_ms_since_boot(), get_absolute_time() #include // tud_cdc_connected() #include // RF24 radio object #include // RF24Network network object @@ -50,8 +50,8 @@ void loop() mesh.update(); // Send the current millis() to the master node every second - if (to_us_since_boot(get_absolute_time()) - displayTimer >= 1000) { - displayTimer = to_us_since_boot(get_absolute_time()); + if (to_ms_since_boot(get_absolute_time()) - displayTimer >= 1000) { + displayTimer = to_ms_since_boot(get_absolute_time()); if (!mesh.write(&displayTimer, 'M', sizeof(displayTimer))) { // If a write fails, check connectivity to the mesh network diff --git a/examples_pico/RF24Mesh_Example_Master.cpp b/examples_pico/RF24Mesh_Example_Master.cpp index 5804144..b337407 100644 --- a/examples_pico/RF24Mesh_Example_Master.cpp +++ b/examples_pico/RF24Mesh_Example_Master.cpp @@ -6,7 +6,7 @@ * routing nodes as required. The master node manages the address assignments for the individual nodes * in a manner similar to DHCP. */ -#include "pico/stdlib.h" // printf(), sleep_ms(), to_us_since_boot(), get_absolute_time() +#include "pico/stdlib.h" // printf(), sleep_ms(), to_ms_since_boot(), get_absolute_time() #include // tud_cdc_connected() #include // RF24 radio object #include // RF24Network network object From 0981279491889aa414e5387924fa679347663f98 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 31 Oct 2021 07:35:32 -0700 Subject: [PATCH 63/72] ready for docs hosting on RTD --- .github/workflows/doxygen.yml | 60 ++++--- .gitignore | 3 +- .readthedocs.yaml | 24 +++ Doxyfile | 8 +- RF24Mesh.h | 144 ++++++++++------- docs/general_usage.md | 79 +++++---- docs/main_page.md | 44 +++-- docs/setup_config.md | 21 ++- docs/sphinx/Makefile | 20 +++ docs/sphinx/README.md | 3 + docs/sphinx/RF24Mesh_8h.rst | 5 + docs/sphinx/RF24Mesh__config_8h.rst | 4 + docs/sphinx/_static/Logo large.png | Bin 0 -> 94683 bytes docs/sphinx/_static/custom_material.css | 76 +++++++++ docs/sphinx/_static/new_favicon.ico | Bin 0 -> 4286 bytes docs/sphinx/classRF24Mesh.rst | 62 +++++++ docs/sphinx/conf.py | 152 ++++++++++++++++++ docs/sphinx/deprecated.rst | 5 + docs/sphinx/examples.rst | 44 +++++ .../examples/Arduino/RF24Mesh_Example.rst | 5 + .../Arduino/RF24Mesh_Example_Master.rst | 5 + .../Arduino/RF24Mesh_Example_Node2Node.rst | 5 + .../RF24Mesh_Example_Node2NodeExtra.rst | 5 + .../Arduino/RF24Mesh_SerialConfig.rst | 5 + .../examples/Linux/RF24Mesh_Example.rst | 6 + .../Linux/RF24Mesh_Example_Master.rst | 6 + .../Linux/RF24Mesh_Ncurses_Master.rst | 10 ++ .../examples/PicoSDK/RF24Mesh_Example.rst | 8 + .../PicoSDK/RF24Mesh_Example_Master.rst | 8 + docs/sphinx/examples/PicoSDK/default_pins.rst | 5 + .../examples/Python/RF24Mesh_Example.rst | 7 + .../Python/RF24Mesh_Example_Master.rst | 7 + docs/sphinx/index.rst | 37 +++++ docs/sphinx/make.bat | 35 ++++ docs/sphinx/md_contributing.rst | 5 + docs/sphinx/md_docs_general_usage.rst | 5 + docs/sphinx/md_docs_setup_config.rst | 5 + docs/sphinx/pages.rst | 9 ++ docs/sphinx/requirements.txt | 2 + 39 files changed, 808 insertions(+), 126 deletions(-) create mode 100644 .readthedocs.yaml create mode 100644 docs/sphinx/Makefile create mode 100644 docs/sphinx/README.md create mode 100644 docs/sphinx/RF24Mesh_8h.rst create mode 100644 docs/sphinx/RF24Mesh__config_8h.rst create mode 100644 docs/sphinx/_static/Logo large.png create mode 100644 docs/sphinx/_static/custom_material.css create mode 100644 docs/sphinx/_static/new_favicon.ico create mode 100644 docs/sphinx/classRF24Mesh.rst create mode 100644 docs/sphinx/conf.py create mode 100644 docs/sphinx/deprecated.rst create mode 100644 docs/sphinx/examples.rst create mode 100644 docs/sphinx/examples/Arduino/RF24Mesh_Example.rst create mode 100644 docs/sphinx/examples/Arduino/RF24Mesh_Example_Master.rst create mode 100644 docs/sphinx/examples/Arduino/RF24Mesh_Example_Node2Node.rst create mode 100644 docs/sphinx/examples/Arduino/RF24Mesh_Example_Node2NodeExtra.rst create mode 100644 docs/sphinx/examples/Arduino/RF24Mesh_SerialConfig.rst create mode 100644 docs/sphinx/examples/Linux/RF24Mesh_Example.rst create mode 100644 docs/sphinx/examples/Linux/RF24Mesh_Example_Master.rst create mode 100644 docs/sphinx/examples/Linux/RF24Mesh_Ncurses_Master.rst create mode 100644 docs/sphinx/examples/PicoSDK/RF24Mesh_Example.rst create mode 100644 docs/sphinx/examples/PicoSDK/RF24Mesh_Example_Master.rst create mode 100644 docs/sphinx/examples/PicoSDK/default_pins.rst create mode 100644 docs/sphinx/examples/Python/RF24Mesh_Example.rst create mode 100644 docs/sphinx/examples/Python/RF24Mesh_Example_Master.rst create mode 100644 docs/sphinx/index.rst create mode 100644 docs/sphinx/make.bat create mode 100644 docs/sphinx/md_contributing.rst create mode 100644 docs/sphinx/md_docs_general_usage.rst create mode 100644 docs/sphinx/md_docs_setup_config.rst create mode 100644 docs/sphinx/pages.rst create mode 100644 docs/sphinx/requirements.txt diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index cef8692..743e980 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -1,4 +1,4 @@ -name: DoxyGen build +name: build Docs on: pull_request: @@ -35,23 +35,41 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: get latest release version number - id: latest_ver - run: echo "::set-output name=release::$(awk -F "=" '/version/ {print $2}' library.properties)" - - name: overwrite doxygen tags - run: | - touch doxygenAction - echo "PROJECT_NUMBER = ${{ steps.latest_ver.outputs.release }}" >> doxygenAction - echo "@INCLUDE = doxygenAction" >> Doxyfile - - name: build doxygen - uses: mattnotmitt/doxygen-action@v1 - with: - working-directory: '.' - doxyfile-path: './Doxyfile' - - name: upload to github pages - if: ${{ github.event_name == 'release'}} - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/html \ No newline at end of file + - uses: actions/checkout@v2 + - name: get latest release version number + id: latest_ver + run: echo "::set-output name=release::$(awk -F "=" '/version/ {print $2}' library.properties)" + - name: overwrite doxygen tags + run: | + touch doxygenAction + echo "PROJECT_NUMBER = ${{ steps.latest_ver.outputs.release }}" >> doxygenAction + echo "@INCLUDE = doxygenAction" >> Doxyfile + - name: build doxygen + uses: mattnotmitt/doxygen-action@v1 + with: + working-directory: '.' + doxyfile-path: './Doxyfile' + - name: Save doxygen docs as artifact + uses: actions/upload-artifact@v2 + with: + name: "RF24Mesh_doxygen_docs" + path: ${{ github.workspace }}/docs/html + - name: upload to github pages + if: ${{ github.event_name == 'release'}} + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/html + + # build pretty docs using doxygen XML output with Sphinx + - uses: actions/setup-python@v2 + - name: Install sphinx deps + run: python -m pip install -r docs/sphinx/requirements.txt + - name: build docs with Sphinx + working-directory: docs + run: sphinx-build sphinx _build + - name: Save sphinx docs as artifact + uses: actions/upload-artifact@v2 + with: + name: "RF24Mesh_sphinx_docs" + path: ${{ github.workspace }}/docs/_build diff --git a/.gitignore b/.gitignore index 2833b1f..4cff0cb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,8 @@ # ignore docs folder docs/html/ -docs/xml/ +docs/sphinx/xml/ +docs/_build # ignore CMake stuff build/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..c843775 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,24 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +build: + os: "ubuntu-20.04" + tools: + python: "3" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/sphinx/conf.py + +# Optionally build your docs in additional formats such as PDF +formats: + - pdf + +# install Python requirements required to build docs +python: + install: + - requirements: docs/sphinx/requirements.txt diff --git a/Doxyfile b/Doxyfile index 91d12cf..be6e778 100644 --- a/Doxyfile +++ b/Doxyfile @@ -2095,7 +2095,7 @@ MAN_LINKS = NO # captures the structure of the code including all documentation. # The default value is: NO. -GENERATE_XML = NO +GENERATE_XML = YES # The XML_OUTPUT tag is used to specify where the XML pages will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of @@ -2103,7 +2103,7 @@ GENERATE_XML = NO # The default directory is: xml. # This tag requires that the tag GENERATE_XML is set to YES. -XML_OUTPUT = xml +XML_OUTPUT = sphinx/xml # If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program # listings (including syntax highlighting and cross-referencing information) to @@ -2313,14 +2313,14 @@ ALLEXTERNALS = NO # listed. # The default value is: YES. -EXTERNAL_GROUPS = YES +EXTERNAL_GROUPS = NO # If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in # the related pages index. If set to NO, only the current project's pages will # be listed. # The default value is: YES. -EXTERNAL_PAGES = YES +EXTERNAL_PAGES = NO #--------------------------------------------------------------------------- # Configuration options related to the dot tool diff --git a/RF24Mesh.h b/RF24Mesh.h index 0a68d54..66507fb 100644 --- a/RF24Mesh.h +++ b/RF24Mesh.h @@ -78,9 +78,9 @@ class RF24Mesh * This may take a few moments to complete. * * The following parameters are optional: - * @param channel The radio channel (1-127) default:97 - * @param data_rate The data rate (RF24_250KBPS,RF24_1MBPS,RF24_2MBPS) default:RF24_1MBPS - * @param timeout How long to attempt address renewal in milliseconds default:7500 + * @param channel The radio channel (0 - 125). Default is 97. + * @param data_rate The data rate (RF24_250KBPS, RF24_1MBPS, RF24_2MBPS). Default is RF24_1MBPS. + * @param timeout How long to attempt address renewal in milliseconds. Default is 7500. */ bool begin(uint8_t channel = MESH_DEFAULT_CHANNEL, rf24_datarate_e data_rate = RF24_1MBPS, uint32_t timeout = MESH_RENEWAL_TIMEOUT); @@ -91,47 +91,52 @@ class RF24Mesh uint8_t update(); /** - * Automatically construct a header and send a payload + * Automatically construct a header and send a payload. * Very similar to the standard network.write() function, which can be used directly. * - * @note Including the nodeID parameter will result in an automatic address lookup being performed. - * @note Message types 1-64 (decimal) will NOT be acknowledged by the network, types 65-127 will be. Use as appropriate to manage traffic: + * @note Including the @ref _nodeID "nodeID" parameter will result in an automatic address lookup being performed. + * @note Message types 1 - 64 (decimal) will NOT be acknowledged by the network, types 65 - 127 will be. Use as appropriate to manage traffic: * if expecting a response, no ack is needed. * - * @param data Send any type of data of any length (Max length determined by RF24Network layer) - * @param msg_type The user-defined (1-127) message header_type to send. Used to distinguish between different types of data being transmitted. + * @param data Send any type of data of any length (maximum length determined by RF24Network layer). + * @param msg_type The user-defined (1 - 127) message header_type to send. Used to distinguish between different types of data being transmitted. * @param size The size of the data being sent - * @param nodeID **Optional**: The nodeID of the recipient if not sending to master - * @return True if success, False if failed + * @param nodeID **Optional**: The @ref _nodeID "nodeID" of the recipient if not sending to master. + * @return True if success; false if failed */ bool write(const void *data, uint8_t msg_type, size_t size, uint8_t nodeID = 0); /** - * Set a unique nodeID for this node. + * Set a unique @ref _nodeID "nodeID" for this node. * - * This needs to be called before mesh.begin(), can be via serial connection, eeprom etc if configuring a large number of nodes... + * This needs to be called before RF24Mesh::begin(). The parameter value passed can be fetched + * via serial connection, eeprom, etc when configuring a large number of nodes. * @note If using RF24Gateway and/or RF24Ethernet, nodeIDs 0 & 1 are used by the master node. - * @param nodeID Can be any unique value ranging from 1 to 253 + * @param nodeID Can be any unique value ranging from 1 to 253 (reserving 0 for the master node). */ void setNodeID(uint8_t nodeID); /** - * Reconnect to the mesh and renew the current RF24Network address. Used to re-establish a connection to the mesh if physical location etc. has changed, or - * a routing node goes down. - * @note Currently times out after 7.5 seconds if address renewal fails. + * @brief Reconnect to the mesh and renew the current RF24Network address. * - * @note If all nodes are set to verify connectivity/reconnect at a specified period, restarting the master (and deleting dhcplist.txt on Linux) will result - * in complete network/mesh reconvergence. - * @param timeout How long to attempt address renewal in milliseconds default:7500 + * This is used to re-establish a connection to the mesh network if physical location of a node + * or surrounding nodes has changed (or a routing node becomes unavailable). * - * @return Returns the newly assigned RF24Network address + * @note If all nodes are set to verify connectivity and reconnect at a specified period, then + * restarting the master (and deleting dhcplist.txt on Linux) will result in complete + * network/mesh reconvergence. + * @param timeout How long to attempt address renewal in milliseconds. Default is 7500 + * @return The newly assigned RF24Network address */ uint16_t renewAddress(uint32_t timeout = MESH_RENEWAL_TIMEOUT); #if !defined(MESH_NOMASTER) /** - * Only to be used on the master node. Provides automatic configuration for sensor nodes, similar to DHCP. - * Call immediately after calling network.update() to ensure address requests are handled appropriately + * This is only to be used on the master node because it manages allocation of network addresses + * for any requesting (non-master) node's ID, similar to DHCP. + * + * @warning On master nodes, It is required to call this function immediately after calling + * RF24Mesh::update() to ensure address requests are handled appropriately. */ void DHCP(); @@ -147,38 +152,40 @@ class RF24Mesh /** * Convert an RF24Network address into a nodeId. - * @param address If no address is provided, returns the local nodeID, otherwise a lookup request is sent to the master node - * @return Returns the unique identifier (1-255) or -1 if not found. + * @param address If no address is provided, returns the local @ref _nodeID "nodeID", + * otherwise a lookup request is sent to the master node + * @return The unique identifier of the node in the range [1, 253] or -1 if node was not found. */ int16_t getNodeID(uint16_t address = MESH_BLANK_ID); /** * Tests connectivity of this node to the mesh. * @note If this function fails, address renewal should typically be done. - * @return Return 1 if connected, 0 if mesh not responding + * @return 1 if connected, 0 if mesh not responding */ bool checkConnection(); /** * Releases the currently assigned address lease. Useful for nodes that will be sleeping etc. * @note Nodes should ensure that addresses are released successfully prior to going offline. - * @return Returns 1 if successfully released, 0 if not + * @return True if successfully released, otherwise false. */ bool releaseAddress(); /** * The assigned RF24Network (Octal) address of this node - * @return Returns an unsigned 16-bit integer containing the RF24Network address in octal format + * @return An unsigned 16-bit integer containing the RF24Network address in octal format. */ uint16_t mesh_address; /** - * Convert a nodeID into an RF24Network address - * @note If printing or displaying the address, it needs to be converted to octal format: Serial.println(address,OCT); + * @brief Convert a @ref _nodeID "nodeID" into an RF24Network address + * @note If printing or displaying the address, it needs to be converted to octal format: + * @code Serial.println(address, OCT); @endcode * * Results in a lookup request being sent to the master node. - * @param nodeID - The unique identifier (1-253) of the node - * @return Returns the RF24Network address of the node, -2 if successful but not in list, -1 if failed + * @param nodeID The unique identifier of the node in the range [1, 253]. + * @return The RF24Network address of the node, -2 if successful but not in list, -1 if failed. */ int16_t getAddress(uint8_t nodeID); @@ -208,7 +215,7 @@ class RF24Mesh * mesh.setCallback(myCallbackFunction); * @endcode * - * @param meshCallback The name of a function to call + * @param meshCallback The name of a function to call. This function should consume no required input parameters. */ void setCallback(void (*meshCallback)(void)); @@ -216,30 +223,38 @@ class RF24Mesh #if !defined(MESH_NOMASTER) /** - * Set or change a nodeID:RF24Network Address (key:value) pair manually on the master node. + * Set or change a @ref _nodeID "nodeID" : node address (key : value) pair manually. + * This function is for use on the master node only. * * @code - * // Set a static address for node 02, with nodeID 23, since it will just be a static routing node for example - * running on an ATTiny chip. - * + * // Set a static address for node 02, with nodeID 23, since it will just be + * // a static routing node for example running on an ATTiny chip. * mesh.setAddress(23, 02); * @endcode - * * @code * // Change or set the nodeID for an existing address - * * uint16_t address = 012; * mesh.setAddress(3, address, true); * @endcode * - * @param nodeID The nodeID to assign + * @param nodeID The @ref _nodeID "nodeID" to assign * @param address The octal RF24Network address to assign - * @param searchBy Optional parameter. Default is search by nodeID and set the address. True allows searching by address and setting nodeID. - * @return If the nodeID exists in the list, + * @param searchBy Optional parameter. Default is search by @ref _nodeID "nodeID" and + * set the address. True allows searching by address and setting @ref _nodeID "nodeID". + * @return If the @ref _nodeID "nodeID" exists in the list, */ void setAddress(uint8_t nodeID, uint16_t address, bool searchBy = false); + /** + * Save the @ref addrList to a binary file named "dhcplist.txt". + * @note This function is for use on the master node only and only on Linux or x86 platforms. + */ void saveDHCP(); + + /** + * Load the @ref addrList from a binary file named "dhcplist.txt". + * @note This function is for use on the master node only and only on Linux or x86 platforms. + */ void loadDHCP(); /** @@ -249,22 +264,35 @@ class RF24Mesh */ /**@{*/ - /** - * Calls setAddress() - */ + /** @deprecated For backward compatibility with older code. Use the synonomous setAddress() instead. */ void setStaticAddress(uint8_t nodeID, uint16_t address); #endif // !defined(MESH_NOMASTER) /**@}*/ + /** + * The unique identifying number used to differentiate mesh nodes' from their assigned network + * address. Ideally, this is set before calling begin() or renewAddress(). It is up to the + * network administrator to make sure that this number is unique to each mesh/network node. + * + * This nodeID number is typically in the range [0, 255], but remember that `0` is reserved for + * the master node. Other external systems may reserve other node ID numbers, for instance + * RF24Gateway/RF24Ethernet reserves the node ID number `1` in addition to the master node ID + * `0`. + */ uint8_t _nodeID; #if !defined(MESH_NOMASTER) - /** @brief A struct for storing a NodeID and an address in a single element of the RF24Mesh::addrList array */ + /** + * @brief A struct for storing a @ref _nodeID "nodeID" and an address in a single element of + * the RF24Mesh::addrList array. + * + * @note This array only exists on the mesh network's master node. + */ typedef struct { - /** @brief the nodeID of an network node (child) */ + /** @brief The @ref _nodeID "nodeID" of an network node (child) */ uint8_t nodeID; - /** @brief the address of an network node (child) */ + /** @brief The logical address of an network node (child) */ uint16_t address; } addrListStruct; @@ -277,11 +305,11 @@ class RF24Mesh // Pointer used for dynamic memory allocation of address list /** - * @brief A array of addressListStruct elements for assigned addresses + * @brief A array of addrListStruct elements for assigned addresses. * @see addrListStruct class reference */ addrListStruct *addrList; - /** @brief The number of entries in the addressListStruct of assigned addresses */ + /** @brief The number of entries in the addrListStruct of assigned addresses. */ uint8_t addrListTop; #endif /**@}*/ @@ -290,17 +318,25 @@ class RF24Mesh RF24 &radio; RF24Network &network; + /** Function pionter for customized callback usage in long running algorithms. */ void (*meshCallback)(void); - bool requestAddress(uint8_t level); /** Actual requesting of the address once a contact node is discovered or supplied **/ + + /** Actual requesting of the address once a contact node is discovered or supplied **/ + bool requestAddress(uint8_t level); #if !defined(MESH_NOMASTER) - bool doDHCP; /** Indicator that an address request is available */ - bool addrMemAllocated; /** Just ensures we don't re-allocate the memory buffer if restarting the mesh on master **/ + /** Indicator that an address request is available. */ + bool doDHCP; + /** Just ensures we don't re-allocate the memory buffer if restarting the mesh on master. **/ + bool addrMemAllocated; #endif - void beginDefault(); /** Starts up the network layer with default address **/ + /** Starts up the network layer with default address. **/ + void beginDefault(); + /** A flag asserted in begin() afer putting the radio in TX mode. */ bool meshStarted; - uint8_t getLevel(uint16_t address); /** Returns the number of digits in the specified address **/ + /** Returns the number of octal digits in the specified address. **/ + uint8_t getLevel(uint16_t address); }; #endif // define __RF24MESH_H__ diff --git a/docs/general_usage.md b/docs/general_usage.md index aec34d0..2dafd13 100644 --- a/docs/general_usage.md +++ b/docs/general_usage.md @@ -1,59 +1,80 @@ # General Usage - + ## Network Design Options -1. **Static Network** (No Mesh)
+1. **Static Network** (No Mesh) + RF24Network can be configured manually, with a static design. RF24Mesh is not used at all. See [Network addressing](http://nRF24.github.io/RF24Network/md_docs_addressing.html) -2. **Static Network w/Dynamic Assignment**
+2. **Static Network w/Dynamic Assignment** + RF24Mesh is only used to acquire an address on startup. Nodes are generally expected to remain stationary. Changes to the network would be addressed manually, by adding, removing, or resetting nodes. Users can choose to use RF24Network functions directly, or use RF24Mesh. -3. **Dynamic Network & Assignment**
+3. **Dynamic Network & Assignment** + Nodes join the mesh automatically and re-attach as required. This is the default and how the examples work. -4. **Hybrid Network**
+4. **Hybrid Network** + Utilizes a combination of static & dynamic nodes. Requires initial planning and deployment, but can result in a more stable network, easing the use of sleeping nodes. ## Network Management -RF24Network addresses can be viewed as MAC addresses, and RF24Mesh nodeIDs viewed as static IP addresses. When joining or re-attaching to the network, nodes -will request a RF24Network address, and are identified via nodeID. + +RF24Network addresses can be viewed as MAC addresses, and RF24Mesh nodeIDs +viewed as static IP addresses. When joining or re-attaching to the network, +nodes will request a RF24Network address, and are identified via nodeID. ### Raspberry Pi/Linux -On Linux devices, the RF24Gateway will save address assignments to file (dhcplist.txt) so they will be restored, even if the -gateway is restarted. To force network re-convergence, delete the dhcplist.txt file and restart the gateway. If nodes are configured to verify their connection at a set -interval, they will come back online in time. + +On Linux devices, the RF24Gateway will save address assignments to file +(dhcplist.txt) so they will be restored, even if the gateway is restarted. +To force network re-convergence, delete the dhcplist.txt file and restart the +gateway. If nodes are configured to verify their connection at a set interval, +they will come back online in time. ### Arduino/AVR -On all other devices, the address list is not saved. To force network re-convergence, restart the gateway. If nodes are configured to verify their -connection at a set interval, they will come back online in time. -If a node/nodeID is removed from the network permanently, the address should be released prior to removal. If it is not, the assigned RF24Network address can be +On all other devices, the address list is not saved. To force network re-convergence, +restart the gateway. If nodes are configured to verify their connection at a set +interval, they will come back online in time. + +If a node/nodeID is removed from the network permanently, the address should be +released prior to removal. If it is not, the assigned RF24Network address can be written to 0 in the RF24Mesh address list. ## Mesh Communication -RF24Mesh nodeIDs are unique identifiers, while RF24Network addresses change dynamically within a statically defined structure. Due to this structure, it is simple -for any node to communicate with the master node, since the RF24Network address is always known (00). Conversely, the master node maintains a list of every node on the -network, so address 'lookups' return immediately. -Communication from node-to-node requires address queries to be sent to the master node, since individual nodes may change RF24Network & radio address at any time. +RF24Mesh nodeIDs are unique identifiers, while RF24Network addresses change +dynamically within a statically defined structure. Due to this structure, it is +simple for any node to communicate with the master node, since the RF24Network +address is always known (00). Conversely, the master node maintains a list of +every node on the network, so address 'lookups' return immediately. + +Communication from node-to-node requires address queries to be sent to the master +node, since individual nodes may change RF24Network & radio address at any time. Due to the extra data transmissions, node-to-node communication is less efficient. ## General Usage -One thing to keep in mind is the dynamic nature of RF24Mesh, and the need to verify connectivity to the network. For nodes that are constantly transmitting, -(every few seconds at most) it is suitable to check the connection, and/or renew the address when connectivity fails. Since data is not saved by the master -node, if the master node goes down, all child nodes must renew their address. In this case, as long as the master node is down for a few seconds, the nodes -will all begin requesting an address. - - -Nodes that are not actively transmitting, should be configured to test their connection at predefined intervals, to allow them to reconnect as necessary. +One thing to keep in mind is the dynamic nature of RF24Mesh, and the need to +verify connectivity to the network. For nodes that are constantly transmitting, +(every few seconds at most) it is suitable to check the connection, and/or renew +the address when connectivity fails. Since data is not saved by the master +node, if the master node goes down, all child nodes must renew their address. +In this case, as long as the master node is down for a few seconds, the nodes +will all begin requesting an address. -In the case of sleeping nodes, or nodes that will only be online temporarily, it is generally suitable to release the address prior to going offline, and -requesting an address upon waking. Keep in mind, address requests can generally take anywhere from 10-15ms, up to few seconds in most cases. +Nodes that are not actively transmitting, should be configured to test their +connection at predefined intervals, to allow them to reconnect as necessary. +In the case of sleeping nodes, or nodes that will only be online temporarily, +it is generally suitable to release the address prior to going offline, and +requesting an address upon waking. Keep in mind, address requests can generally +take anywhere from 10 - 15ms, up to few seconds in most cases. One of the recently introduced features is the ability to transmit payloads without the network returning a network-ack response. If solely using this method -of transmission, the node should also be configured to verify its connection via `mesh.checkConnection();` periodically, to ensure connectivity. +of transmission, the node should also be configured to verify its connection via `RF24Mesh::checkConnection()` periodically, to ensure connectivity. ## RF24Network -Beyond requesting and releasing addresses, usage is outlined in the class documentation, and further information regarding RF24Network is available at -http://nRF24.github.io/RF24Network + +Beyond requesting and releasing addresses, usage is outlined in the RF24Mesh class documentation, and further information regarding RF24Network is available at +[RF24Network documentation](http://nRF24.github.io/RF24Network). diff --git a/docs/main_page.md b/docs/main_page.md index 2fb2593..54c628b 100644 --- a/docs/main_page.md +++ b/docs/main_page.md @@ -1,36 +1,54 @@ # Mesh Networking Layer for RF24 Radios -This class intends to provide a simple and seamless 'mesh' layer for sensor networks, allowing automatic and dynamic configuration -that can be customized to suit many scenarios. It is currently designed to interface directly with with the -[RF24Network library](http://tmrh20.github.com/RF24Network/), an [OSI Network Layer](http://en.wikipedia.org/wiki/Network_layer) using nRF24L01(+) radios driven -by the newly optimized [RF24 library](http://tmrh20.github.com/RF24/) fork. + +This class intends to provide a simple and seamless 'mesh' layer for sensor networks, +allowing automatic and dynamic configuration that can be customized to suit many scenarios. +It is currently designed to interface directly with with the +[RF24Network library](http://tmrh20.github.com/RF24Network/), an +[OSI Network Layer](http://en.wikipedia.org/wiki/Network_layer) using nRF24L01(+) radios +driven by the newly optimized [RF24 library](http://tmrh20.github.com/RF24/) fork. ## Purpose/Goals + - Provide a simple user interface for creating dynamic sensor networks with the RF24 and RF24Network libraries. - Create stable, fully automated/self-managed networks ## News + See a the list of changes on [the Github releases page](https://github.com/nRF24/RF24Mesh/releases/) ## RF24Mesh Overview -The RF24Network library provides a system of addressing and routing for RF24 radio modules that allows large wireless sensor networks to be constructed.
RF24Mesh -provides extended features, including automatic addressing and dynamic configuration of wireless sensors. + +The RF24Network library provides a system of addressing and routing for RF24 radio modules +that allows large wireless sensor networks to be constructed. + +RF24Mesh provides extended features, including automatic addressing and dynamic configuration +of wireless sensors. ### How does it work? + Nodes are assigned a unique number ranging from 1 to 253, and just about everything else, addressing, routing, etc. is managed by the library. -The unique identifier is like an IP address, used to communicate at a high level within the RF24 communication stack and will generally remain static. At the network layer, -the physical radio addresses, similar to MAC addresses, are allocated as nodes move around and establish connections within the network. +The unique identifier is like an IP address, used to communicate at a high level within the +RF24 communication stack and will generally remain static. At the network layer, the physical +radio addresses, similar to MAC addresses, are allocated as nodes move around and establish +connections within the network. -The 'master' node keeps track of the unique nodeIDs and the assigned RF24Network addresses. When a node is moved physically, or just loses its connection to the network, +The 'master' node keeps track of the unique nodeIDs and the assigned RF24Network addresses. +When a node is moved physically, or just loses its connection to the network, it can automatically re-join the mesh and reconfigure itself within the network. -In the mesh configuration sensors/nodes can move around physically, far from the 'master node' using other nodes to route traffic over extended distances. Addressing and -topology is reconfigured as connections are broken and re-established within different areas of the network. +In the mesh configuration sensors/nodes can move around physically, far from the 'master +node' using other nodes to route traffic over extended distances. Addressing and +topology is reconfigured as connections are broken and re-established within different areas +of the network. -RF24Mesh takes advantage of functionality and features within the RF24 and RF24Network libraries, so everything from addressing, routing, fragmentation/re-assembly -(very large payloads) are handled automatically with processes designed to support a multi-node radio network. +RF24Mesh takes advantage of functionality and features within the RF24 and RF24Network +libraries, so everything from addressing, routing, fragmentation/re-assembly +(very large payloads) are handled automatically with processes designed to support a +multi-node radio network. ## How to learn more + - Try it out! - [Setup and Configuration](md_docs_setup_config.html) - [Usage & Overview](md_docs_general_usage.html) diff --git a/docs/setup_config.md b/docs/setup_config.md index 30d3de8..daacdfd 100644 --- a/docs/setup_config.md +++ b/docs/setup_config.md @@ -1,22 +1,28 @@ # Setup And Config -The initial testing version of RF24Mesh is built as a simple overlay for RF24Network. Users currently need to be familiar with the basics of sending and receiving data via -RF24Network, but do not need to understand the topology, routing or addressing systems. RF24Mesh will attempt to construct and maintain a mesh network, keeping all nodes +The initial testing version of RF24Mesh is built as a simple overlay for RF24Network. Users +currently need to be familiar with the basics of sending and receiving data via +RF24Network, but do not need to understand the topology, routing or addressing systems. +RF24Mesh will attempt to construct and maintain a mesh network, keeping all nodes connected together. ## Requirements + ### Hardware Requirements + - 1 Raspberry Pi or Arduino to act as the Master Node - 1 or more Arduino, Raspberry Pi, etc. (Sensor Nodes) - 2 or more NRF24L01+ radio modules - 1 or more various sensors for your sensor nodes ### Software Requirements + - [Download RF24 Core Radio Library](https://github.com/TMRh20/RF24/archive/master.zip) - [Download RF24Network Library](https://github.com/TMRh20/RF24Network/archive/master.zip) - [Download RF24Mesh - Dynamic Mesh Library](https://github.com/TMRh20/RF24Mesh/archive/master.zip) ## Installation + 1. Use the Arduino Library Manager. Selecting RF24Mesh should also install RF24Network and RF24 Core libraries 2. Configure and test the hardware using examples from RF24 and RF24Network prior to attempting to use RF24Mesh - In Arduino IDE @@ -34,20 +40,27 @@ connected together. display incoming data from the sensor examples. Usage is very much the same as RF24Network, except for address assignment and network management. ## Configuration -As per the examples, nodes are configured with a unique value between 1 and 253. This allows them to change positions on the network while still being identified. -For pre-configuration of the mesh, some options are available by editing RF24Mesh_config.h prior to compiling: +As per the examples, nodes are configured with a unique value between 1 and 253. This allows +them to change positions on the network while still being identified. + +For pre-configuration of the mesh, some options are available by editing RF24Mesh_config.h +prior to compiling: ### Restrict number of children + ```cpp #define MESH_MAX_CHILDREN 4 ``` + The @ref MESH_MAX_CHILDREN option restricts the maximum number of child nodes/node and limits the number of available addresses on the network. Max: 4 ### Reduce resource consumption + ```cpp #define MESH_NOMASTER ``` + The MESH_NOMASTER macro optionally reduces program space and memory usage. Can be used on any node except for the master (nodeID 0) @see [General Usage](md_docs_general_usage.html) for information on how to work with the mesh once connected diff --git a/docs/sphinx/Makefile b/docs/sphinx/Makefile new file mode 100644 index 0000000..bbe765a --- /dev/null +++ b/docs/sphinx/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = ../sphinx +BUILDDIR = ../_build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/sphinx/README.md b/docs/sphinx/README.md new file mode 100644 index 0000000..5ca5047 --- /dev/null +++ b/docs/sphinx/README.md @@ -0,0 +1,3 @@ +# Intended for Sphinx only + +The files in this folder are only used to generate documentation using Sphinx (from Doxygen's XML output). diff --git a/docs/sphinx/RF24Mesh_8h.rst b/docs/sphinx/RF24Mesh_8h.rst new file mode 100644 index 0000000..aa584d3 --- /dev/null +++ b/docs/sphinx/RF24Mesh_8h.rst @@ -0,0 +1,5 @@ +RF24Mesh.h +============ + +.. doxygenfile:: RF24Mesh.h + :sections: define diff --git a/docs/sphinx/RF24Mesh__config_8h.rst b/docs/sphinx/RF24Mesh__config_8h.rst new file mode 100644 index 0000000..689495a --- /dev/null +++ b/docs/sphinx/RF24Mesh__config_8h.rst @@ -0,0 +1,4 @@ +RF24Mesh_config.h +================= + +.. doxygenfile:: RF24Mesh_config.h diff --git a/docs/sphinx/_static/Logo large.png b/docs/sphinx/_static/Logo large.png new file mode 100644 index 0000000000000000000000000000000000000000..25e42159f1427f4a210c1cd0e18cdf6d84793889 GIT binary patch literal 94683 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

$XBO&CaTH<}Ju?^EyAf@wax#|hoJF-K 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 E2T54hy8r+H literal 0 HcmV?d00001 diff --git a/docs/sphinx/_static/custom_material.css b/docs/sphinx/_static/custom_material.css new file mode 100644 index 0000000..7ff9d22 --- /dev/null +++ b/docs/sphinx/_static/custom_material.css @@ -0,0 +1,76 @@ +.md-typeset .admonition.tip>.admonition-title::before, +.md-typeset .admonition.hint>.admonition-title::before { + mask-image: url('data:image/svg+xml;charset=utf-8,'); +} + +.md-typeset .admonition.seealso>.admonition-title::before { + mask-image: url('data:image/svg+xml;charset=utf-8,'); + background-color: hsl(301, 100%, 63%); +} + +.md-typeset .admonition.seealso { + border-left: .2rem solid hsl(301, 100%, 63%); +} + +.md-typeset .admonition.seealso>.admonition-title { + background-color: hsla(287, 100%, 63%, 0.25); +} + +.md-typeset .admonition.important>.admonition-title::before { + mask-image: url('data:image/svg+xml;charset=utf-8,'); + background-color: hsl(123, 100%, 63%); +} + +.md-typeset .admonition.important { + border-left: .2rem solid hsl(123, 100%, 63%); +} + +.md-typeset .admonition.important>.admonition-title { + background-color: hsla(123, 100%, 63%, 0.25); +} + +.md-typeset .admonition.warning>.admonition-title::before { + background-color: hsl(0, 100%, 63%); +} + +.md-typeset .admonition.warning { + border-left: .2rem solid hsl(0, 100%, 63%); +} + +.md-typeset .admonition.warning>.admonition-title { + background-color: hsla(0, 100%, 63%, 0.25); +} + +html .md-nav--primary .md-nav__title--site .md-nav__button { + top: 0; + left: 0; + width: inherit; + height: auto; +} + +.md-typeset table:not([class]) th { + background-color: rgba(23, 35, 83, 0.8); +} + +.md-typeset table:not([class]) tr:hover { + background-color: rgba(56, 2, 81, 0.8); + box-shadow: inset 0 .05rem 0 #9515ff; +} + +[data-md-color-scheme="default"] { + --md-code-bg-color: #e8e7e7; +} + +.md-nav__title .md-nav__button.md-logo img, .md-nav__title .md-nav__button.md-logo svg { + height: 3rem; + width: auto; +} + +.md-header__button.md-logo img, .md-header__button.md-logo svg { + width: auto; +} + +.linenos { + background-color: var(--md-default-bg-color--light); + margin-right: 0.5rem; +} diff --git a/docs/sphinx/_static/new_favicon.ico b/docs/sphinx/_static/new_favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c15a1650af557d6ec815ebd7c0b5dbf5cc080676 GIT binary patch literal 4286 zcmc(iTT@%t8HTmbTLO|^B{RuQF8dF1+sS`u|3GnV5NCurSYW$$;sBD+2}$S-;$VR> z*!ajn#&(=Z-R9)9?o6jMz3hMR?3p+3S{oE}NPB1FS+=ab*Lv3Xt?zxmg~y}t&nKUF z#NVEqzwvlJ@OV7G<=|JI-*JfVf9*%_oqePG=NxOG=-7*E4&Qs5*y0{Sc65_ zxlNG&Hiskmy>qbo^6(~C-zHnIT>f|pwm%O$@s9XCv#@)!u;VN5EPkUm0$=F}T^rw^ ztNIsoZoNdu_Fv(<{|df4-@&){J-iQofcMdlu=iiVKKcpP@jqZae+~1+KVf~%!O!A5 z@ytGW<-WVeIh-rj=-By|YyM5H-Bo=lSZ@9qKkEQ`ZwR`70s5r^^noa}p%}E`IJD6O zw6P@AiyYU=P^VH*gJ~!;87Nmct}Q{ip5@57|DQ?A=kmRYMLACka1F7}bP?KMOs=o@ zM;+=_PX@TIokOW%Sw zvkmXkHtg(e>T(BmVHb9B7gqTWtd+a4Dtj>3?!nyPsNRRU^}rqC^G%NRd$3mdPLc15 zIVH{)Yvj3B?l#xv$ZR{pdzb@w&o9CE4(d;vNQ)ehpM^JGdF%DB`R85!g*N;TKZNI_ zkN)@g_+xlfwQ=A7Z_ep+`j6H(?=T0jdvXo&E1!Of_iO*6MqZ+mTItxL79YQcZ@#%bpF{=GSp2O#yGWf zeG{tOLlpYlohJS?w5=EPfkpDk{E|AIt3r$3fp%q?{20{HD6H_~6Mo3iE-t$Lr-}bw z_9^`+Og~tHcc%tBwGK0}0c-s(%=n&MOaF2gdh~4kA#YnIhK68uM8{R#LpfxpY+Dh z+wj+(F@EwlUo`T=cxif5P|rOD%lw68WPs7}svM z#ILWCe}(+DC$0R%BbT2zkms)sh9%z|DuMT}Mt+*}d&2PcH1W@M!^>yd2>DmHTlp(b zpNB>59b*6g9nA0mG+#9l*2tQ|iD?}cJx{DPl4;a_gUFCRu zWD*AX+T^sOlFuB#94*WGKE3~)p1*M+1mAh`WBGb+RPud`HTppm=FF{@_>JNd>Ym!; zeWJPkv<&NSiu}xx8~-r*!K~D_&{qSWXDs=DDzx&GNAxA~gVa`2{Tq4mZ?IPv4qN%z z1E`l4VPtDAe2Ja_E zj$T0jQ)jY{Ivjy#wjQqg`n3Lou8uf21kx#$XmfG-(hw^NU!U=zr z@sE%{u|obvL;kyj|I&Ya=jaX1@kdYi4MCCGTBh4PRd*G;oA7EbCm-WjP*UV5e){wX-s{!`du_%ma8@`Btwsg{)*Btd)N$i0=nYm#ds>5Oa%ew~qyEe{! z_VI2q%(F?A>v2dH{UK;m8F@bt9!f8B=ZhX6Ab+6^zarvKLJ7v9d{JLbCpQx%!9SJ6*+RygSpod4H6581`|My-fOPFvIm? ij#+u`=)qupp7^Zoy>yf-DMy)(JIbZl8`j1<{rMRY83?oh literal 0 HcmV?d00001 diff --git a/docs/sphinx/classRF24Mesh.rst b/docs/sphinx/classRF24Mesh.rst new file mode 100644 index 0000000..d0013c7 --- /dev/null +++ b/docs/sphinx/classRF24Mesh.rst @@ -0,0 +1,62 @@ +RF24Mesh class +~~~~~~~~~~~~~~ + +.. cpp:class:: RF24Mesh + + .. doxygenfunction:: RF24Mesh::RF24Mesh + + .. seealso:: + - :cpp:class:`RF24` for the ``radio`` object + - :cpp:class:`RF24Network` for the ``network`` object. + +Basic API +============ + +.. doxygenfunction:: RF24Mesh::begin + +.. seealso:: + :cpp:enum:`rf24_datarate_e`, :cpp:func:`RF24::setChannel()`, :c:macro:`MESH_DEFAULT_CHANNEL`, :c:macro:`MESH_RENEWAL_TIMEOUT` + +.. doxygenfunction:: RF24Mesh::update + +.. seealso:: + Review :cpp:func:`RF24Network::update()` for more details. + +.. doxygenfunction:: RF24Mesh::write (const void *data, uint8_t msg_type, size_t size, uint8_t nodeID=0) + +.. seealso:: + Review :cpp:var:`RF24NetworkHeader::type` for more details about available message types. + +.. doxygenfunction:: RF24Mesh::renewAddress +.. doxygenfunction:: RF24Mesh::setNodeID +.. doxygenvariable:: RF24Mesh::_nodeID + +Advanced API +============ + +.. doxygenvariable:: RF24Mesh::mesh_address +.. doxygenfunction:: RF24Mesh::getNodeID +.. doxygenfunction:: RF24Mesh::checkConnection +.. doxygenfunction:: RF24Mesh::releaseAddress +.. doxygenfunction:: RF24Mesh::getAddress +.. doxygenfunction:: RF24Mesh::write (uint16_t to_node, const void *data, uint8_t msg_type, size_t size) +.. doxygenfunction:: RF24Mesh::setChannel + +.. seealso:: :cpp:func:`RF24::setChannel` + +.. doxygenfunction:: RF24Mesh::setChild +.. doxygenfunction:: RF24Mesh::setCallback +.. doxygenfunction:: RF24Mesh::setAddress +.. doxygenfunction:: RF24Mesh::setStaticAddress +.. doxygenfunction:: RF24Mesh::DHCP +.. doxygenfunction:: RF24Mesh::saveDHCP +.. doxygenfunction:: RF24Mesh::loadDHCP + +Address List Struct +=================== + +.. doxygenvariable:: RF24Mesh::addrList +.. doxygenvariable:: RF24Mesh::addrListTop + +.. doxygenstruct:: RF24Mesh::addrListStruct + :members: diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py new file mode 100644 index 0000000..96b890a --- /dev/null +++ b/docs/sphinx/conf.py @@ -0,0 +1,152 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) +import subprocess +import os +import json + +# -- Project information ----------------------------------------------------- + +project = "RF24Mesh library" +copyright = "2021, nRF24 org" +author = "nRF24" + +# The full version, including alpha/beta/rc tags +release = "1.1.6" # the minimum version that supports sphinx builds and RTD hosting +with open("../../library.json", "rb") as lib_json: + # get updated info from PlatformIO JSON + release = json.load(lib_json)["version"] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "breathe", + "sphinx_immaterial", + "sphinx.ext.intersphinx", +] + +intersphinx_mapping = { + "python": ("https://rf24.readthedocs.io/en/latest/", None), + "RF24 library": ("https://rf24.readthedocs.io/en/latest", None), + "RF24Network library": ("https://rf24network.readthedocs.io/en/latest", None), +} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# code-blocks will use this as their default syntax highlighting +highlight_language = "c++" + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# -- Options for breathe (XML output from doxygen) --------------------------- + +breathe_projects = {"RF24Mesh": "xml"} +breathe_default_project = "RF24Mesh" + +READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True" + +if READTHEDOCS: + subprocess.call("cd ../..; doxygen", shell=True) + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_immaterial" +html_theme_options = { + "features": [ + # "navigation.expand", + "navigation.tabs", + # "toc.integrate", + # "navigation.sections", + "navigation.instant", + # "header.autohide", + "navigation.top", + # "search.highlight", + "search.share", + ], + "palette": [ + { + "media": "(prefers-color-scheme: dark)", + "scheme": "slate", + "primary": "lime", + "accent": "light-blue", + "toggle": { + "icon": "material/lightbulb", + "name": "Switch to light mode", + }, + }, + { + "media": "(prefers-color-scheme: light)", + "scheme": "default", + "primary": "light-blue", + "accent": "green", + "toggle": { + "icon": "material/lightbulb-outline", + "name": "Switch to dark mode", + }, + }, + ], + # Set the repo location to get a badge with stats + "repo_url": "https://github.com/nRF24/RF24Mesh/", + "repo_name": "RF24Mesh", + "repo_type": "github", + # Visible levels of the global TOC; -1 means unlimited + "globaltoc_depth": 3, + # If False, expand all TOC entries + "globaltoc_collapse": False, + # If True, show hidden TOC entries + "globaltoc_includehidden": True, +} +# Set link name generated in the top bar. +html_title = "RF24Mesh C++ library" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +html_favicon = "_static/new_favicon.ico" + +# project logo +html_logo = "_static/Logo large.png" + +# These paths are either relative to html_static_path +# or fully qualified paths (eg. https://...) +html_css_files = [ + "custom_material.css", +] diff --git a/docs/sphinx/deprecated.rst b/docs/sphinx/deprecated.rst new file mode 100644 index 0000000..3e1b5a0 --- /dev/null +++ b/docs/sphinx/deprecated.rst @@ -0,0 +1,5 @@ +Deprecated API +============== + +.. doxygenpage:: deprecated + :content-only: diff --git a/docs/sphinx/examples.rst b/docs/sphinx/examples.rst new file mode 100644 index 0000000..0fd5469 --- /dev/null +++ b/docs/sphinx/examples.rst @@ -0,0 +1,44 @@ +Examples +========== + +Arduino Examples +---------------- + +.. toctree:: + :maxdepth: 1 + + examples/Arduino/RF24Mesh_Example + examples/Arduino/RF24Mesh_Example_Master + examples/Arduino/RF24Mesh_Example_Node2Node + examples/Arduino/RF24Mesh_Example_Node2NodeExtra + examples/Arduino/RF24Mesh_SerialConfig + +Linux Examples +---------------- + +.. toctree:: + :maxdepth: 1 + + examples/Linux/RF24Mesh_Example + examples/Linux/RF24Mesh_Example_Master + examples/Linux/RF24Mesh_Ncurses_Master + +PicoSDK Examples +---------------- + +.. toctree:: + :maxdepth: 1 + + examples/PicoSDK/RF24Mesh_Example_Master + examples/PicoSDK/RF24Mesh_Example + examples/PicoSDK/default_pins + + +Python Examples +---------------- + +.. toctree:: + :maxdepth: 1 + + examples/Python/RF24Mesh_Example_Master + examples/Python/RF24Mesh_Example diff --git a/docs/sphinx/examples/Arduino/RF24Mesh_Example.rst b/docs/sphinx/examples/Arduino/RF24Mesh_Example.rst new file mode 100644 index 0000000..04327d3 --- /dev/null +++ b/docs/sphinx/examples/Arduino/RF24Mesh_Example.rst @@ -0,0 +1,5 @@ +RF24Mesh_Example.ino +=========================== + +.. literalinclude:: ../../../../examples/RF24Mesh_Example/RF24Mesh_Example.ino + :linenos: diff --git a/docs/sphinx/examples/Arduino/RF24Mesh_Example_Master.rst b/docs/sphinx/examples/Arduino/RF24Mesh_Example_Master.rst new file mode 100644 index 0000000..0b702e9 --- /dev/null +++ b/docs/sphinx/examples/Arduino/RF24Mesh_Example_Master.rst @@ -0,0 +1,5 @@ +RF24Mesh_Example_Master.ino +=========================== + +.. literalinclude:: ../../../../examples/RF24Mesh_Example_Master/RF24Mesh_Example_Master.ino + :linenos: diff --git a/docs/sphinx/examples/Arduino/RF24Mesh_Example_Node2Node.rst b/docs/sphinx/examples/Arduino/RF24Mesh_Example_Node2Node.rst new file mode 100644 index 0000000..6bc84a6 --- /dev/null +++ b/docs/sphinx/examples/Arduino/RF24Mesh_Example_Node2Node.rst @@ -0,0 +1,5 @@ +RF24Mesh_Example_Node2Node.ino +============================== + +.. literalinclude:: ../../../../examples/RF24Mesh_Example_Node2Node/RF24Mesh_Example_Node2Node.ino + :linenos: diff --git a/docs/sphinx/examples/Arduino/RF24Mesh_Example_Node2NodeExtra.rst b/docs/sphinx/examples/Arduino/RF24Mesh_Example_Node2NodeExtra.rst new file mode 100644 index 0000000..f23763f --- /dev/null +++ b/docs/sphinx/examples/Arduino/RF24Mesh_Example_Node2NodeExtra.rst @@ -0,0 +1,5 @@ +RF24Mesh_Example_Node2NodeExtra.ino +=================================== + +.. literalinclude:: ../../../../examples/RF24Mesh_Example_Node2NodeExtra/RF24Mesh_Example_Node2NodeExtra.ino + :linenos: diff --git a/docs/sphinx/examples/Arduino/RF24Mesh_SerialConfig.rst b/docs/sphinx/examples/Arduino/RF24Mesh_SerialConfig.rst new file mode 100644 index 0000000..bf2d82c --- /dev/null +++ b/docs/sphinx/examples/Arduino/RF24Mesh_SerialConfig.rst @@ -0,0 +1,5 @@ +RF24Mesh_SerialConfig.ino +========================= + +.. literalinclude:: ../../../../examples/RF24Mesh_SerialConfig/RF24Mesh_SerialConfig.ino + :linenos: diff --git a/docs/sphinx/examples/Linux/RF24Mesh_Example.rst b/docs/sphinx/examples/Linux/RF24Mesh_Example.rst new file mode 100644 index 0000000..f24e5dd --- /dev/null +++ b/docs/sphinx/examples/Linux/RF24Mesh_Example.rst @@ -0,0 +1,6 @@ +RF24Mesh_Example.cpp +=========================== + +.. literalinclude:: ../../../../examples_RPi/RF24Mesh_Example.cpp + :caption: examples_RPi/RF24Mesh_Example.cpp + :linenos: diff --git a/docs/sphinx/examples/Linux/RF24Mesh_Example_Master.rst b/docs/sphinx/examples/Linux/RF24Mesh_Example_Master.rst new file mode 100644 index 0000000..6099457 --- /dev/null +++ b/docs/sphinx/examples/Linux/RF24Mesh_Example_Master.rst @@ -0,0 +1,6 @@ +RF24Mesh_Example_Master.cpp +=========================== + +.. literalinclude:: ../../../../examples_RPi/RF24Mesh_Example_Master.cpp + :caption: examples_RPi/RF24Mesh_Example_Master.cpp + :linenos: diff --git a/docs/sphinx/examples/Linux/RF24Mesh_Ncurses_Master.rst b/docs/sphinx/examples/Linux/RF24Mesh_Ncurses_Master.rst new file mode 100644 index 0000000..e044fe7 --- /dev/null +++ b/docs/sphinx/examples/Linux/RF24Mesh_Ncurses_Master.rst @@ -0,0 +1,10 @@ +RF24Mesh_Ncurses_Master.cpp +=========================== + +A very limited ncurses interface used for initial monitoring/testing of RF24Mesh. + +.. image:: ../../../../images/RF24Mesh_Ncurses.JPG + +.. literalinclude:: ../../../../examples_RPi/ncurses/RF24Mesh_Ncurses_Master.cpp + :caption: examples_RPi/RF24Mesh_Ncurses_Master.cpp + :linenos: diff --git a/docs/sphinx/examples/PicoSDK/RF24Mesh_Example.rst b/docs/sphinx/examples/PicoSDK/RF24Mesh_Example.rst new file mode 100644 index 0000000..982a3eb --- /dev/null +++ b/docs/sphinx/examples/PicoSDK/RF24Mesh_Example.rst @@ -0,0 +1,8 @@ +RF24Mesh_Example.cpp +=========================== + +.. seealso:: + `defaultPins.h `_ + +.. literalinclude:: ../../../../examples_pico/RF24Mesh_Example.cpp + :linenos: diff --git a/docs/sphinx/examples/PicoSDK/RF24Mesh_Example_Master.rst b/docs/sphinx/examples/PicoSDK/RF24Mesh_Example_Master.rst new file mode 100644 index 0000000..308d3f5 --- /dev/null +++ b/docs/sphinx/examples/PicoSDK/RF24Mesh_Example_Master.rst @@ -0,0 +1,8 @@ +RF24Mesh_Example_Master.cpp +=========================== + +.. seealso:: + `defaultPins.h `_ + +.. literalinclude:: ../../../../examples_pico/RF24Mesh_Example_Master.cpp + :linenos: diff --git a/docs/sphinx/examples/PicoSDK/default_pins.rst b/docs/sphinx/examples/PicoSDK/default_pins.rst new file mode 100644 index 0000000..5408726 --- /dev/null +++ b/docs/sphinx/examples/PicoSDK/default_pins.rst @@ -0,0 +1,5 @@ +PicoSDK Examples' Default Pins +============================== + +.. literalinclude:: ../../../../examples_pico/defaultPins.h + :linenos: diff --git a/docs/sphinx/examples/Python/RF24Mesh_Example.rst b/docs/sphinx/examples/Python/RF24Mesh_Example.rst new file mode 100644 index 0000000..43a09e1 --- /dev/null +++ b/docs/sphinx/examples/Python/RF24Mesh_Example.rst @@ -0,0 +1,7 @@ +RF24Mesh_Example.py +=========================== + +.. literalinclude:: ../../../../examples_RPi/RF24Mesh_Example.py + :language: python + :caption: examples_RPi/RF24Mesh_Example.py + :linenos: diff --git a/docs/sphinx/examples/Python/RF24Mesh_Example_Master.rst b/docs/sphinx/examples/Python/RF24Mesh_Example_Master.rst new file mode 100644 index 0000000..8b8f354 --- /dev/null +++ b/docs/sphinx/examples/Python/RF24Mesh_Example_Master.rst @@ -0,0 +1,7 @@ +RF24Mesh_Example_Master.py +=========================== + +.. literalinclude:: ../../../../examples_RPi/RF24Mesh_Example_Master.py + :language: python + :caption: examples_RPi/RF24Mesh_Example_Master.py + :linenos: diff --git a/docs/sphinx/index.rst b/docs/sphinx/index.rst new file mode 100644 index 0000000..716d1e2 --- /dev/null +++ b/docs/sphinx/index.rst @@ -0,0 +1,37 @@ + +:hero: Automated Networking for nrf24L01 radios + +Introduction +============= + +.. doxygenpage:: index + :content-only: + +Site Index +----------- + +:ref:`Site index` + +.. toctree:: + :maxdepth: 2 + :caption: API Reference + :hidden: + + classRF24Mesh + RF24Mesh_8h + RF24Mesh__config_8h + deprecated + + +.. toctree:: + :maxdepth: 1 + :hidden: + + pages + + +.. toctree:: + :maxdepth: 2 + :hidden: + + examples diff --git a/docs/sphinx/make.bat b/docs/sphinx/make.bat new file mode 100644 index 0000000..bba1007 --- /dev/null +++ b/docs/sphinx/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=../sphinx +set BUILDDIR=../_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/sphinx/md_contributing.rst b/docs/sphinx/md_contributing.rst new file mode 100644 index 0000000..8a9b00d --- /dev/null +++ b/docs/sphinx/md_contributing.rst @@ -0,0 +1,5 @@ +Contributing +============= + +.. doxygenpage:: md_CONTRIBUTING + :content-only: diff --git a/docs/sphinx/md_docs_general_usage.rst b/docs/sphinx/md_docs_general_usage.rst new file mode 100644 index 0000000..5a53a1a --- /dev/null +++ b/docs/sphinx/md_docs_general_usage.rst @@ -0,0 +1,5 @@ +General Usage +================ + +.. doxygenpage:: md_docs_general_usage + :content-only: diff --git a/docs/sphinx/md_docs_setup_config.rst b/docs/sphinx/md_docs_setup_config.rst new file mode 100644 index 0000000..81cc4df --- /dev/null +++ b/docs/sphinx/md_docs_setup_config.rst @@ -0,0 +1,5 @@ +Setup And Config +================ + +.. doxygenpage:: md_docs_setup_config + :content-only: diff --git a/docs/sphinx/pages.rst b/docs/sphinx/pages.rst new file mode 100644 index 0000000..a8b4b11 --- /dev/null +++ b/docs/sphinx/pages.rst @@ -0,0 +1,9 @@ +Related Pages +============= + +.. toctree:: + :maxdepth: 1 + + md_contributing + md_docs_general_usage + md_docs_setup_config diff --git a/docs/sphinx/requirements.txt b/docs/sphinx/requirements.txt new file mode 100644 index 0000000..1707fa2 --- /dev/null +++ b/docs/sphinx/requirements.txt @@ -0,0 +1,2 @@ +breathe +sphinx-immaterial From 578e54147bf09fe289617b535b7256b4034464db Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 31 Oct 2021 07:41:24 -0700 Subject: [PATCH 64/72] fix broken links on docs main pg --- docs/main_page.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/main_page.md b/docs/main_page.md index 54c628b..6219073 100644 --- a/docs/main_page.md +++ b/docs/main_page.md @@ -3,9 +3,9 @@ This class intends to provide a simple and seamless 'mesh' layer for sensor networks, allowing automatic and dynamic configuration that can be customized to suit many scenarios. It is currently designed to interface directly with with the -[RF24Network library](http://tmrh20.github.com/RF24Network/), an +[RF24Network library](http://nRF24.github.io/RF24Network), an [OSI Network Layer](http://en.wikipedia.org/wiki/Network_layer) using nRF24L01(+) radios -driven by the newly optimized [RF24 library](http://tmrh20.github.com/RF24/) fork. +driven by the newly optimized [RF24 library](http://nRF24.github.io/RF24) fork. ## Purpose/Goals @@ -57,4 +57,4 @@ multi-node radio network. - [RF24Ethernet: TCP/IP based Mesh over RF24](http://nRF24.github.io/RF24Ethernet/) - [RF24Gateway: A TCP/IP and RF24 Gateway for RF24 nodes](http://nRF24.github.io/RF24Gateway/) - [All Documentation and Downloads](https://tmrh20.github.io) -- [Source Code](https://github.com/TMRh20/RF24Mesh) +- [Source Code](https://github.com/nRF24/RF24Mesh) From 9b94c34b82b8aed1d4db630600dd1384e267dd3b Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 31 Oct 2021 20:22:20 -0700 Subject: [PATCH 65/72] small docs update --- Doxyfile | 7 ++++++- docs/general_usage.md | 2 +- docs/sphinx/_static/custom_material.css | 5 +++++ docs/sphinx/conf.py | 4 ++++ docs/sphinx/examples/PicoSDK/RF24Mesh_Example.rst | 2 +- docs/sphinx/examples/PicoSDK/RF24Mesh_Example_Master.rst | 2 +- 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Doxyfile b/Doxyfile index be6e778..c32f7c6 100644 --- a/Doxyfile +++ b/Doxyfile @@ -269,7 +269,12 @@ TAB_SIZE = 4 # commands \{ and \} for these it is advised to use the version @{ and @} or use # a double escape (\\{ and \\}) -ALIASES = +ALIASES = "rst=\xmlonlyembed:rst:leading-asterisk^^" +ALIASES += "endrst=\endxmlonly" +ALIASES += "see{1}=@rst .. seealso:: \1@endrst" +ALIASES += "important{1}=@rst .. important:: \1@endrst" +ALIASES += "hint{1}=@rst.. hint:: \1@endrst" +ALIASES += "tip{1}=@rst.. tip:: \1@endrst" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For diff --git a/docs/general_usage.md b/docs/general_usage.md index 2dafd13..403d7f3 100644 --- a/docs/general_usage.md +++ b/docs/general_usage.md @@ -53,7 +53,7 @@ Communication from node-to-node requires address queries to be sent to the maste node, since individual nodes may change RF24Network & radio address at any time. Due to the extra data transmissions, node-to-node communication is less efficient. -## General Usage +## Tricks of Trade One thing to keep in mind is the dynamic nature of RF24Mesh, and the need to verify connectivity to the network. For nodes that are constantly transmitting, diff --git a/docs/sphinx/_static/custom_material.css b/docs/sphinx/_static/custom_material.css index 7ff9d22..53bb485 100644 --- a/docs/sphinx/_static/custom_material.css +++ b/docs/sphinx/_static/custom_material.css @@ -1,3 +1,8 @@ +.md-typeset .admonition, +.md-typeset details { + font-size: 0.75rem; +} + .md-typeset .admonition.tip>.admonition-title::before, .md-typeset .admonition.hint>.admonition-title::before { mask-image: url('data:image/svg+xml;charset=utf-8,'); diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py index 96b890a..feffb42 100644 --- a/docs/sphinx/conf.py +++ b/docs/sphinx/conf.py @@ -45,6 +45,7 @@ "breathe", "sphinx_immaterial", "sphinx.ext.intersphinx", + "sphinx.ext.autosectionlabel", ] intersphinx_mapping = { @@ -71,6 +72,9 @@ breathe_projects = {"RF24Mesh": "xml"} breathe_default_project = "RF24Mesh" +breathe_show_define_initializer = True +breathe_show_enumvalue_initializer = True +breathe_domain_by_extension = { "h" : "cpp" } READTHEDOCS = os.environ.get("READTHEDOCS", None) == "True" diff --git a/docs/sphinx/examples/PicoSDK/RF24Mesh_Example.rst b/docs/sphinx/examples/PicoSDK/RF24Mesh_Example.rst index 982a3eb..90b61cc 100644 --- a/docs/sphinx/examples/PicoSDK/RF24Mesh_Example.rst +++ b/docs/sphinx/examples/PicoSDK/RF24Mesh_Example.rst @@ -1,4 +1,4 @@ -RF24Mesh_Example.cpp +RF24Mesh_Example =========================== .. seealso:: diff --git a/docs/sphinx/examples/PicoSDK/RF24Mesh_Example_Master.rst b/docs/sphinx/examples/PicoSDK/RF24Mesh_Example_Master.rst index 308d3f5..6fc89fe 100644 --- a/docs/sphinx/examples/PicoSDK/RF24Mesh_Example_Master.rst +++ b/docs/sphinx/examples/PicoSDK/RF24Mesh_Example_Master.rst @@ -1,4 +1,4 @@ -RF24Mesh_Example_Master.cpp +RF24Mesh_Example_Master =========================== .. seealso:: From 9e34e875f43afac5b823c05036b7adf522144587 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Sun, 31 Oct 2021 20:43:31 -0700 Subject: [PATCH 66/72] node ID max is 255 --- RF24Mesh.h | 6 +++--- docs/main_page.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RF24Mesh.h b/RF24Mesh.h index 66507fb..b53ae3f 100644 --- a/RF24Mesh.h +++ b/RF24Mesh.h @@ -112,7 +112,7 @@ class RF24Mesh * This needs to be called before RF24Mesh::begin(). The parameter value passed can be fetched * via serial connection, eeprom, etc when configuring a large number of nodes. * @note If using RF24Gateway and/or RF24Ethernet, nodeIDs 0 & 1 are used by the master node. - * @param nodeID Can be any unique value ranging from 1 to 253 (reserving 0 for the master node). + * @param nodeID Can be any unique value ranging from 1 to 255 (reserving 0 for the master node). */ void setNodeID(uint8_t nodeID); @@ -154,7 +154,7 @@ class RF24Mesh * Convert an RF24Network address into a nodeId. * @param address If no address is provided, returns the local @ref _nodeID "nodeID", * otherwise a lookup request is sent to the master node - * @return The unique identifier of the node in the range [1, 253] or -1 if node was not found. + * @return The unique identifier of the node in the range [1, 255] or -1 if node was not found. */ int16_t getNodeID(uint16_t address = MESH_BLANK_ID); @@ -184,7 +184,7 @@ class RF24Mesh * @code Serial.println(address, OCT); @endcode * * Results in a lookup request being sent to the master node. - * @param nodeID The unique identifier of the node in the range [1, 253]. + * @param nodeID The unique identifier of the node in the range [1, 255]. * @return The RF24Network address of the node, -2 if successful but not in list, -1 if failed. */ int16_t getAddress(uint8_t nodeID); diff --git a/docs/main_page.md b/docs/main_page.md index 6219073..4e1115d 100644 --- a/docs/main_page.md +++ b/docs/main_page.md @@ -26,7 +26,7 @@ of wireless sensors. ### How does it work? -Nodes are assigned a unique number ranging from 1 to 253, and just about everything else, addressing, routing, etc. is managed by the library. +Nodes are assigned a unique number ranging from 1 to 255, and just about everything else, addressing, routing, etc. is managed by the library. The unique identifier is like an IP address, used to communicate at a high level within the RF24 communication stack and will generally remain static. At the network layer, the physical From e73cca08dcd1c5c00edbf4fe44da247e3dfd3753 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 31 Oct 2021 22:06:45 -0700 Subject: [PATCH 67/72] gimme that RTD badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 28987af..1194dc3 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Arduino CLI build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml) [![PlatformIO build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_platformIO.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_platformIO.yml) [![Pico SDK build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_rp2xxx.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_rp2xxx.yml) +[![Documentation Status](https://readthedocs.org/projects/rf24mesh/badge/?version=latest)](https://rf24mesh.readthedocs.io/en/latest/?badge=latest) RF24Mesh ======== From a8a7f210158b7e17b7f775626ab1d0a83e2dab77 Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Wed, 3 Nov 2021 00:34:04 -0700 Subject: [PATCH 68/72] [docs] fix multiline API signatures --- README.md | 5 +++-- docs/sphinx/_static/custom_material.css | 26 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1194dc3..aa235d3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ + [![Linux build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_linux.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_linux.yml) [![Arduino CLI build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_arduino.yml) [![PlatformIO build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_platformIO.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_platformIO.yml) [![Pico SDK build](https://github.com/nRF24/RF24Mesh/actions/workflows/build_rp2xxx.yml/badge.svg)](https://github.com/nRF24/RF24Mesh/actions/workflows/build_rp2xxx.yml) [![Documentation Status](https://readthedocs.org/projects/rf24mesh/badge/?version=latest)](https://rf24mesh.readthedocs.io/en/latest/?badge=latest) -RF24Mesh -======== +# RF24Mesh + Mesh Networking for RF24Network https://nRF24.github.io/RF24Mesh diff --git a/docs/sphinx/_static/custom_material.css b/docs/sphinx/_static/custom_material.css index 53bb485..aef855b 100644 --- a/docs/sphinx/_static/custom_material.css +++ b/docs/sphinx/_static/custom_material.css @@ -79,3 +79,29 @@ html .md-nav--primary .md-nav__title--site .md-nav__button { background-color: var(--md-default-bg-color--light); margin-right: 0.5rem; } + +/* ************* temp workaround styling multi-line API signatures ******************* */ +/* In the future, we plan to do this without CSS in a more programatic way, but for now... */ + +.md-typeset dl.objdesc>dt.sig-wrap .n + .sig-paren::before, +.md-typeset dl.api-field>dt.sig-wrap .n + .sig-paren::before { + content: "\a "; + white-space: pre; +} + +.md-typeset dl.objdesc>dt.sig-wrap .sig-paren + .n:not(.sig-param)::before, +.md-typeset dl.api-field>dt.sig-wrap .sig-paren + .n:not(.sig-param)::before, +.md-typeset dl.objdesc>dt.sig-wrap .sig-param + .sig-param::before, +.md-typeset dl.api-field>dt.sig-wrap .n.sig-param + .kt::before, +.md-typeset dl.objdesc>dt.sig-wrap .n.sig-param + .kt::before, +.md-typeset dl.api-field>dt.sig-wrap .n.sig-param + .n:not(.sig-param)::before, +.md-typeset dl.objdesc>dt.sig-wrap .n.sig-param + .n:not(.sig-param)::before { + content: "\a "; + white-space: break-spaces; +} + +.md-typeset dl.api-field>dt.sig-wrap .sig-param:before, +.md-typeset dl.objdesc>dt.sig-wrap .sig-param:before { + content: unset; + white-space: pre; +} From 67d25b91a5828adc92bc437a3cf1b6b26a8bfb0d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 14 Nov 2021 14:54:47 -0800 Subject: [PATCH 69/72] use RF24Network master branch in linux workflow --- .github/workflows/build_linux.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 7889613..db26e2e 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -96,7 +96,6 @@ jobs: uses: actions/checkout@v2 with: repository: nRF24/RF24Network - ref: CMake-4-Linux - name: build & install RF24Network run: | From 0df592ed59d926d414e64cdf86a3384ca85c44dc Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 14 Nov 2021 14:55:52 -0800 Subject: [PATCH 70/72] use RF24Network master branch in arduino workflow --- .github/workflows/build_arduino.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build_arduino.yml b/.github/workflows/build_arduino.yml index f854dbb..97e53ba 100644 --- a/.github/workflows/build_arduino.yml +++ b/.github/workflows/build_arduino.yml @@ -98,7 +98,6 @@ jobs: libraries: | - name: RF24 - source-url: https://github.com/nRF24/RF24Network.git - version: CMake-4-Linux - source-path: ./ # - name: RF24Network fqbn: ${{ matrix.fqbn }} From 13cae652b35d76ba1dfb93889bebabfbe4d326fc Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 14 Nov 2021 14:57:12 -0800 Subject: [PATCH 71/72] use RF24Network master branch in PIO workflow --- .github/workflows/build_platformIO.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_platformIO.yml b/.github/workflows/build_platformIO.yml index 016a34a..07a7c01 100644 --- a/.github/workflows/build_platformIO.yml +++ b/.github/workflows/build_platformIO.yml @@ -133,7 +133,7 @@ jobs: - name: Install library dependencies run: | pio lib -g install nrf24/RF24 - pio lib -g install https://github.com/nrf24/RF24Network.git#CMake-4-Linux + pio lib -g install https://github.com/nrf24/RF24Network.git # pio lib -g install nrf24/RF24Network - name: Run PlatformIO From a33695b6a220b05426feb75759be4abd079f621a Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 14 Nov 2021 14:58:26 -0800 Subject: [PATCH 72/72] use RF24* master branch in rp2xxx workflow --- .github/workflows/build_rp2xxx.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build_rp2xxx.yml b/.github/workflows/build_rp2xxx.yml index 27378f0..b8f7b72 100644 --- a/.github/workflows/build_rp2xxx.yml +++ b/.github/workflows/build_rp2xxx.yml @@ -59,14 +59,12 @@ jobs: with: repository: nRF24/RF24Network path: RF24Network - ref: CMake-4-Linux - name: checkout RF24 lib uses: actions/checkout@v2 with: repository: nRF24/RF24 path: RF24 - ref: rp2xxx - name: Install toolchain run: sudo apt update && sudo apt install gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential