diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7c366ee..0000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -sudo: false - -before_install: - - cd ${TMPDIR-/tmp} - - wget -q http://doxygen.nl/files/doxygen-1.9.0.src.tar.gz - - tar -xzvf doxygen-1.9.0.src.tar.gz - - mkdir doxygen_build - - cd doxygen_build - - cmake ../doxygen-1.9.0/ - - make - - export PATH="${TMPDIR-/tmp}/doxygen_build/bin:$PATH" - - cd ${TRAVIS_BUILD_DIR} - -branches: - only: - - master - -script: - - doxygen ./docs/Doxyfile - -deploy: - provider: pages - skip_cleanup: true - local_dir: docs/html - github_token: $GH_REPO_TOKEN - on: - branch: master \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 69bb956..3dfcd1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,38 @@ # Changelog -All notable changes to this project will be documented in this file. +All notable changes to this project will be documented in this file. + +## [1.3.3] - 2022-02-15 + +### Changed +- If attribute retrieval fails with a "not found" try again with the 16 bit version if a 128 bit base uuid is used. + +### Fixed +- Memory leak when deleting client instance. +- IDf version check for data length extension. +- Memory leak when server services changed. +- Compiler warnings for non-esp32 devices. + +## [1.3.2] - 2022-01-15 + +### Fixed +- Initialize advertising complete callback in NimBLEAdvertising constructor. +- Clear client disconnect timer in constructor before initializing. +- Fix missing data when reading large values. +- Fix missing data in notifications when using a large MTU size and more than 270 bytes of data are sent. +- Workaround fix added for cases when the task notification value is not cleared, causing various functions that should block not to block. + +### Added +- `NimBLEClient::getLastError` : Gets the error code of the last function call that produces a return code from the stack. +- `NimBLECharacteristic::notify` : Overload method to send notifications/indications with custom values. +- Added conditional checks for ESP32 specific functions/values to support use of the library on non-esp32 devices. +- Added an alias to use the callback name from the original library `onMtuChanged`. +- `NimBLEClient::setDataLen` and `NimBLEServer::setDataLen`: Data length extension support (IDF version >= 4.3.2 only) +- Config option to set logging level for esp-nimble-cpp + +### Changed +- Critical section calls now use the NimBLE API instead of FreeRTOS directly. This removes the need for a `portMUX_TYPE` variable in the class definitions. +- Removed unnecessary variables in `NimBLEService` and changed the constructor no no longer accept `numHandles` and `inst_id` parameters. ## [1.3.1] - 2021-08-04 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f9e8cd..28780b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ idf_component_register( "src/NimBLEDevice.cpp" "src/NimBLEEddystoneTLM.cpp" "src/NimBLEEddystoneURL.cpp" + "src/NimBLEExtAdvertising.cpp" "src/NimBLEHIDDevice.cpp" "src/NimBLERemoteCharacteristic.cpp" "src/NimBLERemoteDescriptor.cpp" diff --git a/Kconfig b/Kconfig index 069b000..c13fde1 100644 --- a/Kconfig +++ b/Kconfig @@ -1,5 +1,31 @@ menu "ESP-NimBLE-CPP configuration" +choice NIMBLE_CPP_LOG_LEVEL + prompt "NimBLE CPP log verbosity" + default NIMBLE_CPP_LOG_LEVEL_NONE + help + Select NimBLE CPP log verbosity level. + + config NIMBLE_CPP_LOG_LEVEL_NONE + bool "No logs" + config NIMBLE_CPP_LOG_LEVEL_ERROR + bool "Error logs" + config NIMBLE_CPP_LOG_LEVEL_WARNING + bool "Warning logs" + config NIMBLE_CPP_LOG_LEVEL_INFO + bool "Info logs" + config NIMBLE_CPP_LOG_LEVEL_DEBUG + bool "Debug logs" +endchoice #NIMBLE_CPP_LOG_LEVEL + +config NIMBLE_CPP_LOG_LEVEL + int + default 0 if NIMBLE_CPP_LOG_LEVEL_NONE + default 1 if NIMBLE_CPP_LOG_LEVEL_ERROR + default 2 if NIMBLE_CPP_LOG_LEVEL_WARNING + default 3 if NIMBLE_CPP_LOG_LEVEL_INFO + default 4 if NIMBLE_CPP_LOG_LEVEL_DEBUG + config NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT bool "Show NimBLE return codes as text in debug log." default "n" @@ -23,5 +49,24 @@ config NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT Enabling this option will display advertisment types recieved while scanning as text messages in the debug log. This will use approximately 250 bytes of flash memory. + +config NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + bool "Enable timestamps to be stored with attribute values." + default "n" + help + Enabling this option will store the timestamp when an attribute value is updated. + This allows for checking the last update time using getTimeStamp() + or getValue(time_t*). If disabled, the timestamp returned from these functions will be 0. + Disabling timestamps will reduce the memory used for each value. + +config NIMBLE_CPP_ATT_VALUE_INIT_LENGTH + int "Initial attribute value size (bytes) for empty values." + range 1 512 + default 20 + help + Sets the default allocation size (bytes) for each attribute if not specified + when the constructor is called. This is also the size used when a remote + characteristic or descriptor is constructed before a value is read/notifed. + Increasing this will reduce reallocations but increase memory footprint. endmenu diff --git a/docs/Bluetooth 5 features.md b/docs/Bluetooth 5 features.md new file mode 100644 index 0000000..c0d03b5 --- /dev/null +++ b/docs/Bluetooth 5 features.md @@ -0,0 +1,29 @@ +# Bluetooth 5.x features + +## About extended advertising +Extended advertising allows for much more capability and flexibility. + +* Allows for 251 bytes of advertisement data and up to 1650 bytes when chained (configuration dependant) vs 31. + +* New PHY's (physical layers) that allow for faster data rate (2M PHY) or long range/slower data rates (CODED PHY) as well as the original 1M PHY. + +* New periodic advertising, allowing the scanning device to sync with the advertisements of a beacon. This allows for the scanning device to sleep or perform other tasks before the next expected advertisement is sent, preserving cpu cycles and power (To be implemented). +
+ +## Enabling extended advertising +Extended advertising is supported when enabled with the config option `CONFIG_BT_NIMBLE_EXT_ADV` set to a value of 1. This is done in menuconfig under `Component config > Bluetooth > NimBLE options > +Enable extended advertising`. + +When enabled the following will occur: +* `NimBLEScan::start` method will scan on both the 1M PHY and the coded PHY standards automatically. + +* `NimBLEClient::connect` will use the primary PHY the device is listening on, unless specified (see below). + +* `NimBLEClient::setConnectPhy` becomes available to specify the PHY's to connect with (default is all). + +* `NimBLEAdvertising` is no longer available for use and is replaced by `NimBLEExtAdvertising`. `NimBLEDevice::getAdvertising` will now return an instance of `NimBLEExtAdvertising`. + +* `NimBLEAdvertisementData` is no longer available for use and is replaced by `NimBLEExtAdvertisement`. This new class is where everything about the advertisement is configured, including the advertisement intervals and advertisement ended callback. + + + diff --git a/docs/Command_line_config.md b/docs/Command_line_config.md index c22565f..3fe4acb 100644 --- a/docs/Command_line_config.md +++ b/docs/Command_line_config.md @@ -6,6 +6,24 @@ Sets the number of simultaneous connections (esp controller max is 9) - Default value is 3
+`CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED` + +Enable/disable storing the timestamp when an attribute value is updated +This allows for checking the last update time using getTimeStamp() or getValue(time_t*) +If disabled, the timestamp returned from these functions will be 0. +Disabling timestamps will reduce the memory used for each value. +1 = Enabled, 0 = Disabled; Default = Disabled +
+ +`CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH` + +Set the default allocation size (bytes) for each attribute. +If not specified when the constructor is called. This is also the size used when a remote +characteristic or descriptor is constructed before a value is read/notifed. +Increasing this will reduce reallocations but increase memory footprint. +Default value is 20. Range: 1 : 512 (BLE_ATT_ATTR_MAX_LEN) +
+ `CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU` Sets the default MTU size. @@ -24,6 +42,13 @@ If defined, enables debug log messages from the NimBLE host - Uses approx. 32kB of flash memory.
+`CONFIG_NIMBLE_CPP_LOG_LEVEL` + +Define to set the debug log message level from the NimBLE CPP Wrapper. +If not defined it will use the same value as the Arduino core debug level. +Values: 0 = NONE, 1 = ERROR, 2 = WARNING, 3 = INFO, 4+ = DEBUG +
+ `CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT` If defined, NimBLE host return codes will be printed as text in debug log messages. diff --git a/docs/Doxyfile b/docs/Doxyfile index 34d13cc..1d9a18b 100644 --- a/docs/Doxyfile +++ b/docs/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. @@ -32,13 +32,13 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "esp-nimble-cpp / NimBLE-Arduino" +PROJECT_NAME = esp-nimble-cpp # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.3.1 +PROJECT_NUMBER = 1.3.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = docs +OUTPUT_DIRECTORY = . # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -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 = @@ -449,6 +460,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 #--------------------------------------------------------------------------- @@ -512,6 +536,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 @@ -549,11 +580,18 @@ HIDE_IN_BODY_DOCS = YES 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 = NO @@ -792,7 +830,10 @@ 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 @@ -823,13 +864,15 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = . +INPUT = ../CHANGELOG.md \ + . \ + ../src # 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 @@ -842,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 \ @@ -911,11 +956,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = ./README.md \ - ./src/FreeRTOS.h \ - ./src/FreeRTOS.cpp \ - ./examples \ - ./CMakelists.txt +EXCLUDE = ../src/nimconfig_rename.h # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1112,6 +1153,44 @@ 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. +# 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 +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# 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. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1123,13 +1202,6 @@ VERBATIM_HEADERS = YES 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 = 5 - # 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 @@ -1300,10 +1372,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. @@ -1345,8 +1418,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 @@ -1376,7 +1449,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. @@ -1421,7 +1494,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. @@ -1429,8 +1503,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. @@ -1438,16 +1512,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 = @@ -1459,9 +1533,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 = @@ -1542,8 +1616,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. @@ -1588,7 +1662,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. @@ -1618,7 +1692,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. @@ -1665,7 +1740,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. @@ -1678,8 +1754,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 = @@ -1843,9 +1920,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. @@ -2084,6 +2163,10 @@ DOCBOOK_PROGRAMLISTING = NO GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2361,10 +2444,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. @@ -2554,9 +2659,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/examples/Bluetooth_5/NimBLE_extended_client/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_client/CMakeLists.txt new file mode 100644 index 0000000..f46b44a --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32c3 esp32s3) +project(NimBLE_extended_client) diff --git a/examples/Bluetooth_5/NimBLE_extended_client/Makefile b/examples/Bluetooth_5/NimBLE_extended_client/Makefile new file mode 100644 index 0000000..2e4842d --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := NimBLE_extended_client + +include $(IDF_PATH)/make/project.mk diff --git a/examples/Bluetooth_5/NimBLE_extended_client/main/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_client/main/CMakeLists.txt new file mode 100644 index 0000000..0a5a557 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/Bluetooth_5/NimBLE_extended_client/main/component.mk b/examples/Bluetooth_5/NimBLE_extended_client/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/Bluetooth_5/NimBLE_extended_client/main/main.cpp b/examples/Bluetooth_5/NimBLE_extended_client/main/main.cpp new file mode 100644 index 0000000..56881d0 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_client/main/main.cpp @@ -0,0 +1,169 @@ + +/** NimBLE Extended Client Demo: + * + * Demonstrates the Bluetooth 5.x client capabilities. + * + * Created: on April 2 2022 + * Author: H2zero + * +*/ +#include + +extern "C" void app_main(void); + +void scanEndedCB(NimBLEScanResults results); + +#define SERVICE_UUID "ABCD" +#define CHARACTERISTIC_UUID "1234" + +static NimBLEAdvertisedDevice* advDevice; +static bool doConnect = false; +static uint32_t scanTime = 10; /* 0 = scan forever */ + +/* Define the PHY's to use when connecting to peer devices, can be 1, 2, or all 3 (default).*/ +static uint8_t connectPhys = BLE_GAP_LE_PHY_CODED_MASK | BLE_GAP_LE_PHY_1M_MASK /*| BLE_GAP_LE_PHY_2M_MASK */ ; + +/* Define a class to handle the callbacks for client connection events */ +class ClientCallbacks : public NimBLEClientCallbacks { + void onConnect(NimBLEClient* pClient) { + printf("Connected\n"); + }; + + void onDisconnect(NimBLEClient* pClient) { + printf("%s Disconnected - Starting scan\n", pClient->getPeerAddress().toString().c_str()); + NimBLEDevice::getScan()->start(scanTime, scanEndedCB); + }; +}; + + +/* Define a class to handle the callbacks when advertisements are received */ +class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { + + void onResult(NimBLEAdvertisedDevice* advertisedDevice) { + printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str()); + if(advertisedDevice->isAdvertisingService(NimBLEUUID("ABCD"))) + { + printf("Found Our Service\n"); + /* Ready to connect now */ + doConnect = true; + /* Save the device reference in a global for the client to use*/ + advDevice = advertisedDevice; + /* stop scan before connecting */ + NimBLEDevice::getScan()->stop(); + } + }; +}; + + +/* Callback to process the results of the last scan or restart it */ +void scanEndedCB(NimBLEScanResults results){ + printf("Scan Ended\n"); + if (!doConnect) { /* Don't start the scan while connecting */ + NimBLEDevice::getScan()->start(scanTime, scanEndedCB); + } +} + + +/* Handles the provisioning of clients and connects / interfaces with the server */ +bool connectToServer() { + NimBLEClient* pClient = nullptr; + + pClient = NimBLEDevice::createClient(); + pClient->setClientCallbacks(new ClientCallbacks, false); + + /* Set the PHY's to use for this connection. This is a bitmask that represents the PHY's: + * * 0x01 BLE_GAP_LE_PHY_1M_MASK + * * 0x02 BLE_GAP_LE_PHY_2M_MASK + * * 0x04 BLE_GAP_LE_PHY_CODED_MASK + * Combine these with OR ("|"), eg BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK | BLE_GAP_LE_PHY_CODED_MASK; + */ + pClient->setConnectPhy(connectPhys); + + /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ + pClient->setConnectTimeout(10); + + if (!pClient->connect(advDevice)) { + /* Created a client but failed to connect, don't need to keep it as it has no data */ + NimBLEDevice::deleteClient(pClient); + printf("Failed to connect, deleted client\n"); + return false; + } + + printf("Connected to: %s RSSI: %d\n", + pClient->getPeerAddress().toString().c_str(), + pClient->getRssi()); + + /* Now we can read/write/subscribe the charateristics of the services we are interested in */ + NimBLERemoteService* pSvc = nullptr; + NimBLERemoteCharacteristic* pChr = nullptr; + + pSvc = pClient->getService(SERVICE_UUID); + + if (pSvc) { + pChr = pSvc->getCharacteristic(CHARACTERISTIC_UUID); + + if (pChr) { + // Read the value of the characteristic. + if (pChr->canRead()) { + std::string value = pChr->readValue(); + printf("Characteristic value: %s\n", value.c_str()); + } + } + + } else { + printf("ABCD service not found.\n"); + } + + NimBLEDevice::deleteClient(pClient); + printf("Done with this device!\n"); + return true; +} + +void connectTask (void * parameter){ + /* Loop here until we find a device we want to connect to */ + for (;;) { + if (doConnect) { + /* Found a device we want to connect to, do it now */ + if (connectToServer()) { + printf("Success!, scanning for more!\n"); + } else { + printf("Failed to connect, starting scan\n"); + } + + doConnect = false; + NimBLEDevice::getScan()->start(scanTime, scanEndedCB); + } + vTaskDelay(pdMS_TO_TICKS(10)); + } + + vTaskDelete(NULL); +} + +void app_main (void) { + printf("Starting NimBLE Client\n"); + /* Create a task to handle connecting to peers */ + xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL); + + /* Initialize NimBLE, no device name specified as we are not advertising */ + NimBLEDevice::init(""); + NimBLEScan* pScan = NimBLEDevice::getScan(); + + /* create a callback that gets called when advertisers are found */ + pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); + + /* Set scan interval (how often) and window (how long) in milliseconds */ + pScan->setInterval(97); + pScan->setWindow(67); + + /* Active scan will gather scan response data from advertisers + * but will use more energy from both devices + */ + pScan->setActiveScan(true); + + /* Start scanning for advertisers for the scan time specified (in seconds) 0 = forever + * Optional callback for when scanning stops. + */ + pScan->start(scanTime, scanEndedCB); + + printf("Scanning for peripherals\n"); +} diff --git a/examples/Bluetooth_5/NimBLE_extended_server/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_server/CMakeLists.txt new file mode 100644 index 0000000..c58174a --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32c3 esp32s3) +project(NimBLE_extended_server) diff --git a/examples/Bluetooth_5/NimBLE_extended_server/Makefile b/examples/Bluetooth_5/NimBLE_extended_server/Makefile new file mode 100644 index 0000000..a18cf9f --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := NimBLE_extended_server + +include $(IDF_PATH)/make/project.mk diff --git a/examples/Bluetooth_5/NimBLE_extended_server/main/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_server/main/CMakeLists.txt new file mode 100644 index 0000000..0a5a557 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/Bluetooth_5/NimBLE_extended_server/main/component.mk b/examples/Bluetooth_5/NimBLE_extended_server/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp b/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp new file mode 100644 index 0000000..096a281 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_server/main/main.cpp @@ -0,0 +1,139 @@ +/** NimBLE Extended Server Demo: + * + * Demonstrates the Bluetooth 5.x extended advertising capabilities. + * + * This demo will advertise a long data string on the CODED and 1M Phy's and + * starts a server allowing connection over either PHY's. It will advertise for + * 5 seconds then sleep for 20 seconds, if a client connects it will sleep once + * it has disconnected then repeats. + * + * Created: on April 2 2022 + * Author: H2zero + * +*/ + +#include "NimBLEDevice.h" +#include "esp_sleep.h" + +extern "C" void app_main(void); + +#define SERVICE_UUID "ABCD" +#define CHARACTERISTIC_UUID "1234" + +/* Time in milliseconds to advertise */ +static uint32_t advTime = 5000; + +/* Time to sleep between advertisements */ +static uint32_t sleepSeconds = 20; + +/* Primary PHY used for advertising, can be one of BLE_HCI_LE_PHY_1M or BLE_HCI_LE_PHY_CODED */ +static uint8_t primaryPhy = BLE_HCI_LE_PHY_CODED; + +/* Secondary PHY used for advertising and connecting, + * can be one of BLE_HCI_LE_PHY_1M, BLE_HCI_LE_PHY_2M or BLE_HCI_LE_PHY_CODED + */ +static uint8_t secondaryPhy = BLE_HCI_LE_PHY_1M; + + +/* Handler class for server events */ +class ServerCallbacks: public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) { + printf("Client connected: %s\n", NimBLEAddress(desc->peer_ota_addr).toString().c_str()); + }; + + void onDisconnect(NimBLEServer* pServer) { + printf("Client disconnected - sleeping for %u seconds\n", sleepSeconds); + esp_deep_sleep_start(); + }; +}; + +/* Callback class to handle advertising events */ +class advertisingCallbacks: public NimBLEExtAdvertisingCallbacks { + void onStopped(NimBLEExtAdvertising* pAdv, int reason, uint8_t inst_id) { + /* Check the reason advertising stopped, don't sleep if client is connecting */ + printf("Advertising instance %u stopped\n", inst_id); + switch (reason) { + case 0: + printf("Client connecting\n"); + return; + case BLE_HS_ETIMEOUT: + printf("Time expired - sleeping for %u seconds\n", sleepSeconds); + break; + default: + break; + } + + esp_deep_sleep_start(); + } +}; + +void app_main (void) { + NimBLEDevice::init("Extended advertiser"); + + /* Create the server and add the services/characteristics/descriptors */ + NimBLEServer *pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks); + + NimBLEService *pService = pServer->createService(SERVICE_UUID); + NimBLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID, + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE | + NIMBLE_PROPERTY::NOTIFY); + + pCharacteristic->setValue("Hello World"); + + /* Start the services */ + pService->start(); + + /* + * Create an extended advertisement with the instance ID 0 and set the PHY's. + * Multiple instances can be added as long as the instance ID is incremented. + */ + NimBLEExtAdvertisement extAdv(primaryPhy, secondaryPhy); + + /* Set the advertisement as connectable */ + extAdv.setConnectable(true); + + /* As per Bluetooth specification, extended advertising cannot be both scannable and connectable */ + extAdv.setScannable(false); // The default is false, set here for demonstration. + + /* Extended advertising allows for 251 bytes (minus header bytes ~20) in a single advertisement or up to 1650 if chained */ + extAdv.setServiceData(NimBLEUUID(SERVICE_UUID), std::string("Extended Advertising Demo.\r\n" + "Extended advertising allows for " + "251 bytes of data in a single advertisement,\r\n" + "or up to 1650 bytes with chaining.\r\n" + "This example message is 226 bytes long " + "and is using CODED_PHY for long range.")); + + extAdv.setCompleteServices16({NimBLEUUID(SERVICE_UUID)}); + + /* When extended advertising is enabled `NimBLEDevice::getAdvertising` returns a pointer to `NimBLEExtAdvertising */ + NimBLEExtAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + + /* Set the callbacks for advertising events */ + pAdvertising->setCallbacks(new advertisingCallbacks); + + /* + * NimBLEExtAdvertising::setInstanceData takes the instance ID and + * a reference to a `NimBLEExtAdvertisement` object. This sets the data + * that will be advertised for this instance ID, returns true if successful. + * + * Note: It is safe to create the advertisement as a local variable if setInstanceData + * is called before exiting the code block as the data will be copied. + */ + if (pAdvertising->setInstanceData(0, extAdv)) { + /* + * `NimBLEExtAdvertising::start` takes the advertisement instance ID to start + * and a duration in milliseconds or a max number of advertisements to send (or both). + */ + if (pAdvertising->start(0, advTime)) { + printf("Started advertising\n"); + } else { + printf("Failed to start advertising\n"); + } + } else { + printf("Failed to register advertisment data\n"); + } + + esp_sleep_enable_timer_wakeup(sleepSeconds * 1000000); +} diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_multi_advertiser/CMakeLists.txt new file mode 100644 index 0000000..7cfce86 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32c3 esp32s3) +project(NimBLE_multi_advertiser) diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/Makefile b/examples/Bluetooth_5/NimBLE_multi_advertiser/Makefile new file mode 100644 index 0000000..501edc9 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := NimBLE_multi_advertiser + +include $(IDF_PATH)/make/project.mk diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/main/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/CMakeLists.txt new file mode 100644 index 0000000..0a5a557 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/main/component.mk b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp new file mode 100644 index 0000000..fbbca34 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_multi_advertiser/main/main.cpp @@ -0,0 +1,170 @@ +/** NimBLE Multi Advertiser Demo: + * + * Demonstrates the Bluetooth 5.x extended advertising capabilities. + * + * This demo will advertise 2 advertisements, and extended scannable instance + * and a connectable legacy instance. They will advertise for 5 seconds then + * sleep for 20 seconds. The extended scannable instance will use the scan + * request callback to update it's data when a scan response is requested. + * + * Created: on April 9 2022 + * Author: H2zero + * +*/ + +#include "NimBLEDevice.h" +#include "esp_sleep.h" + +extern "C" void app_main(void); + +#define SERVICE_UUID "ABCD" +#define CHARACTERISTIC_UUID "1234" + +/* Time in milliseconds to advertise */ +static uint32_t advTime = 5000; + +/* Time to sleep between advertisements */ +static uint32_t sleepTime = 20; + +/* Primary PHY used for advertising, can be one of BLE_HCI_LE_PHY_1M or BLE_HCI_LE_PHY_CODED */ +static uint8_t primaryPhy = BLE_HCI_LE_PHY_CODED; + +/* Secondary PHY used for advertising and connecting, + * can be one of BLE_HCI_LE_PHY_1M, BLE_HCI_LE_PHY_2M or BLE_HCI_LE_PHY_CODED + */ +static uint8_t secondaryPhy = BLE_HCI_LE_PHY_1M; + + +/* Handler class for server events */ +class ServerCallbacks: public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) { + printf("Client connected: %s\n", NimBLEAddress(desc->peer_ota_addr).toString().c_str()); + }; + + void onDisconnect(NimBLEServer* pServer) { + printf("Client disconnected\n"); + // if still advertising we won't sleep yet. + if (!pServer->getAdvertising()->isAdvertising()) { + printf("Sleeping for %u seconds\n", sleepTime); + esp_deep_sleep_start(); + } + }; +}; + +/* Callback class to handle advertising events */ +class advCallbacks: public NimBLEExtAdvertisingCallbacks { + void onStopped(NimBLEExtAdvertising* pAdv, int reason, uint8_t inst_id) { + /* Check the reason advertising stopped, don't sleep if client is connecting */ + printf("Advertising instance %u stopped\n", inst_id); + switch (reason) { + case 0: + printf(" client connecting\n"); + return; + case BLE_HS_ETIMEOUT: + printf("Time expired - sleeping for %u seconds\n", sleepTime); + break; + default: + break; + } + + esp_deep_sleep_start(); + } + + bool m_updatedSR = false; + + void onScanRequest(NimBLEExtAdvertising* pAdv, uint8_t inst_id, NimBLEAddress addr) { + printf("Scan request for instance %u\n", inst_id); + // if the data has already been updated we don't need to change it again. + if (!m_updatedSR) { + printf("Updating scan data\n"); + NimBLEExtAdvertisement sr; + sr.setServiceData(NimBLEUUID(SERVICE_UUID), std::string("Hello from scan response!")); + pAdv->setScanResponseData(inst_id, sr); + m_updatedSR = true; + } + } +}; + +void app_main (void) { + NimBLEDevice::init("Multi advertiser"); + + /* Create a server for our legacy advertiser */ + NimBLEServer *pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks); + + NimBLEService *pService = pServer->createService(SERVICE_UUID); + NimBLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID, + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE | + NIMBLE_PROPERTY::NOTIFY); + + pCharacteristic->setValue("Hello World"); + + /* Start the service */ + pService->start(); + + /* Create our multi advertising instances */ + + // extended scannable instance advertising on coded and 1m PHY's. + NimBLEExtAdvertisement extScannable(primaryPhy, secondaryPhy); + + // Legacy advertising as a connectable device. + NimBLEExtAdvertisement legacyConnectable; + + // Optional scan response data. + NimBLEExtAdvertisement legacyScanResponse; + + /* As per Bluetooth specification, extended advertising cannot be both scannable and connectable */ + extScannable.setScannable(true); + extScannable.setConnectable(false); + + /* Set the initial data */ + extScannable.setServiceData(NimBLEUUID(SERVICE_UUID), std::string("Scan me!")); + + /* enable the scan response callback, we will use this to update the data. */ + extScannable.enableScanRequestCallback(true); + + /* Optional custom address for this advertisment. */ + legacyConnectable.setAddress(NimBLEAddress("DE:AD:BE:EF:BA:AD")); + + /* Set the advertising data. */ + legacyConnectable.setName("Legacy"); + legacyConnectable.setCompleteServices16({NimBLEUUID(SERVICE_UUID)}); + + /* Set the legacy and connectable flags. */ + legacyConnectable.setLegacyAdvertising(true); + legacyConnectable.setConnectable(true); + + /* Put some data in the scan response if desired. */ + legacyScanResponse.setServiceData(NimBLEUUID(SERVICE_UUID), "Legacy SR"); + + /* Get the advertising ready */ + NimBLEExtAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + + /* Set the callbacks to handle advertising events */ + pAdvertising->setCallbacks(new advCallbacks); + + /* Set instance data. + * Up to 5 instances can be used if configured in menuconfig, instance 0 is always available. + * + * We will set the extended scannable data on instance 0 and the legacy data on instance 1. + * Note that the legacy scan response data needs to be set to the same instance (1). + */ + if (pAdvertising->setInstanceData( 0, extScannable ) && + pAdvertising->setInstanceData( 1, legacyConnectable ) && + pAdvertising->setScanResponseData( 1, legacyScanResponse )) { + /* + * `NimBLEExtAdvertising::start` takes the advertisement instance ID to start + * and a duration in milliseconds or a max number of advertisements to send (or both). + */ + if (pAdvertising->start(0, advTime) && pAdvertising->start(1, advTime)) { + printf("Started advertising\n"); + } else { + printf("Failed to start advertising\n"); + } + } else { + printf("Failed to register advertisment data\n"); + } + + esp_sleep_enable_timer_wakeup(sleepTime * 1000000); +} diff --git a/src/HIDTypes.h b/src/HIDTypes.h index 726b84b..8ee31da 100644 --- a/src/HIDTypes.h +++ b/src/HIDTypes.h @@ -45,13 +45,8 @@ /* of data as per HID Class standard */ /* Main items */ -#ifdef ARDUINO_ARCH_ESP32 -#define HIDINPUT(size) (0x80 | size) -#define HIDOUTPUT(size) (0x90 | size) -#else -#define INPUT(size) (0x80 | size) -#define OUTPUT(size) (0x90 | size) -#endif +#define HIDINPUT(size) (0x80 | size) +#define HIDOUTPUT(size) (0x90 | size) #define FEATURE(size) (0xb0 | size) #define COLLECTION(size) (0xa0 | size) #define END_COLLECTION(size) (0xc0 | size) diff --git a/src/NimBLE2904.cpp b/src/NimBLE2904.cpp index 80318b5..282eff5 100644 --- a/src/NimBLE2904.cpp +++ b/src/NimBLE2904.cpp @@ -16,11 +16,8 @@ * See also: * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #include "NimBLE2904.h" @@ -86,5 +83,4 @@ void NimBLE2904::setUnit(uint16_t unit) { setValue((uint8_t*) &m_data, sizeof(m_data)); } // setUnit -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ diff --git a/src/NimBLE2904.h b/src/NimBLE2904.h index d8800dd..29dde51 100644 --- a/src/NimBLE2904.h +++ b/src/NimBLE2904.h @@ -14,11 +14,8 @@ #ifndef MAIN_NIMBLE2904_H_ #define MAIN_NIMBLE2904_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #include "NimBLEDescriptor.h" @@ -82,6 +79,5 @@ private: BLE2904_Data m_data; }; // BLE2904 -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /* MAIN_NIMBLE2904_H_ */ diff --git a/src/NimBLEAddress.cpp b/src/NimBLEAddress.cpp index e1d3e54..b8df5ac 100644 --- a/src/NimBLEAddress.cpp +++ b/src/NimBLEAddress.cpp @@ -11,7 +11,7 @@ * Created on: Jul 2, 2017 * Author: kolban */ -#include "sdkconfig.h" +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) #include diff --git a/src/NimBLEAddress.h b/src/NimBLEAddress.h index 50f9231..a6e10a0 100644 --- a/src/NimBLEAddress.h +++ b/src/NimBLEAddress.h @@ -14,10 +14,15 @@ #ifndef COMPONENTS_NIMBLEADDRESS_H_ #define COMPONENTS_NIMBLEADDRESS_H_ -#include "sdkconfig.h" +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "nimble/ble.h" +#else +#include "nimble/nimble/include/nimble/ble.h" +#endif + /**** FIX COMPILATION ****/ #undef min #undef max diff --git a/src/NimBLEAdvertisedDevice.cpp b/src/NimBLEAdvertisedDevice.cpp index ecfd498..29c9532 100644 --- a/src/NimBLEAdvertisedDevice.cpp +++ b/src/NimBLEAdvertisedDevice.cpp @@ -11,17 +11,17 @@ * Created on: Jul 3, 2017 * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) #include "NimBLEDevice.h" #include "NimBLEAdvertisedDevice.h" #include "NimBLEUtils.h" #include "NimBLELog.h" +#include + static const char* LOG_TAG = "NimBLEAdvertisedDevice"; @@ -71,7 +71,7 @@ uint8_t NimBLEAdvertisedDevice::getAdvType() { * @return The appearance of the advertised device. */ uint16_t NimBLEAdvertisedDevice::getAppearance() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_APPEARANCE, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -89,7 +89,7 @@ uint16_t NimBLEAdvertisedDevice::getAppearance() { * @return The advertisement interval in 0.625ms units. */ uint16_t NimBLEAdvertisedDevice::getAdvInterval() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -107,7 +107,7 @@ uint16_t NimBLEAdvertisedDevice::getAdvInterval() { * @return The preferred min connection interval in 1.25ms units. */ uint16_t NimBLEAdvertisedDevice::getMinInterval() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -125,7 +125,7 @@ uint16_t NimBLEAdvertisedDevice::getMinInterval() { * @return The preferred max connection interval in 1.25ms units. */ uint16_t NimBLEAdvertisedDevice::getMaxInterval() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -143,7 +143,7 @@ uint16_t NimBLEAdvertisedDevice::getMaxInterval() { * @return The manufacturer data of the advertised device. */ std::string NimBLEAdvertisedDevice::getManufacturerData() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_MFG_DATA, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -161,7 +161,7 @@ std::string NimBLEAdvertisedDevice::getManufacturerData() { * @return The URI data. */ std::string NimBLEAdvertisedDevice::getURI() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_URI, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -179,7 +179,7 @@ std::string NimBLEAdvertisedDevice::getURI() { * @return The name of the advertised device. */ std::string NimBLEAdvertisedDevice::getName() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_COMP_NAME, 0, &data_loc) > 0 || findAdvField(BLE_HS_ADV_TYPE_INCOMP_NAME, 0, &data_loc) > 0) @@ -216,7 +216,7 @@ NimBLEScan* NimBLEAdvertisedDevice::getScan() { * @brief Get the number of target addresses. * @return The number of addresses. */ -size_t NimBLEAdvertisedDevice::getTargetAddressCount() { +uint8_t NimBLEAdvertisedDevice::getTargetAddressCount() { uint8_t count = 0; count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR); @@ -234,7 +234,7 @@ size_t NimBLEAdvertisedDevice::getTargetAddressCount() { NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) { ble_hs_adv_field *field = nullptr; uint8_t count = 0; - uint8_t data_loc = 0xFF; + size_t data_loc = ULONG_MAX; index++; count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR, index, &data_loc); @@ -244,7 +244,7 @@ NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) { count = findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR, index, &data_loc); } - if(count > 0 && data_loc != 0xFF) { + if(count > 0 && data_loc != ULONG_MAX) { field = (ble_hs_adv_field *)&m_payload[data_loc]; if(field->length < index * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) { index -= count - field->length / BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN; @@ -266,9 +266,9 @@ NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) { std::string NimBLEAdvertisedDevice::getServiceData(uint8_t index) { ble_hs_adv_field *field = nullptr; uint8_t bytes; - uint8_t data_loc = findServiceData(index, &bytes); + size_t data_loc = findServiceData(index, &bytes); - if(data_loc != 0xFF) { + if(data_loc != ULONG_MAX) { field = (ble_hs_adv_field *)&m_payload[data_loc]; if(field->length > bytes) { return std::string((char*)(field->value + bytes), field->length - bytes - 1); @@ -288,9 +288,9 @@ std::string NimBLEAdvertisedDevice::getServiceData(const NimBLEUUID &uuid) { ble_hs_adv_field *field = nullptr; uint8_t bytes; uint8_t index = 0; - uint8_t data_loc = findServiceData(index, &bytes); + size_t data_loc = findServiceData(index, &bytes); + size_t plSize = m_payload.size() - 2; uint8_t uuidBytes = uuid.bitSize() / 8; - uint8_t plSize = m_payload.size() - 2; while(data_loc < plSize) { field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -315,9 +315,9 @@ std::string NimBLEAdvertisedDevice::getServiceData(const NimBLEUUID &uuid) { NimBLEUUID NimBLEAdvertisedDevice::getServiceDataUUID(uint8_t index) { ble_hs_adv_field *field = nullptr; uint8_t bytes; - uint8_t data_loc = findServiceData(index, &bytes); + size_t data_loc = findServiceData(index, &bytes); - if(data_loc != 0xFF) { + if(data_loc != ULONG_MAX) { field = (ble_hs_adv_field *)&m_payload[data_loc]; if(field->length >= bytes) { return NimBLEUUID(field->value, bytes, false); @@ -332,10 +332,10 @@ NimBLEUUID NimBLEAdvertisedDevice::getServiceDataUUID(uint8_t index) { * @brief Find the service data at the index. * @param [in] index The index of the service data to find. * @param [in] bytes A pointer to storage for the number of the bytes in the UUID. - * @return The index in the vector where the data is located, 0xFF if not found. + * @return The index in the vector where the data is located, ULONG_MAX if not found. */ -uint8_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) { - uint8_t data_loc = 0; +size_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) { + size_t data_loc = 0; uint8_t found = 0; *bytes = 0; @@ -360,7 +360,7 @@ uint8_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) { return data_loc; } - return 0xFF; + return ULONG_MAX; } @@ -368,7 +368,7 @@ uint8_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) { * @brief Get the count of advertised service data UUIDS * @return The number of service data UUIDS in the vector. */ -size_t NimBLEAdvertisedDevice::getServiceDataCount() { +uint8_t NimBLEAdvertisedDevice::getServiceDataCount() { uint8_t count = 0; count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16); @@ -386,7 +386,7 @@ size_t NimBLEAdvertisedDevice::getServiceDataCount() { */ NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID(uint8_t index) { uint8_t count = 0; - uint8_t data_loc = 0; + size_t data_loc = 0; uint8_t uuidBytes = 0; uint8_t type = BLE_HS_ADV_TYPE_INCOMP_UUIDS16; ble_hs_adv_field *field = nullptr; @@ -433,7 +433,7 @@ NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID(uint8_t index) { * @brief Get the number of services advertised * @return The count of services in the advertising packet. */ -size_t NimBLEAdvertisedDevice::getServiceUUIDCount() { +uint8_t NimBLEAdvertisedDevice::getServiceUUIDCount() { uint8_t count = 0; count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS16); @@ -469,7 +469,7 @@ bool NimBLEAdvertisedDevice::isAdvertisingService(const NimBLEUUID &uuid) { * @return The TX Power of the advertised device. */ int8_t NimBLEAdvertisedDevice::getTXPower() { - uint8_t data_loc = 0; + size_t data_loc = 0; if(findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL, 0, &data_loc) > 0) { ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; @@ -583,17 +583,60 @@ bool NimBLEAdvertisedDevice::haveTXPower() { } // haveTXPower -uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, uint8_t *data_loc) { - ble_hs_adv_field *field = nullptr; - uint8_t data = 0; - uint8_t length = m_payload.size(); - uint8_t count = 0; +#if CONFIG_BT_NIMBLE_EXT_ADV +/** + * @brief Get the set ID of the extended advertisement. + * @return The set ID. + */ +uint8_t NimBLEAdvertisedDevice::getSetId() { + return m_sid; +} // getSetId - if(length < 2) { + +/** + * @brief Get the primary PHY used by this advertisement. + * @return The PHY type, one of: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_CODED + */ +uint8_t NimBLEAdvertisedDevice::getPrimaryPhy() { + return m_primPhy; +} // getPrimaryPhy + + +/** + * @brief Get the primary PHY used by this advertisement. + * @return The PHY type, one of: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_2M + * * BLE_HCI_LE_PHY_CODED + */ +uint8_t NimBLEAdvertisedDevice::getSecondaryPhy() { + return m_secPhy; +} // getSecondaryPhy + + +/** + * @brief Get the periodic interval of the advertisement. + * @return The periodic advertising interval, 0 if not periodic advertising. + */ +uint16_t NimBLEAdvertisedDevice::getPeriodicInterval() { + return m_periodicItvl; +} // getPeriodicInterval +#endif + + +uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, size_t * data_loc) { + ble_hs_adv_field *field = nullptr; + size_t length = m_payload.size(); + size_t data = 0; + uint8_t count = 0; + + if (length < 3) { return count; } - while (length > 1) { + while (length > 2) { field = (ble_hs_adv_field*)&m_payload[data]; if (field->length >= length) { @@ -601,7 +644,7 @@ uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, uint8_ } if (field->type == type) { - switch(type) { + switch (type) { case BLE_HS_ADV_TYPE_INCOMP_UUIDS16: case BLE_HS_ADV_TYPE_COMP_UUIDS16: count += field->length / 2; @@ -627,8 +670,8 @@ uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, uint8_ break; } - if(data_loc != nullptr) { - if(index == 0 || count >= index) { + if (data_loc != nullptr) { + if (index == 0 || count >= index) { break; } } @@ -638,7 +681,7 @@ uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, uint8_ data += 1 + field->length; } - if(data_loc != nullptr && field != nullptr) { + if (data_loc != nullptr && field != nullptr) { *data_loc = data; } @@ -659,8 +702,13 @@ void NimBLEAdvertisedDevice::setAddress(NimBLEAddress address) { * @brief Set the adFlag for this device. * @param [in] advType The advertisement flag data from the advertisement. */ -void NimBLEAdvertisedDevice::setAdvType(uint8_t advType) { +void NimBLEAdvertisedDevice::setAdvType(uint8_t advType, bool isLegacyAdv) { m_advType = advType; +#if CONFIG_BT_NIMBLE_EXT_ADV + m_isLegacyAdv = isLegacyAdv; +#else + (void)isLegacyAdv; +#endif } // setAdvType @@ -705,10 +753,10 @@ std::string NimBLEAdvertisedDevice::toString() { res += val; } - if(haveServiceData()) { - size_t count = getServiceDataCount(); + if (haveServiceData()) { + uint8_t count = getServiceDataCount(); res += "\nService Data:"; - for(size_t i = 0; i < count; i++) { + for(uint8_t i = 0; i < count; i++) { res += "\nUUID: " + std::string(getServiceDataUUID(i)); res += ", Data: " + getServiceData(i); } @@ -784,6 +832,33 @@ size_t NimBLEAdvertisedDevice::getPayloadLength() { } // getPayloadLength -#endif // #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif /* CONFIG_BT_ENABLED */ +/** + * @brief Check if this device is advertising as connectable. + * @return True if the device is connectable. + */ +bool NimBLEAdvertisedDevice::isConnectable() { +#if CONFIG_BT_NIMBLE_EXT_ADV + if (m_isLegacyAdv) { + return m_advType == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND || + m_advType == BLE_HCI_ADV_RPT_EVTYPE_DIR_IND; + } +#endif + return (m_advType & BLE_HCI_ADV_CONN_MASK) || + (m_advType & BLE_HCI_ADV_DIRECT_MASK); +} // isConnectable + + +/** + * @brief Check if this advertisement is a legacy or extended type + * @return True if legacy (Bluetooth 4.x), false if extended (bluetooth 5.x). + */ +bool NimBLEAdvertisedDevice::isLegacyAdvertisement() { +#if CONFIG_BT_NIMBLE_EXT_ADV + return m_isLegacyAdv; +# else + return true; +#endif +} // isLegacyAdvertisement + +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ diff --git a/src/NimBLEAdvertisedDevice.h b/src/NimBLEAdvertisedDevice.h index 7d378ed..772bab9 100644 --- a/src/NimBLEAdvertisedDevice.h +++ b/src/NimBLEAdvertisedDevice.h @@ -14,20 +14,22 @@ #ifndef COMPONENTS_NIMBLEADVERTISEDDEVICE_H_ #define COMPONENTS_NIMBLEADVERTISEDDEVICE_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) #include "NimBLEAddress.h" #include "NimBLEScan.h" #include "NimBLEUUID.h" +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_hs_adv.h" +#else +#include "nimble/nimble/host/include/host/ble_hs_adv.h" +#endif #include #include +#include class NimBLEScan; @@ -69,7 +71,7 @@ public: std::string getName(); int getRSSI(); NimBLEScan* getScan(); - size_t getServiceDataCount(); + uint8_t getServiceDataCount(); std::string getServiceData(uint8_t index = 0); std::string getServiceData(const NimBLEUUID &uuid); @@ -109,9 +111,9 @@ public: NimBLEUUID getServiceDataUUID(uint8_t index = 0); NimBLEUUID getServiceUUID(uint8_t index = 0); - size_t getServiceUUIDCount(); + uint8_t getServiceUUIDCount(); NimBLEAddress getTargetAddress(uint8_t index = 0); - size_t getTargetAddressCount(); + uint8_t getTargetAddressCount(); int8_t getTXPower(); uint8_t* getPayload(); uint8_t getAdvLength(); @@ -131,16 +133,30 @@ public: bool haveTargetAddress(); bool haveURI(); std::string toString(); + bool isConnectable(); + bool isLegacyAdvertisement(); +#if CONFIG_BT_NIMBLE_EXT_ADV + uint8_t getSetId(); + uint8_t getPrimaryPhy(); + uint8_t getSecondaryPhy(); + uint16_t getPeriodicInterval(); +#endif private: friend class NimBLEScan; void setAddress(NimBLEAddress address); - void setAdvType(uint8_t advType); + void setAdvType(uint8_t advType, bool isLegacyAdv); void setPayload(const uint8_t *payload, uint8_t length, bool append); void setRSSI(int rssi); - uint8_t findAdvField(uint8_t type, uint8_t index = 0, uint8_t *data_loc = nullptr); - uint8_t findServiceData(uint8_t index, uint8_t* bytes); +#if CONFIG_BT_NIMBLE_EXT_ADV + void setSetId(uint8_t sid) { m_sid = sid; } + void setPrimaryPhy(uint8_t phy) { m_primPhy = phy; } + void setSecondaryPhy(uint8_t phy) { m_secPhy = phy; } + void setPeriodicInterval(uint16_t itvl) { m_periodicItvl = itvl; } +#endif + uint8_t findAdvField(uint8_t type, uint8_t index = 0, size_t * data_loc = nullptr); + size_t findServiceData(uint8_t index, uint8_t* bytes); NimBLEAddress m_address = NimBLEAddress(""); uint8_t m_advType; @@ -148,6 +164,13 @@ private: time_t m_timestamp; bool m_callbackSent; uint8_t m_advLength; +#if CONFIG_BT_NIMBLE_EXT_ADV + bool m_isLegacyAdv; + uint8_t m_sid; + uint8_t m_primPhy; + uint8_t m_secPhy; + uint16_t m_periodicItvl; +#endif std::vector m_payload; }; @@ -171,6 +194,5 @@ public: virtual void onResult(NimBLEAdvertisedDevice* advertisedDevice) = 0; }; -#endif // #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_OBSERVER */ #endif /* COMPONENTS_NIMBLEADVERTISEDDEVICE_H_ */ diff --git a/src/NimBLEAdvertising.cpp b/src/NimBLEAdvertising.cpp index 3112eff..d244f41 100644 --- a/src/NimBLEAdvertising.cpp +++ b/src/NimBLEAdvertising.cpp @@ -13,13 +13,16 @@ * Author: kolban * */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +#if (defined(CONFIG_BT_ENABLED) && \ + defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \ + !CONFIG_BT_NIMBLE_EXT_ADV) || defined(_DOXYGEN_) +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "services/gap/ble_svc_gap.h" +#else +#include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h" +#endif #include "NimBLEAdvertising.h" #include "NimBLEDevice.h" #include "NimBLEServer.h" @@ -68,6 +71,7 @@ void NimBLEAdvertising::reset() { m_advDataSet = false; // Set this to non-zero to prevent auto start if host reset before started by app. m_duration = BLE_HS_FOREVER; + m_advCompCB = nullptr; } // reset @@ -410,7 +414,7 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv // If already advertising just return if(ble_gap_adv_active()) { NIMBLE_LOGW(LOG_TAG, "Advertising already active"); - return false; + return true; } // Save the duration incase of host reset so we can restart with the same params @@ -623,7 +627,7 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv &m_advParams, (pServer != nullptr) ? NimBLEServer::handleGapEvent : NimBLEAdvertising::handleGapEvent, - (pServer != nullptr) ? (void*)pServer : (void*)this); + (void*)this); #else rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration, &m_advParams, NimBLEAdvertising::handleGapEvent, this); @@ -632,6 +636,10 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv case 0: break; + case BLE_HS_EALREADY: + NIMBLE_LOGI(LOG_TAG, "Advertisement Already active"); + break; + case BLE_HS_EINVAL: NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Duration too long"); break; @@ -653,29 +661,27 @@ bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv break; } - if(rc != 0) { - return false; - } - NIMBLE_LOGD(LOG_TAG, "<< Advertising start"); - return true; + return (rc == 0 || rc == BLE_HS_EALREADY); } // start /** * @brief Stop advertising. + * @return True if advertising stopped successfully. */ -void NimBLEAdvertising::stop() { +bool NimBLEAdvertising::stop() { NIMBLE_LOGD(LOG_TAG, ">> stop"); int rc = ble_gap_adv_stop(); if (rc != 0 && rc != BLE_HS_EALREADY) { NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_stop rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); - return; + return false; } NIMBLE_LOGD(LOG_TAG, "<< stop"); + return true; } // stop @@ -1028,5 +1034,4 @@ std::string NimBLEAdvertisementData::getPayload() { return m_payload; } // getPayload -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && !CONFIG_BT_NIMBLE_EXT_ADV */ diff --git a/src/NimBLEAdvertising.h b/src/NimBLEAdvertising.h index 17a8665..dd72ede 100644 --- a/src/NimBLEAdvertising.h +++ b/src/NimBLEAdvertising.h @@ -14,13 +14,17 @@ #ifndef MAIN_BLEADVERTISING_H_ #define MAIN_BLEADVERTISING_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +#if (defined(CONFIG_BT_ENABLED) && \ + defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \ + !CONFIG_BT_NIMBLE_EXT_ADV) || defined(_DOXYGEN_) +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_gap.h" +#else +#include "nimble/nimble/host/include/host/ble_gap.h" +#endif + /**** FIX COMPILATION ****/ #undef min #undef max @@ -87,7 +91,7 @@ public: void addServiceUUID(const char* serviceUUID); void removeServiceUUID(const NimBLEUUID &serviceUUID); bool start(uint32_t duration = 0, void (*advCompleteCB)(NimBLEAdvertising *pAdv) = nullptr); - void stop(); + bool stop(); void setAppearance(uint16_t appearance); void setName(const std::string &name); void setManufacturerData(const std::string &data); @@ -109,6 +113,7 @@ public: private: friend class NimBLEDevice; + friend class NimBLEServer; void onHostSync(); static int handleGapEvent(struct ble_gap_event *event, void *arg); @@ -132,6 +137,5 @@ private: std::vector m_uri; }; -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && !CONFIG_BT_NIMBLE_EXT_ADV */ #endif /* MAIN_BLEADVERTISING_H_ */ diff --git a/src/NimBLEAttValue.h b/src/NimBLEAttValue.h new file mode 100644 index 0000000..11cd3f8 --- /dev/null +++ b/src/NimBLEAttValue.h @@ -0,0 +1,447 @@ +/* + * NimBLEAttValue.h + * + * Created: on March 18, 2021 + * Author H2zero + * + */ + +#ifndef MAIN_NIMBLEATTVALUE_H_ +#define MAIN_NIMBLEATTVALUE_H_ +#include "nimconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE +#include +#endif + +#include "NimBLELog.h" + +/**** FIX COMPILATION ****/ +#undef min +#undef max +/**************************/ + +#include +#include + +#ifndef CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED +# define CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED 0 +#endif + +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED +# include +#endif + +#if !defined(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH) +# define CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH 20 +#elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH > BLE_ATT_ATTR_MAX_LEN +# error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be larger than 512 (BLE_ATT_ATTR_MAX_LEN) +#elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH < 1 +# error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be less than 1; Range = 1 : 512 +#endif + + +/* Used to determine if the type passed to a template has a c_str() and length() method. */ +template +struct Has_c_str_len : std::false_type {}; + +template +struct Has_c_str_len().c_str())), + decltype(void(std::declval().length()))> : std::true_type {}; + + +/** + * @brief A specialized container class to hold BLE attribute values. + * @details This class is designed to be more memory efficient than using\n + * standard container types for value storage, while being convertable to\n + * many different container classes. + */ +class NimBLEAttValue +{ + uint8_t* m_attr_value = nullptr; + uint16_t m_attr_max_len = 0; + uint16_t m_attr_len = 0; + uint16_t m_capacity = 0; +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + time_t m_timestamp = 0; +#endif + void deepCopy(const NimBLEAttValue & source); + +public: + /** + * @brief Default constructor. + * @param[in] init_len The initial size in bytes. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(uint16_t init_len = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); + + /** + * @brief Construct with an initial value from a buffer. + * @param value A pointer to the initial value to set. + * @param[in] len The size in bytes of the value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const uint8_t *value, uint16_t len, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); + + /** + * @brief Construct with an initializer list. + * @param list An initializer list containing the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(std::initializer_list list, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue(list.begin(), (uint16_t)list.size(), max_len){} + + /** + * @brief Construct with an initial value from a const char string. + * @param value A pointer to the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const char *value, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue((uint8_t*)value, (uint16_t)strlen(value), max_len){} + + /** + * @brief Construct with an initial value from a std::string. + * @param str A std::string containing to the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const std::string str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue((uint8_t*)str.data(), (uint16_t)str.length(), max_len){} + + /** + * @brief Construct with an initial value from a std::vector. + * @param vec A std::vector containing to the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const std::vector vec, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue(&vec[0], (uint16_t)vec.size(), max_len){} + +#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE + /** + * @brief Construct with an initial value from an Arduino String. + * @param str An Arduino String containing to the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const String str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue((uint8_t*)str.c_str(), str.length(), max_len){} +#endif + + /** @brief Copy constructor */ + NimBLEAttValue(const NimBLEAttValue & source) { deepCopy(source); } + + /** @brief Move constructor */ + NimBLEAttValue(NimBLEAttValue && source) { *this = std::move(source); } + + /** @brief Destructor */ + ~NimBLEAttValue(); + + /** @brief Returns the max size in bytes */ + uint16_t max_size() const { return m_attr_max_len; } + + /** @brief Returns the currently allocated capacity in bytes */ + uint16_t capacity() const { return m_capacity; } + + /** @brief Returns the current length of the value in bytes */ + uint16_t length() const { return m_attr_len; } + + /** @brief Returns the current size of the value in bytes */ + uint16_t size() const { return m_attr_len; } + + /** @brief Returns a pointer to the internal buffer of the value */ + const uint8_t* data() const { return m_attr_value; } + + /** @brief Returns a pointer to the internal buffer of the value as a const char* */ + const char* c_str() const { return (const char*)m_attr_value; } + + /** @brief Iterator begin */ + const uint8_t* begin() const { return m_attr_value; } + + /** @brief Iterator end */ + const uint8_t* end() const { return m_attr_value + m_attr_len; } + +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + /** @brief Returns a timestamp of when the value was last updated */ + time_t getTimeStamp() const { return m_timestamp; } + + /** @brief Set the timestamp to the current time */ + void setTimeStamp() { m_timestamp = time(nullptr); } + + /** + * @brief Set the timestamp to the specified time + * @param[in] t The timestamp value to set + */ + void setTimeStamp(time_t t) { m_timestamp = t; } +#else + time_t getTimeStamp() const { return 0; } + void setTimeStamp() { } + void setTimeStamp(time_t t) { } +#endif + + /** + * @brief Set the value from a buffer + * @param[in] value A ponter to a buffer containing the value. + * @param[in] len The length of the value in bytes. + * @returns True if successful. + */ + bool setValue(const uint8_t *value, uint16_t len); + + /** + * @brief Set value to the value of const char*. + * @param [in] s A ponter to a const char value to set. + */ + bool setValue(const char* s) { + return setValue((uint8_t*)s, (uint16_t)strlen(s)); } + + /** + * @brief Get a pointer to the value buffer with timestamp. + * @param[in] timestamp A ponter to a time_t variable to store the timestamp. + * @returns A pointer to the internal value buffer. + */ + const uint8_t* getValue(time_t *timestamp); + + /** + * @brief Append data to the value. + * @param[in] value A ponter to a data buffer with the value to append. + * @param[in] len The length of the value to append in bytes. + * @returns A reference to the appended NimBLEAttValue. + */ + NimBLEAttValue& append(const uint8_t *value, uint16_t len); + + + /*********************** Template Functions ************************/ + + /** + * @brief Template to set value to the value of val. + * @param [in] s The value to set. + * @details Only used for types without a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + bool +#else + typename std::enable_if::value, bool>::type +#endif + setValue(const T &s) { + return setValue((uint8_t*)&s, sizeof(T)); + } + + /** + * @brief Template to set value to the value of val. + * @param [in] s The value to set. + * @details Only used if the has a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + bool +#else + typename std::enable_if::value, bool>::type +#endif + setValue(const T & s) { + return setValue((uint8_t*)s.c_str(), (uint16_t)s.length()); + } + + /** + * @brief Template to return the value as a . + * @tparam T The type to convert the data to. + * @param [in] timestamp A pointer to a time_t struct to store the time the value was read. + * @param [in] skipSizeCheck If true it will skip checking if the data size is less than\n + * sizeof(). + * @return The data converted to or NULL if skipSizeCheck is false and the data is\n + * less than sizeof(). + * @details Use: getValue(×tamp, skipSizeCheck); + */ + template + T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { + if(!skipSizeCheck && size() < sizeof(T)) { + return T(); + } + return *((T *)getValue(timestamp)); + } + + + /*********************** Operators ************************/ + + /** @brief Subscript operator */ + uint8_t operator [](int pos) const { + assert(pos < m_attr_len && "out of range"); return m_attr_value[pos]; } + + /** @brief Operator; Get the value as a std::vector. */ + operator std::vector() const { + return std::vector(m_attr_value, m_attr_value + m_attr_len); } + + /** @brief Operator; Get the value as a std::string. */ + operator std::string() const { + return std::string((char*)m_attr_value, m_attr_len); } + + /** @brief Operator; Get the value as a const uint8_t*. */ + operator const uint8_t*() const { return m_attr_value; } + + /** @brief Operator; Append another NimBLEAttValue. */ + NimBLEAttValue& operator +=(const NimBLEAttValue & source) { + return append(source.data(), source.size()); } + + /** @brief Operator; Set the value from a std::string source. */ + NimBLEAttValue& operator =(const std::string & source) { + setValue((uint8_t*)source.data(), (uint16_t)source.size()); return *this; } + + /** @brief Move assignment operator */ + NimBLEAttValue& operator =(NimBLEAttValue && source); + + /** @brief Copy assignment operator */ + NimBLEAttValue& operator =(const NimBLEAttValue & source); + + /** @brief Equality operator */ + bool operator ==(const NimBLEAttValue & source) { + return (m_attr_len == source.size()) ? + memcmp(m_attr_value, source.data(), m_attr_len) == 0 : false; } + + /** @brief Inequality operator */ + bool operator !=(const NimBLEAttValue & source){ return !(*this == source); } + +#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE + /** @brief Operator; Get the value as an Arduino String value. */ + operator String() const { return String((char*)m_attr_value); } +#endif + +}; + + +inline NimBLEAttValue::NimBLEAttValue(uint16_t init_len, uint16_t max_len) { + m_attr_value = (uint8_t*)calloc(init_len + 1, 1); + assert(m_attr_value && "No Mem"); + m_attr_max_len = std::min(BLE_ATT_ATTR_MAX_LEN, (int)max_len); + m_attr_len = 0; + m_capacity = init_len; + setTimeStamp(0); +} + +inline NimBLEAttValue::NimBLEAttValue(const uint8_t *value, uint16_t len, uint16_t max_len) +: NimBLEAttValue(len, max_len) { + memcpy(m_attr_value, value, len); + m_attr_value[len] = '\0'; + m_attr_len = len; +} + +inline NimBLEAttValue::~NimBLEAttValue() { + if(m_attr_value != nullptr) { + free(m_attr_value); + } +} + +inline NimBLEAttValue& NimBLEAttValue::operator =(NimBLEAttValue && source) { + if (this != &source){ + free(m_attr_value); + + m_attr_value = source.m_attr_value; + m_attr_max_len = source.m_attr_max_len; + m_attr_len = source.m_attr_len; + m_capacity = source.m_capacity; + setTimeStamp(source.getTimeStamp()); + source.m_attr_value = nullptr; + } + return *this; +} + +inline NimBLEAttValue& NimBLEAttValue::operator =(const NimBLEAttValue & source) { + if (this != &source) { + deepCopy(source); + } + return *this; +} + +inline void NimBLEAttValue::deepCopy(const NimBLEAttValue & source) { + uint8_t* res = (uint8_t*)realloc( m_attr_value, source.m_capacity + 1); + assert(res && "deepCopy: realloc failed"); + + ble_npl_hw_enter_critical(); + m_attr_value = res; + m_attr_max_len = source.m_attr_max_len; + m_attr_len = source.m_attr_len; + m_capacity = source.m_capacity; + setTimeStamp(source.getTimeStamp()); + memcpy(m_attr_value, source.m_attr_value, m_attr_len + 1); + ble_npl_hw_exit_critical(0); +} + +inline const uint8_t* NimBLEAttValue::getValue(time_t *timestamp) { + if(timestamp != nullptr) { +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + *timestamp = m_timestamp; +#else + *timestamp = 0; +#endif + } + return m_attr_value; +} + +inline bool NimBLEAttValue::setValue(const uint8_t *value, uint16_t len) { + if (len > m_attr_max_len) { + NIMBLE_LOGE("NimBLEAttValue", "value exceeds max, len=%u, max=%u", + len, m_attr_max_len); + return false; + } + + uint8_t *res = m_attr_value; + if (len > m_capacity) { + res = (uint8_t*)realloc(m_attr_value, (len + 1)); + m_capacity = len; + } + assert(res && "setValue: realloc failed"); + +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + time_t t = time(nullptr); +#else + time_t t = 0; +#endif + + ble_npl_hw_enter_critical(); + m_attr_value = res; + memcpy(m_attr_value, value, len); + m_attr_value[len] = '\0'; + m_attr_len = len; + setTimeStamp(t); + ble_npl_hw_exit_critical(0); + return true; +} + +inline NimBLEAttValue& NimBLEAttValue::append(const uint8_t *value, uint16_t len) { + if (len < 1) { + return *this; + } + + if ((m_attr_len + len) > m_attr_max_len) { + NIMBLE_LOGE("NimBLEAttValue", "val > max, len=%u, max=%u", + len, m_attr_max_len); + return *this; + } + + uint8_t* res = m_attr_value; + uint16_t new_len = m_attr_len + len; + if (new_len > m_capacity) { + res = (uint8_t*)realloc(m_attr_value, (new_len + 1)); + m_capacity = new_len; + } + assert(res && "append: realloc failed"); + +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + time_t t = time(nullptr); +#else + time_t t = 0; +#endif + + ble_npl_hw_enter_critical(); + m_attr_value = res; + memcpy(m_attr_value + m_attr_len, value, len); + m_attr_len = new_len; + m_attr_value[m_attr_len] = '\0'; + setTimeStamp(t); + ble_npl_hw_exit_critical(0); + + return *this; +} + +#endif /*(CONFIG_BT_ENABLED) */ +#endif /* MAIN_NIMBLEATTVALUE_H_ */ diff --git a/src/NimBLEBeacon.cpp b/src/NimBLEBeacon.cpp index 8c4574b..996893a 100644 --- a/src/NimBLEBeacon.cpp +++ b/src/NimBLEBeacon.cpp @@ -11,7 +11,7 @@ * Created on: Jan 4, 2018 * Author: kolban */ -#include "sdkconfig.h" +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) #include diff --git a/src/NimBLECharacteristic.cpp b/src/NimBLECharacteristic.cpp index f3965a4..3b95030 100644 --- a/src/NimBLECharacteristic.cpp +++ b/src/NimBLECharacteristic.cpp @@ -9,11 +9,9 @@ * Created on: Jun 22, 2017 * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #include "NimBLECharacteristic.h" #include "NimBLE2904.h" @@ -32,27 +30,29 @@ static const char* LOG_TAG = "NimBLECharacteristic"; * @brief Construct a characteristic * @param [in] uuid - UUID (const char*) for the characteristic. * @param [in] properties - Properties for the characteristic. + * @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others). * @param [in] pService - pointer to the service instance this characteristic belongs to. */ -NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties, NimBLEService* pService) -: NimBLECharacteristic(NimBLEUUID(uuid), properties, pService) { +NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties, + uint16_t max_len, NimBLEService* pService) +: NimBLECharacteristic(NimBLEUUID(uuid), properties, max_len, pService) { } /** * @brief Construct a characteristic * @param [in] uuid - UUID for the characteristic. * @param [in] properties - Properties for the characteristic. + * @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others). * @param [in] pService - pointer to the service instance this characteristic belongs to. */ -NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID &uuid, uint16_t properties, NimBLEService* pService) { +NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID &uuid, uint16_t properties, + uint16_t max_len, NimBLEService* pService) +: m_value(std::min(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH , (int)max_len), max_len) { m_uuid = uuid; m_handle = NULL_HANDLE; m_properties = properties; m_pCallbacks = &defaultCallback; m_pService = pService; - m_value = ""; - m_valMux = portMUX_INITIALIZER_UNLOCKED; - m_timestamp = 0; m_removed = 0; } // NimBLECharacteristic @@ -234,17 +234,14 @@ NimBLEUUID NimBLECharacteristic::getUUID() { /** * @brief Retrieve the current value of the characteristic. - * @return A std::string containing the current characteristic value. + * @return The NimBLEAttValue containing the current characteristic value. */ -std::string NimBLECharacteristic::getValue(time_t *timestamp) { - portENTER_CRITICAL(&m_valMux); - std::string retVal = m_value; +NimBLEAttValue NimBLECharacteristic::getValue(time_t *timestamp) { if(timestamp != nullptr) { - *timestamp = m_timestamp; + m_value.getValue(timestamp); } - portEXIT_CRITICAL(&m_valMux); - return retVal; + return m_value; } // getValue @@ -253,11 +250,7 @@ std::string NimBLECharacteristic::getValue(time_t *timestamp) { * @return The length of the current characteristic data. */ size_t NimBLECharacteristic::getDataLength() { - portENTER_CRITICAL(&m_valMux); - size_t len = m_value.length(); - portEXIT_CRITICAL(&m_valMux); - - return len; + return m_value.size(); } @@ -289,27 +282,27 @@ int NimBLECharacteristic::handleGapEvent(uint16_t conn_handle, uint16_t attr_han pCharacteristic->m_pCallbacks->onRead(pCharacteristic, &desc); } - portENTER_CRITICAL(&pCharacteristic->m_valMux); - rc = os_mbuf_append(ctxt->om, (uint8_t*)pCharacteristic->m_value.data(), - pCharacteristic->m_value.length()); - portEXIT_CRITICAL(&pCharacteristic->m_valMux); - + ble_npl_hw_enter_critical(); + rc = os_mbuf_append(ctxt->om, pCharacteristic->m_value.data(), pCharacteristic->m_value.size()); + ble_npl_hw_exit_critical(0); return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; } case BLE_GATT_ACCESS_OP_WRITE_CHR: { - if (ctxt->om->om_len > BLE_ATT_ATTR_MAX_LEN) { + uint16_t att_max_len = pCharacteristic->m_value.max_size(); + + if (ctxt->om->om_len > att_max_len) { return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } - uint8_t buf[BLE_ATT_ATTR_MAX_LEN]; + uint8_t buf[att_max_len]; size_t len = ctxt->om->om_len; memcpy(buf, ctxt->om->om_data,len); os_mbuf *next; next = SLIST_NEXT(ctxt->om, om_next); while(next != NULL){ - if((len + next->om_len) > BLE_ATT_ATTR_MAX_LEN) { + if((len + next->om_len) > att_max_len) { return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } memcpy(&buf[len], next->om_data, next->om_len); @@ -389,26 +382,60 @@ void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) { /** - * @brief Send an indication.\n - * An indication is a transmission of up to the first 20 bytes of the characteristic value.\n - * An indication will block waiting for a positive confirmation from the client. + * @brief Send an indication. */ void NimBLECharacteristic::indicate() { - NIMBLE_LOGD(LOG_TAG, ">> indicate: length: %d", getDataLength()); notify(false); - NIMBLE_LOGD(LOG_TAG, "<< indicate"); } // indicate + /** - * @brief Send a notification.\n - * A notification is a transmission of up to the first 20 bytes of the characteristic value.\n - * A notification will not block; it is a fire and forget. + * @brief Send an indication. + * @param[in] value A pointer to the data to send. + * @param[in] length The length of the data to send. + */ +void NimBLECharacteristic::indicate(const uint8_t* value, size_t length) { + notify(value, length, false); +} // indicate + + +/** + * @brief Send an indication. + * @param[in] value A std::vector containing the value to send as the notification value. + */ +void NimBLECharacteristic::indicate(const std::vector& value) { + notify(value.data(), value.size(), false); +} // indicate + + +/** + * @brief Send a notification or indication. * @param[in] is_notification if true sends a notification, false sends an indication. */ void NimBLECharacteristic::notify(bool is_notification) { - NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", getDataLength()); + notify(m_value.data(), m_value.length(), is_notification); +} // notify +/** + * @brief Send a notification or indication. + * @param[in] value A std::vector containing the value to send as the notification value. + * @param[in] is_notification if true sends a notification, false sends an indication. + */ +void NimBLECharacteristic::notify(const std::vector& value, bool is_notification) { + notify(value.data(), value.size(), is_notification); +} // notify + + +/** + * @brief Send a notification or indication. + * @param[in] value A pointer to the data to send. + * @param[in] length The length of the data to send. + * @param[in] is_notification if true sends a notification, false sends an indication. + */ +void NimBLECharacteristic::notify(const uint8_t* value, size_t length, bool is_notification) { + NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", length); + if(!(m_properties & NIMBLE_PROPERTY::NOTIFY) && !(m_properties & NIMBLE_PROPERTY::INDICATE)) { @@ -424,15 +451,13 @@ void NimBLECharacteristic::notify(bool is_notification) { m_pCallbacks->onNotify(this); - std::string value = getValue(); - size_t length = value.length(); bool reqSec = (m_properties & BLE_GATT_CHR_F_READ_AUTHEN) || (m_properties & BLE_GATT_CHR_F_READ_AUTHOR) || (m_properties & BLE_GATT_CHR_F_READ_ENC); int rc = 0; for (auto &it : m_subscribedVec) { - uint16_t _mtu = getService()->getServer()->getPeerMTU(it.first); + uint16_t _mtu = getService()->getServer()->getPeerMTU(it.first) - 3; // check if connected and subscribed if(_mtu == 0 || it.second == 0) { @@ -448,8 +473,8 @@ void NimBLECharacteristic::notify(bool is_notification) { } } - if (length > _mtu - 3) { - NIMBLE_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu - 3); + if (length > _mtu) { + NIMBLE_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu); } if(is_notification && (!(it.second & NIMBLE_SUB_NOTIFY))) { @@ -467,7 +492,7 @@ void NimBLECharacteristic::notify(bool is_notification) { // don't create the m_buf until we are sure to send the data or else // we could be allocating a buffer that doesn't get released. // We also must create it in each loop iteration because it is consumed with each host call. - os_mbuf *om = ble_hs_mbuf_from_flat((uint8_t*)value.data(), length); + os_mbuf *om = ble_hs_mbuf_from_flat(value, length); if(!is_notification && (m_properties & NIMBLE_PROPERTY::INDICATE)) { if(!NimBLEDevice::getServer()->setIndicateWait(it.first)) { @@ -511,40 +536,30 @@ NimBLECharacteristicCallbacks* NimBLECharacteristic::getCallbacks() { /** - * @brief Set the value of the characteristic. - * @param [in] data The data to set for the characteristic. - * @param [in] length The length of the data in bytes. + * @brief Set the value of the characteristic from a data buffer . + * @param [in] data The data buffer to set for the characteristic. + * @param [in] length The number of bytes in the data buffer. */ void NimBLECharacteristic::setValue(const uint8_t* data, size_t length) { -#if CONFIG_LOG_DEFAULT_LEVEL > 3 || (ARDUINO_ARCH_ESP32 && CORE_DEBUG_LEVEL >= 4) +#if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 4 char* pHex = NimBLEUtils::buildHexData(nullptr, data, length); - NIMBLE_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s", length, pHex, getUUID().toString().c_str()); + NIMBLE_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s", + length, pHex, getUUID().toString().c_str()); free(pHex); #endif - if (length > BLE_ATT_ATTR_MAX_LEN) { - NIMBLE_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, BLE_ATT_ATTR_MAX_LEN); - return; - } - - time_t t = time(nullptr); - portENTER_CRITICAL(&m_valMux); - m_value = std::string((char*)data, length); - m_timestamp = t; - portEXIT_CRITICAL(&m_valMux); - + m_value.setValue(data, length); NIMBLE_LOGD(LOG_TAG, "<< setValue"); } // setValue /** - * @brief Set the value of the characteristic from string data.\n - * We set the value of the characteristic from the bytes contained in the string. - * @param [in] value the std::string value of the characteristic. + * @brief Set the value of the characteristic from a `std::vector`.\n + * @param [in] vec The std::vector reference to set the characteristic value from. */ -void NimBLECharacteristic::setValue(const std::string &value) { - setValue((uint8_t*)(value.data()), value.length()); -} // setValue +void NimBLECharacteristic::setValue(const std::vector& vec) { + return setValue((uint8_t*)&vec[0], vec.size()); +}// setValue /** @@ -641,6 +656,4 @@ void NimBLECharacteristicCallbacks::onSubscribe(NimBLECharacteristic* pCharacter NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onSubscribe: default"); } - -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ diff --git a/src/NimBLECharacteristic.h b/src/NimBLECharacteristic.h index 6008d12..0f84e2d 100644 --- a/src/NimBLECharacteristic.h +++ b/src/NimBLECharacteristic.h @@ -13,13 +13,15 @@ #ifndef MAIN_NIMBLECHARACTERISTIC_H_ #define MAIN_NIMBLECHARACTERISTIC_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_hs.h" +#else +#include "nimble/nimble/host/include/host/ble_hs.h" +#endif + /**** FIX COMPILATION ****/ #undef min #undef max @@ -42,6 +44,7 @@ typedef enum { #include "NimBLEService.h" #include "NimBLEDescriptor.h" +#include "NimBLEAttValue.h" #include #include @@ -63,11 +66,13 @@ public: uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN, NimBLEService* pService = nullptr); NimBLECharacteristic(const NimBLEUUID &uuid, uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN, NimBLEService* pService = nullptr); ~NimBLECharacteristic(); @@ -75,64 +80,93 @@ public: uint16_t getHandle(); NimBLEUUID getUUID(); std::string toString(); - - void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks); - NimBLECharacteristicCallbacks* - getCallbacks(); - void indicate(); + void indicate(const uint8_t* value, size_t length); + void indicate(const std::vector& value); void notify(bool is_notification = true); + void notify(const uint8_t* value, size_t length, bool is_notification = true); + void notify(const std::vector& value, bool is_notification = true); size_t getSubscribedCount(); - - NimBLEDescriptor* createDescriptor(const char* uuid, - uint32_t properties = - NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE, - uint16_t max_len = 100); - NimBLEDescriptor* createDescriptor(const NimBLEUUID &uuid, - uint32_t properties = - NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE, - uint16_t max_len = 100); - void addDescriptor(NimBLEDescriptor *pDescriptor); NimBLEDescriptor* getDescriptorByUUID(const char* uuid); NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID &uuid); NimBLEDescriptor* getDescriptorByHandle(uint16_t handle); void removeDescriptor(NimBLEDescriptor *pDescriptor, bool deleteDsc = false); - - std::string getValue(time_t *timestamp = nullptr); + NimBLEService* getService(); + uint16_t getProperties(); + NimBLEAttValue getValue(time_t *timestamp = nullptr); size_t getDataLength(); - /** - * @brief A template to convert the characteristic data to . - * @tparam T The type to convert the data to. - * @param [in] timestamp A pointer to a time_t struct to store the time the value was read. - * @param [in] skipSizeCheck If true it will skip checking if the data size is less than sizeof(). - * @return The data converted to or NULL if skipSizeCheck is false and the data is - * less than sizeof(). - * @details Use: getValue(×tamp, skipSizeCheck); - */ - template - T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { - std::string value = getValue(); - if(!skipSizeCheck && value.size() < sizeof(T)) return T(); - const char *pData = value.data(); - return *((T *)pData); - } - void setValue(const uint8_t* data, size_t size); - void setValue(const std::string &value); + void setValue(const std::vector& vec); + void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks); + NimBLEDescriptor* createDescriptor(const char* uuid, + uint32_t properties = + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);; + NimBLEDescriptor* createDescriptor(const NimBLEUUID &uuid, + uint32_t properties = + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); + + NimBLECharacteristicCallbacks* getCallbacks(); + + + /*********************** Template Functions ************************/ + /** - * @brief Convenience template to set the characteristic value to val. + * @brief Template to set the characteristic value to val. * @param [in] s The value to set. */ template - void setValue(const T &s) { - setValue((uint8_t*)&s, sizeof(T)); + void setValue(const T &s) { m_value.setValue(s); } + + /** + * @brief Template to convert the characteristic data to . + * @tparam T The type to convert the data to. + * @param [in] timestamp (Optional) A pointer to a time_t struct to store the time the value was read. + * @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than sizeof(). + * @return The data converted to or NULL if skipSizeCheck is false and the data is less than sizeof(). + * @details Use: getValue(×tamp, skipSizeCheck); + */ + template + T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { + return m_value.getValue(timestamp, skipSizeCheck); } - NimBLEService* getService(); - uint16_t getProperties(); + /** + * @brief Template to send a notification from a class type that has a c_str() and length() method. + * @tparam T The a reference to a class containing the data to send. + * @param[in] value The value to set. + * @param[in] is_notification if true sends a notification, false sends an indication. + * @details Only used if the has a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + void +#else + typename std::enable_if::value, void>::type +#endif + notify(const T& value, bool is_notification = true) { + notify((uint8_t*)value.c_str(), value.length(), is_notification); + } + + /** + * @brief Template to send an indication from a class type that has a c_str() and length() method. + * @tparam T The a reference to a class containing the data to send. + * @param[in] value The value to set. + * @details Only used if the has a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + void +#else + typename std::enable_if::value, void>::type +#endif + indicate(const T& value) { + indicate((uint8_t*)value.c_str(), value.length()); + } private: @@ -149,10 +183,8 @@ private: uint16_t m_properties; NimBLECharacteristicCallbacks* m_pCallbacks; NimBLEService* m_pService; - std::string m_value; + NimBLEAttValue m_value; std::vector m_dscVec; - portMUX_TYPE m_valMux; - time_t m_timestamp; uint8_t m_removed; std::vector> m_subscribedVec; @@ -185,7 +217,7 @@ public: ERROR_INDICATE_FAILURE }Status; - virtual ~NimBLECharacteristicCallbacks(); + virtual ~NimBLECharacteristicCallbacks(); virtual void onRead(NimBLECharacteristic* pCharacteristic); virtual void onRead(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc); virtual void onWrite(NimBLECharacteristic* pCharacteristic); @@ -195,6 +227,5 @@ public: virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue); }; -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /*MAIN_NIMBLECHARACTERISTIC_H_*/ diff --git a/src/NimBLEClient.cpp b/src/NimBLEClient.cpp index a171b8c..3619b44 100644 --- a/src/NimBLEClient.cpp +++ b/src/NimBLEClient.cpp @@ -11,11 +11,8 @@ * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #include "NimBLEClient.h" #include "NimBLEDevice.h" @@ -23,9 +20,13 @@ #include #include +#include +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "nimble/nimble_port.h" - +#else +#include "nimble/porting/nimble/include/nimble/nimble_port.h" +#endif static const char* LOG_TAG = "NimBLEClient"; static NimBLEClientCallbacks defaultCallbacks; @@ -63,6 +64,12 @@ NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(pee m_deleteCallbacks = false; m_pTaskData = nullptr; m_connEstablished = false; + m_lastErr = 0; +#if CONFIG_BT_NIMBLE_EXT_ADV + m_phyMask = BLE_GAP_LE_PHY_1M_MASK | + BLE_GAP_LE_PHY_2M_MASK | + BLE_GAP_LE_PHY_CODED_MASK; +#endif m_pConnParams.scan_itvl = 16; // Scan interval in 0.625ms units (NimBLE Default) m_pConnParams.scan_window = 16; // Scan window in 0.625ms units (NimBLE Default) @@ -73,6 +80,7 @@ NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(pee m_pConnParams.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN; // Minimum length of connection event in 0.625ms units m_pConnParams.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN; // Maximum length of connection event in 0.625ms units + memset(&m_dcTimer, 0, sizeof(m_dcTimer)); ble_npl_callout_init(&m_dcTimer, nimble_port_get_dflt_eventq(), NimBLEClient::dcTimerCb, this); } // NimBLEClient @@ -91,6 +99,8 @@ NimBLEClient::~NimBLEClient() { delete m_pClientCallbacks; } + ble_npl_callout_deinit(&m_dcTimer); + } // ~NimBLEClient @@ -205,7 +215,8 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { m_peerAddress = address; } - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; m_pTaskData = &taskData; int rc = 0; @@ -214,9 +225,22 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { * Loop on BLE_HS_EBUSY if the scan hasn't stopped yet. */ do { +#if CONFIG_BT_NIMBLE_EXT_ADV + rc = ble_gap_ext_connect(NimBLEDevice::m_own_addr_type, + &peerAddr_t, + m_connectTimeout, + m_phyMask, + &m_pConnParams, + &m_pConnParams, + &m_pConnParams, + NimBLEClient::handleGapEvent, + this); + +#else rc = ble_gap_connect(NimBLEDevice::m_own_addr_type, &peerAddr_t, m_connectTimeout, &m_pConnParams, NimBLEClient::handleGapEvent, this); +#endif switch (rc) { case 0: break; @@ -251,11 +275,17 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { } while (rc == BLE_HS_EBUSY); + m_lastErr = rc; + if(rc != 0) { m_pTaskData = nullptr; return false; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif // Wait for the connect timeout time +1 second for the connection to complete if(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(m_connectTimeout + 1000)) == pdFALSE) { m_pTaskData = nullptr; @@ -273,6 +303,7 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { return false; } else if(taskData.rc != 0){ + m_lastErr = taskData.rc; NIMBLE_LOGE(LOG_TAG, "Connection failed; status=%d %s", taskData.rc, NimBLEUtils::returnCodeToString(taskData.rc)); @@ -305,7 +336,8 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { * @return True on success. */ bool NimBLEClient::secureConnection() { - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; int retryCount = 1; @@ -314,14 +346,20 @@ bool NimBLEClient::secureConnection() { int rc = NimBLEDevice::startSecurity(m_conn_id); if(rc != 0){ + m_lastErr = rc; m_pTaskData = nullptr; return false; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); } while (taskData.rc == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING) && retryCount--); if(taskData.rc != 0){ + m_lastErr = taskData.rc; return false; } @@ -372,10 +410,26 @@ int NimBLEClient::disconnect(uint8_t reason) { } NIMBLE_LOGD(LOG_TAG, "<< disconnect()"); + m_lastErr = rc; return rc; } // disconnect +#if CONFIG_BT_NIMBLE_EXT_ADV +/** + * @brief Set the PHY types to use when connecting to a server. + * @param [in] mask A bitmask indicating what PHYS to connect with.\n + * The available bits are: + * * 0x01 BLE_GAP_LE_PHY_1M_MASK + * * 0x02 BLE_GAP_LE_PHY_2M_MASK + * * 0x04 BLE_GAP_LE_PHY_CODED_MASK + */ +void NimBLEClient::setConnectPhy(uint8_t mask) { + m_phyMask = mask; +} +#endif + + /** * @brief Set the connection paramaters to use when connecting to a server. * @param [in] minInterval The minimum connection interval in 1.25ms units. @@ -436,6 +490,29 @@ void NimBLEClient::updateConnParams(uint16_t minInterval, uint16_t maxInterval, } // updateConnParams +/** + * @brief Request an update of the data packet length. + * * Can only be used after a connection has been established. + * @details Sends a data length update request to the server the client is connected to. + * The Data Length Extension (DLE) allows to increase the Data Channel Payload from 27 bytes to up to 251 bytes. + * The server needs to support the Bluetooth 4.2 specifications, to be capable of DLE. + * @param [in] tx_octets The preferred number of payload octets to use (Range 0x001B-0x00FB). + */ +void NimBLEClient::setDataLen(uint16_t tx_octets) { +#if defined(CONFIG_NIMBLE_CPP_IDF) && !defined(ESP_IDF_VERSION) || \ + (ESP_IDF_VERSION_MAJOR * 100 + ESP_IDF_VERSION_MINOR * 10 + ESP_IDF_VERSION_PATCH) < 432 + return; +#else + uint16_t tx_time = (tx_octets + 14) * 8; + + int rc = ble_gap_set_data_len(m_conn_id, tx_octets, tx_time); + if(rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Set data length error: %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); + } +#endif +} // setDataLen + + /** * @brief Get detailed information about the current peer connection. */ @@ -512,6 +589,7 @@ int NimBLEClient::getRssi() { if(rc != 0) { NIMBLE_LOGE(LOG_TAG, "Failed to read RSSI error code: %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); + m_lastErr = rc; return 0; } @@ -568,14 +646,31 @@ NimBLERemoteService* NimBLEClient::getService(const NimBLEUUID &uuid) { return m_servicesVector.back(); } - // If the request was successful but 16/32 bit service not found + // If the request was successful but 16/32 bit uuid not found // try again with the 128 bit uuid. if(uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) { NimBLEUUID uuid128(uuid); uuid128.to128(); - return getService(uuid128); + if(retrieveServices(&uuid128)) { + if(m_servicesVector.size() > prev_size) { + return m_servicesVector.back(); + } + } + } else { + // If the request was successful but the 128 bit uuid not found + // try again with the 16 bit uuid. + NimBLEUUID uuid16(uuid); + uuid16.to16(); + // if the uuid was 128 bit but not of the BLE base type this check will fail + if (uuid16.bitSize() == BLE_UUID_TYPE_16) { + if(retrieveServices(&uuid16)) { + if(m_servicesVector.size() > prev_size) { + return m_servicesVector.back(); + } + } + } } } @@ -639,7 +734,8 @@ bool NimBLEClient::retrieveServices(const NimBLEUUID *uuid_filter) { } int rc = 0; - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; if(uuid_filter == nullptr) { rc = ble_gattc_disc_all_svcs(m_conn_id, NimBLEClient::serviceDiscoveredCB, &taskData); @@ -650,11 +746,18 @@ bool NimBLEClient::retrieveServices(const NimBLEUUID *uuid_filter) { if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_svcs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); + m_lastErr = rc; return false; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + // wait until we have all the services ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + m_lastErr = taskData.rc; if(taskData.rc == 0){ NIMBLE_LOGD(LOG_TAG, "<< retrieveServices"); @@ -698,7 +801,7 @@ int NimBLEClient::serviceDiscoveredCB( if(error->status == BLE_HS_EDONE) { pTaskData->rc = 0; } else { - NIMBLE_LOGE(LOG_TAG, "characteristicDiscCB() rc=%d %s", + NIMBLE_LOGE(LOG_TAG, "serviceDiscoveredCB() rc=%d %s", error->status, NimBLEUtils::returnCodeToString(error->status)); pTaskData->rc = error->status; @@ -706,7 +809,7 @@ int NimBLEClient::serviceDiscoveredCB( xTaskNotifyGive(pTaskData->task); - NIMBLE_LOGD(LOG_TAG,"<< << Service Discovered"); + NIMBLE_LOGD(LOG_TAG,"<< Service Discovered"); return error->status; } @@ -717,11 +820,11 @@ int NimBLEClient::serviceDiscoveredCB( * @param [in] characteristicUUID The characteristic whose value we wish to read. * @returns characteristic value or an empty string if not found */ -std::string NimBLEClient::getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID) { +NimBLEAttValue NimBLEClient::getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID) { NIMBLE_LOGD(LOG_TAG, ">> getValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - std::string ret = ""; + NimBLEAttValue ret; NimBLERemoteService* pService = getService(serviceUUID); if(pService != nullptr) { @@ -731,7 +834,7 @@ std::string NimBLEClient::getValue(const NimBLEUUID &serviceUUID, const NimBLEUU } } - NIMBLE_LOGD(LOG_TAG, "<> setValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); @@ -806,7 +909,7 @@ uint16_t NimBLEClient::getMTU() { * @param [in] arg A pointer to the client instance that registered for this callback. */ /*STATIC*/ - int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) { +int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) { NimBLEClient* client = (NimBLEClient*)arg; int rc; @@ -924,19 +1027,14 @@ uint16_t NimBLEClient::getMTU() { NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", (*characteristic)->toString().c_str()); - time_t t = time(nullptr); - portENTER_CRITICAL(&(*characteristic)->m_valMux); - (*characteristic)->m_value = std::string((char *)event->notify_rx.om->om_data, - event->notify_rx.om->om_len); - (*characteristic)->m_timestamp = t; - portEXIT_CRITICAL(&(*characteristic)->m_valMux); + uint32_t data_len = OS_MBUF_PKTLEN(event->notify_rx.om); + (*characteristic)->m_value.setValue(event->notify_rx.om->om_data, data_len); if ((*characteristic)->m_notifyCallback != nullptr) { NIMBLE_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s", (*characteristic)->toString().c_str()); (*characteristic)->m_notifyCallback(*characteristic, event->notify_rx.om->om_data, - event->notify_rx.om->om_len, - !event->notify_rx.indication); + data_len, !event->notify_rx.indication); } break; } @@ -1034,7 +1132,7 @@ uint16_t NimBLEClient::getMTU() { NIMBLE_LOGD(LOG_TAG, "ble_sm_inject_io result: %d", rc); } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { - NIMBLE_LOGD(LOG_TAG, "Passkey on device's display: %d", event->passkey.params.numcmp); + NIMBLE_LOGD(LOG_TAG, "Passkey on device's display: %" PRIu32, event->passkey.params.numcmp); pkey.action = event->passkey.params.action; // Compatibility only - Do not use, should be removed the in future if(NimBLEDevice::m_securityCallbacks != nullptr) { @@ -1136,6 +1234,15 @@ std::string NimBLEClient::toString() { } // toString +/** + * @brief Get the last error code reported by the NimBLE host + * @return int, the NimBLE error code. + */ +int NimBLEClient::getLastError() { + return m_lastErr; +} // getLastError + + void NimBLEClientCallbacks::onConnect(NimBLEClient* pClient) { NIMBLE_LOGD("NimBLEClientCallbacks", "onConnect: default"); } @@ -1170,5 +1277,4 @@ bool NimBLEClientCallbacks::onConfirmPIN(uint32_t pin){ return true; } -#endif // #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif // CONFIG_BT_ENABLED +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ diff --git a/src/NimBLEClient.h b/src/NimBLEClient.h index d90086c..4aed02a 100644 --- a/src/NimBLEClient.h +++ b/src/NimBLEClient.h @@ -14,16 +14,14 @@ #ifndef MAIN_NIMBLECLIENT_H_ #define MAIN_NIMBLECLIENT_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #include "NimBLEAddress.h" #include "NimBLEUUID.h" #include "NimBLEUtils.h" #include "NimBLEConnInfo.h" +#include "NimBLEAttValue.h" #include "NimBLEAdvertisedDevice.h" #include "NimBLERemoteService.h" @@ -54,9 +52,9 @@ public: NimBLERemoteService* getService(const NimBLEUUID &uuid); void deleteServices(); size_t deleteService(const NimBLEUUID &uuid); - std::string getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID); + NimBLEAttValue getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID); bool setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID, - const std::string &value, bool response = false); + const NimBLEAttValue &value, bool response = false); NimBLERemoteCharacteristic* getCharacteristic(const uint16_t handle); bool isConnected(); void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks, @@ -71,8 +69,13 @@ public: uint16_t scanInterval=16, uint16_t scanWindow=16); void updateConnParams(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout); + void setDataLen(uint16_t tx_octets); void discoverAttributes(); NimBLEConnInfo getConnInfo(); + int getLastError(); +#if CONFIG_BT_NIMBLE_EXT_ADV + void setConnectPhy(uint8_t mask); +#endif private: NimBLEClient(const NimBLEAddress &peerAddress); @@ -90,6 +93,7 @@ private: bool retrieveServices(const NimBLEUUID *uuid_filter = nullptr); NimBLEAddress m_peerAddress; + int m_lastErr; uint16_t m_conn_id; bool m_connEstablished; bool m_deleteCallbacks; @@ -97,6 +101,9 @@ private: NimBLEClientCallbacks* m_pClientCallbacks; ble_task_data_t* m_pTaskData; ble_npl_callout m_dcTimer; +#if CONFIG_BT_NIMBLE_EXT_ADV + uint8_t m_phyMask; +#endif std::vector m_servicesVector; @@ -158,6 +165,5 @@ public: virtual bool onConfirmPIN(uint32_t pin); }; -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif // CONFIG_BT_ENABLED +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* MAIN_NIMBLECLIENT_H_ */ diff --git a/src/NimBLEDescriptor.cpp b/src/NimBLEDescriptor.cpp index f946766..3429d13 100644 --- a/src/NimBLEDescriptor.cpp +++ b/src/NimBLEDescriptor.cpp @@ -11,11 +11,9 @@ * Created on: Jun 22, 2017 * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #include "NimBLEService.h" #include "NimBLEDescriptor.h" @@ -30,28 +28,32 @@ static NimBLEDescriptorCallbacks defaultCallbacks; /** - * @brief NimBLEDescriptor constructor. + * @brief Construct a descriptor + * @param [in] uuid - UUID (const char*) for the descriptor. + * @param [in] properties - Properties for the descriptor. + * @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others). + * @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to. */ NimBLEDescriptor::NimBLEDescriptor(const char* uuid, uint16_t properties, uint16_t max_len, - NimBLECharacteristic* pCharacteristic) -: NimBLEDescriptor(NimBLEUUID(uuid), max_len, properties, pCharacteristic) { + NimBLECharacteristic* pCharacteristic) +: NimBLEDescriptor(NimBLEUUID(uuid), properties, max_len, pCharacteristic) { } /** - * @brief NimBLEDescriptor constructor. + * @brief Construct a descriptor + * @param [in] uuid - UUID (const char*) for the descriptor. + * @param [in] properties - Properties for the descriptor. + * @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others). + * @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to. */ NimBLEDescriptor::NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties, uint16_t max_len, NimBLECharacteristic* pCharacteristic) -{ +: m_value(std::min(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH , (int)max_len), max_len) { m_uuid = uuid; - m_value.attr_len = 0; // Initial length is 0. - m_value.attr_max_len = max_len; // Maximum length of the data. m_handle = NULL_HANDLE; // Handle is initially unknown. m_pCharacteristic = pCharacteristic; m_pCallbacks = &defaultCallbacks; // No initial callback. - m_value.attr_value = (uint8_t*) calloc(max_len,1); // Allocate storage for the value. - m_valMux = portMUX_INITIALIZER_UNLOCKED; m_properties = 0; m_removed = 0; @@ -87,7 +89,6 @@ NimBLEDescriptor::NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties, uint16_ * @brief NimBLEDescriptor destructor. */ NimBLEDescriptor::~NimBLEDescriptor() { - free(m_value.attr_value); // Release the storage we created in the constructor. } // ~NimBLEDescriptor /** @@ -104,7 +105,7 @@ uint16_t NimBLEDescriptor::getHandle() { * @return The length (in bytes) of the value of this descriptor. */ size_t NimBLEDescriptor::getLength() { - return m_value.attr_len; + return m_value.size(); } // getLength @@ -118,10 +119,14 @@ NimBLEUUID NimBLEDescriptor::getUUID() { /** * @brief Get the value of this descriptor. - * @return A pointer to the value of this descriptor. + * @return The NimBLEAttValue of this descriptor. */ -uint8_t* NimBLEDescriptor::getValue() { - return m_value.attr_value; +NimBLEAttValue NimBLEDescriptor::getValue(time_t *timestamp) { + if (timestamp != nullptr) { + m_value.getValue(timestamp); + } + + return m_value; } // getValue @@ -130,7 +135,7 @@ uint8_t* NimBLEDescriptor::getValue() { * @return A std::string instance containing a copy of the descriptor's value. */ std::string NimBLEDescriptor::getStringValue() { - return std::string((char *) m_value.attr_value, m_value.attr_len); + return std::string(m_value); } @@ -145,6 +150,9 @@ NimBLECharacteristic* NimBLEDescriptor::getCharacteristic() { int NimBLEDescriptor::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { + (void)conn_handle; + (void)attr_handle; + const ble_uuid_t *uuid; int rc; NimBLEDescriptor* pDescriptor = (NimBLEDescriptor*)arg; @@ -161,24 +169,27 @@ int NimBLEDescriptor::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle, if(ctxt->om->om_pkthdr_len > 8) { pDescriptor->m_pCallbacks->onRead(pDescriptor); } - portENTER_CRITICAL(&pDescriptor->m_valMux); - rc = os_mbuf_append(ctxt->om, pDescriptor->getValue(), pDescriptor->getLength()); - portEXIT_CRITICAL(&pDescriptor->m_valMux); + + ble_npl_hw_enter_critical(); + rc = os_mbuf_append(ctxt->om, pDescriptor->m_value.data(), pDescriptor->m_value.size()); + ble_npl_hw_exit_critical(0); return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; } case BLE_GATT_ACCESS_OP_WRITE_DSC: { - if (ctxt->om->om_len > pDescriptor->m_value.attr_max_len) { + uint16_t att_max_len = pDescriptor->m_value.max_size(); + + if (ctxt->om->om_len > att_max_len) { return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } - uint8_t buf[pDescriptor->m_value.attr_max_len]; + uint8_t buf[att_max_len]; size_t len = ctxt->om->om_len; memcpy(buf, ctxt->om->om_data,len); os_mbuf *next; next = SLIST_NEXT(ctxt->om, om_next); while(next != NULL){ - if((len + next->om_len) > pDescriptor->m_value.attr_max_len) { + if((len + next->om_len) > att_max_len) { return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } memcpy(&buf[len], next->om_data, next->om_len); @@ -230,25 +241,19 @@ void NimBLEDescriptor::setHandle(uint16_t handle) { * @param [in] length The length of the data in bytes. */ void NimBLEDescriptor::setValue(const uint8_t* data, size_t length) { - if (length > m_value.attr_max_len) { - NIMBLE_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, m_value.attr_max_len); - return; - } - portENTER_CRITICAL(&m_valMux); - m_value.attr_len = length; - memcpy(m_value.attr_value, data, length); - portEXIT_CRITICAL(&m_valMux); + m_value.setValue(data, length); } // setValue /** - * @brief Set the value of the descriptor. - * @param [in] value The value of the descriptor in string form. + * @brief Set the value of the descriptor from a `std::vector`.\n + * @param [in] vec The std::vector reference to set the descriptor value from. */ -void NimBLEDescriptor::setValue(const std::string &value) { - setValue((uint8_t*) value.data(), value.length()); +void NimBLEDescriptor::setValue(const std::vector& vec) { + return setValue((uint8_t*)&vec[0], vec.size()); } // setValue + /** * @brief Set the characteristic this descriptor belongs to. * @param [in] pChar A pointer to the characteristic this descriptior belongs to. @@ -277,6 +282,7 @@ NimBLEDescriptorCallbacks::~NimBLEDescriptorCallbacks() {} * @param [in] pDescriptor The descriptor that is the source of the event. */ void NimBLEDescriptorCallbacks::onRead(NimBLEDescriptor* pDescriptor) { + (void)pDescriptor; NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onRead: default"); } // onRead @@ -286,8 +292,8 @@ void NimBLEDescriptorCallbacks::onRead(NimBLEDescriptor* pDescriptor) { * @param [in] pDescriptor The descriptor that is the source of the event. */ void NimBLEDescriptorCallbacks::onWrite(NimBLEDescriptor* pDescriptor) { + (void)pDescriptor; NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onWrite: default"); } // onWrite -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ diff --git a/src/NimBLEDescriptor.h b/src/NimBLEDescriptor.h index 5dc0ce8..4ee9a62 100644 --- a/src/NimBLEDescriptor.h +++ b/src/NimBLEDescriptor.h @@ -14,25 +14,16 @@ #ifndef MAIN_NIMBLEDESCRIPTOR_H_ #define MAIN_NIMBLEDESCRIPTOR_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #include "NimBLECharacteristic.h" #include "NimBLEUUID.h" +#include "NimBLEAttValue.h" #include - -typedef struct -{ - uint16_t attr_max_len; /*!< attribute max value length */ - uint16_t attr_len; /*!< attribute current value length */ - uint8_t *attr_value; /*!< the pointer to attribute value */ -} attr_value_t; - class NimBLEService; class NimBLECharacteristic; class NimBLEDescriptorCallbacks; @@ -56,24 +47,36 @@ public: uint16_t getHandle(); NimBLEUUID getUUID(); std::string toString(); - void setCallbacks(NimBLEDescriptorCallbacks* pCallbacks); + NimBLECharacteristic* getCharacteristic(); size_t getLength(); - uint8_t* getValue(); + NimBLEAttValue getValue(time_t *timestamp = nullptr); std::string getStringValue(); void setValue(const uint8_t* data, size_t size); - void setValue(const std::string &value); - NimBLECharacteristic* getCharacteristic(); + void setValue(const std::vector& vec); + + /*********************** Template Functions ************************/ /** - * @brief Convenience template to set the descriptor value to val. + * @brief Template to set the characteristic value to val. * @param [in] s The value to set. */ template - void setValue(const T &s) { - setValue((uint8_t*)&s, sizeof(T)); + void setValue(const T &s) { m_value.setValue(s); } + + /** + * @brief Template to convert the descriptor data to . + * @tparam T The type to convert the data to. + * @param [in] timestamp (Optional) A pointer to a time_t struct to store the time the value was read. + * @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than sizeof(). + * @return The data converted to or NULL if skipSizeCheck is false and the data is less than sizeof(). + * @details Use: getValue(×tamp, skipSizeCheck); + */ + template + T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { + return m_value.getValue(timestamp, skipSizeCheck); } private: @@ -91,8 +94,7 @@ private: NimBLEDescriptorCallbacks* m_pCallbacks; NimBLECharacteristic* m_pCharacteristic; uint8_t m_properties; - attr_value_t m_value; - portMUX_TYPE m_valMux; + NimBLEAttValue m_value; uint8_t m_removed; }; // NimBLEDescriptor @@ -113,6 +115,5 @@ public: #include "NimBLE2904.h" -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /* MAIN_NIMBLEDESCRIPTOR_H_ */ diff --git a/src/NimBLEDevice.cpp b/src/NimBLEDevice.cpp index d7a4215..b935120 100644 --- a/src/NimBLEDevice.cpp +++ b/src/NimBLEDevice.cpp @@ -11,27 +11,45 @@ * Created on: Mar 16, 2017 * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" +#if defined(CONFIG_BT_ENABLED) + #include "NimBLEDevice.h" #include "NimBLEUtils.h" -#include "esp_err.h" -#include "esp_bt.h" -#include "nvs_flash.h" -#include "esp_nimble_hci.h" -#include "nimble/nimble_port.h" -#include "nimble/nimble_port_freertos.h" -#include "host/ble_hs.h" -#include "host/ble_hs_pvcy.h" -#include "host/util/util.h" -#include "services/gap/ble_svc_gap.h" -#include "services/gatt/ble_svc_gatt.h" +#ifdef ESP_PLATFORM +# include "esp_err.h" +# include "esp_bt.h" +# include "nvs_flash.h" +# if defined(CONFIG_NIMBLE_CPP_IDF) +# include "esp_nimble_hci.h" +# include "nimble/nimble_port.h" +# include "nimble/nimble_port_freertos.h" +# include "host/ble_hs.h" +# include "host/ble_hs_pvcy.h" +# include "host/util/util.h" +# include "services/gap/ble_svc_gap.h" +# include "services/gatt/ble_svc_gatt.h" +# else +# include "nimble/esp_port/esp-hci/include/esp_nimble_hci.h" +# endif +#else +# include "nimble/nimble/controller/include/controller/ble_phy.h" +#endif -#ifdef CONFIG_ENABLE_ARDUINO_DEPENDS -#include "esp32-hal-bt.h" +#ifndef CONFIG_NIMBLE_CPP_IDF +# include "nimble/porting/nimble/include/nimble/nimble_port.h" +# include "nimble/porting/npl/freertos/include/nimble/nimble_port_freertos.h" +# include "nimble/nimble/host/include/host/ble_hs.h" +# include "nimble/nimble/host/include/host/ble_hs_pvcy.h" +# include "nimble/nimble/host/util/include/host/util/util.h" +# include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h" +# include "nimble/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h" +#endif + +#if defined(ESP_PLATFORM) && defined(CONFIG_ENABLE_ARDUINO_DEPENDS) +# include "esp32-hal-bt.h" #endif #include "NimBLELog.h" @@ -51,7 +69,11 @@ NimBLEServer* NimBLEDevice::m_pServer = nullptr; uint32_t NimBLEDevice::m_passkey = 123456; bool NimBLEDevice::m_synced = false; #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +# if CONFIG_BT_NIMBLE_EXT_ADV +NimBLEExtAdvertising* NimBLEDevice::m_bleAdvertising = nullptr; +# else NimBLEAdvertising* NimBLEDevice::m_bleAdvertising = nullptr; +# endif #endif NimBLEMeshNode* NimBLEDevice::m_pMeshNode = nullptr; @@ -65,9 +87,10 @@ std::list NimBLEDevice::m_ignoreList; std::vector NimBLEDevice::m_whiteList; NimBLESecurityCallbacks* NimBLEDevice::m_securityCallbacks = nullptr; uint8_t NimBLEDevice::m_own_addr_type = BLE_OWN_ADDR_PUBLIC; +#ifdef ESP_PLATFORM uint16_t NimBLEDevice::m_scanDuplicateSize = CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE; uint8_t NimBLEDevice::m_scanFilterMode = CONFIG_BTDM_SCAN_DUPL_TYPE; - +#endif /** * @brief Create a new mesh node. @@ -121,6 +144,45 @@ NimBLEMeshNode* NimBLEDevice::getMeshNode() { #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +# if CONFIG_BT_NIMBLE_EXT_ADV +/** + * @brief Get the instance of the advertising object. + * @return A pointer to the advertising object. + */ +NimBLEExtAdvertising* NimBLEDevice::getAdvertising() { + if(m_bleAdvertising == nullptr) { + m_bleAdvertising = new NimBLEExtAdvertising(); + } + return m_bleAdvertising; +} + + +/** + * @brief Convenience function to begin advertising. + * @param [in] inst_id The extended advertisement instance ID to start. + * @param [in] duration How long to advertise for in milliseconds, 0 = forever (default). + * @param [in] max_events Maximum number of advertisement events to send, 0 = no limit (default). + * @return True if advertising started successfully. + */ +bool NimBLEDevice::startAdvertising(uint8_t inst_id, + int duration, + int max_events) { + return getAdvertising()->start(inst_id, duration, max_events); +} // startAdvertising + + +/** + * @brief Convenience function to stop advertising a data set. + * @param [in] inst_id The extended advertisement instance ID to stop advertising. + * @return True if advertising stopped successfully. + */ +bool NimBLEDevice::stopAdvertising(uint8_t inst_id) { + return getAdvertising()->stop(inst_id); +} // stopAdvertising + +# endif + +# if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_) /** * @brief Get the instance of the advertising object. * @return A pointer to the advertising object. @@ -135,17 +197,19 @@ NimBLEAdvertising* NimBLEDevice::getAdvertising() { /** * @brief Convenience function to begin advertising. + * @return True if advertising started successfully. */ -void NimBLEDevice::startAdvertising() { - getAdvertising()->start(); +bool NimBLEDevice::startAdvertising() { + return getAdvertising()->start(); } // startAdvertising - +# endif /** - * @brief Convenience function to stop advertising. + * @brief Convenience function to stop all advertising. + * @return True if advertising stopped successfully. */ -void NimBLEDevice::stopAdvertising() { - getAdvertising()->stop(); +bool NimBLEDevice::stopAdvertising() { + return getAdvertising()->stop(); } // stopAdvertising #endif // #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) @@ -156,7 +220,8 @@ void NimBLEDevice::stopAdvertising() { * try and release/delete it. */ #if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) -/* STATIC */ NimBLEScan* NimBLEDevice::getScan() { +/* STATIC */ +NimBLEScan* NimBLEDevice::getScan() { if (m_pScan == nullptr) { m_pScan = new NimBLEScan(); } @@ -173,7 +238,8 @@ void NimBLEDevice::stopAdvertising() { * @return A reference to the new client object. */ #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) -/* STATIC */ NimBLEClient* NimBLEDevice::createClient(NimBLEAddress peerAddress) { +/* STATIC */ +NimBLEClient* NimBLEDevice::createClient(NimBLEAddress peerAddress) { if(m_cList.size() >= NIMBLE_MAX_CONNECTIONS) { NIMBLE_LOGW(LOG_TAG,"Number of clients exceeds Max connections. Cur=%d Max=%d", m_cList.size(), NIMBLE_MAX_CONNECTIONS); @@ -191,7 +257,8 @@ void NimBLEDevice::stopAdvertising() { * Checks if it is connected or trying to connect and disconnects/stops it first. * @param [in] pClient A pointer to the client object. */ -/* STATIC */ bool NimBLEDevice::deleteClient(NimBLEClient* pClient) { +/* STATIC */ +bool NimBLEDevice::deleteClient(NimBLEClient* pClient) { if(pClient == nullptr) { return false; } @@ -235,7 +302,8 @@ void NimBLEDevice::stopAdvertising() { * @brief Get the list of created client objects. * @return A pointer to the list of clients. */ -/* STATIC */std::list* NimBLEDevice::getClientList() { +/* STATIC */ +std::list* NimBLEDevice::getClientList() { return &m_cList; } // getClientList @@ -244,7 +312,8 @@ void NimBLEDevice::stopAdvertising() { * @brief Get the number of created client objects. * @return Number of client objects created. */ -/* STATIC */size_t NimBLEDevice::getClientListSize() { +/* STATIC */ +size_t NimBLEDevice::getClientListSize() { return m_cList.size(); } // getClientList @@ -254,7 +323,8 @@ void NimBLEDevice::stopAdvertising() { * @param [in] conn_id The client connection ID to search for. * @return A pointer to the client object with the spcified connection ID. */ -/* STATIC */NimBLEClient* NimBLEDevice::getClientByID(uint16_t conn_id) { +/* STATIC */ +NimBLEClient* NimBLEDevice::getClientByID(uint16_t conn_id) { for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) { if((*it)->getConnId() == conn_id) { return (*it); @@ -270,7 +340,8 @@ void NimBLEDevice::stopAdvertising() { * @param [in] peer_addr The address of the peer to search for. * @return A pointer to the client object with the peer address. */ -/* STATIC */NimBLEClient* NimBLEDevice::getClientByPeerAddress(const NimBLEAddress &peer_addr) { +/* STATIC */ +NimBLEClient* NimBLEDevice::getClientByPeerAddress(const NimBLEAddress &peer_addr) { for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) { if((*it)->getPeerAddress().equals(peer_addr)) { return (*it); @@ -284,7 +355,8 @@ void NimBLEDevice::stopAdvertising() { * @brief Finds the first disconnected client in the list. * @return A pointer to the first client object that is not connected to a peer. */ -/* STATIC */NimBLEClient* NimBLEDevice::getDisconnectedClient() { +/* STATIC */ +NimBLEClient* NimBLEDevice::getDisconnectedClient() { for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) { if(!(*it)->isConnected()) { return (*it); @@ -295,7 +367,7 @@ void NimBLEDevice::stopAdvertising() { #endif // #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) - +#ifdef ESP_PLATFORM /** * @brief Set the transmission power. * @param [in] powerLevel The power level to set, can be one of: @@ -321,12 +393,15 @@ void NimBLEDevice::stopAdvertising() { * * ESP_BLE_PWR_TYPE_SCAN = 10, For scan * * ESP_BLE_PWR_TYPE_DEFAULT = 11, For default, if not set other, it will use default value */ -/* STATIC */ void NimBLEDevice::setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType) { +/* STATIC */ +void NimBLEDevice::setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType) { NIMBLE_LOGD(LOG_TAG, ">> setPower: %d (type: %d)", powerLevel, powerType); + esp_err_t errRc = esp_ble_tx_power_set(powerType, powerLevel); if (errRc != ESP_OK) { NIMBLE_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d", errRc); } + NIMBLE_LOGD(LOG_TAG, "<< setPower"); } // setPower @@ -348,9 +423,8 @@ void NimBLEDevice::stopAdvertising() { * * ESP_BLE_PWR_TYPE_DEFAULT = 11, For default, if not set other, it will use default value * @return the power level currently used by the type specified. */ - -/* STATIC */ int NimBLEDevice::getPower(esp_ble_power_type_t powerType) { - +/* STATIC */ +int NimBLEDevice::getPower(esp_ble_power_type_t powerType) { switch(esp_ble_tx_power_get(powerType)) { case ESP_PWR_LVL_N12: return -12; @@ -373,13 +447,25 @@ void NimBLEDevice::stopAdvertising() { } } // getPower +#else + +void NimBLEDevice::setPower(int dbm) { + ble_phy_txpwr_set(dbm); +} + + +int NimBLEDevice::getPower() { + return ble_phy_txpwr_get(); +} +#endif /** * @brief Get our device address. * @return A NimBLEAddress object of our public address if we have one, * if not then our current random address. */ -/* STATIC*/ NimBLEAddress NimBLEDevice::getAddress() { +/* STATIC*/ +NimBLEAddress NimBLEDevice::getAddress() { ble_addr_t addr = {BLE_ADDR_PUBLIC, 0}; if(BLE_HS_ENOADDR == ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr.val, NULL)) { @@ -396,7 +482,8 @@ void NimBLEDevice::stopAdvertising() { * @brief Return a string representation of the address of this device. * @return A string representation of this device address. */ -/* STATIC */ std::string NimBLEDevice::toString() { +/* STATIC */ +std::string NimBLEDevice::toString() { return getAddress().toString(); } // toString @@ -406,7 +493,8 @@ void NimBLEDevice::stopAdvertising() { * @param [in] mtu Value to set local mtu: * * This should be larger than 23 and lower or equal to BLE_ATT_MTU_MAX = 527. */ -/* STATIC */int NimBLEDevice::setMTU(uint16_t mtu) { +/* STATIC */ +int NimBLEDevice::setMTU(uint16_t mtu) { NIMBLE_LOGD(LOG_TAG, ">> setLocalMTU: %d", mtu); int rc = ble_att_set_preferred_mtu(mtu); @@ -424,11 +512,13 @@ void NimBLEDevice::stopAdvertising() { * @brief Get local MTU value set. * @return The current preferred MTU setting. */ -/* STATIC */uint16_t NimBLEDevice::getMTU() { +/* STATIC */ +uint16_t NimBLEDevice::getMTU() { return ble_att_preferred_mtu(); } +#ifdef ESP_PLATFORM /** * @brief Set the duplicate filter cache size for filtering scanned devices. * @param [in] cacheSize The number of advertisements filtered before the cache is reset.\n @@ -474,6 +564,7 @@ void NimBLEDevice::setScanFilterMode(uint8_t mode) { m_scanFilterMode = mode; } +#endif #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) || defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) /** @@ -496,7 +587,7 @@ int NimBLEDevice::getNumBonds() { /** * @brief Deletes all bonding information. */ - /*STATIC*/ +/*STATIC*/ void NimBLEDevice::deleteAllBonds() { ble_store_clear(); } @@ -576,6 +667,7 @@ NimBLEAddress NimBLEDevice::getBondedAddress(int index) { * @param [in] address The address to check for in the whitelist. * @returns true if the address is in the whitelist. */ +/*STATIC*/ bool NimBLEDevice::onWhiteList(const NimBLEAddress & address) { for (auto &it : m_whiteList) { if (it == address) { @@ -592,6 +684,7 @@ bool NimBLEDevice::onWhiteList(const NimBLEAddress & address) { * @param [in] address The address to add to the whitelist. * @returns true if successful. */ +/*STATIC*/ bool NimBLEDevice::whiteListAdd(const NimBLEAddress & address) { if (NimBLEDevice::onWhiteList(address)) { return true; @@ -623,6 +716,7 @@ bool NimBLEDevice::whiteListAdd(const NimBLEAddress & address) { * @param [in] address The address to remove from the whitelist. * @returns true if successful. */ +/*STATIC*/ bool NimBLEDevice::whiteListRemove(const NimBLEAddress & address) { if (!NimBLEDevice::onWhiteList(address)) { return true; @@ -662,6 +756,7 @@ bool NimBLEDevice::whiteListRemove(const NimBLEAddress & address) { * @brief Gets the count of addresses in the whitelist. * @returns The number of addresses in the whitelist. */ +/*STATIC*/ size_t NimBLEDevice::getWhiteListCount() { return m_whiteList.size(); } @@ -672,6 +767,7 @@ size_t NimBLEDevice::getWhiteListCount() { * @param [in] index The vector index to retrieve the address from. * @returns the NimBLEAddress at the whitelist index or nullptr if not found. */ +/*STATIC*/ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { if (index > m_whiteList.size()) { NIMBLE_LOGE(LOG_TAG, "Invalid index; %u", index); @@ -685,7 +781,8 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { * @brief Host reset, we pass the message so we don't make calls until resynced. * @param [in] reason The reason code for the reset. */ -/* STATIC */ void NimBLEDevice::onReset(int reason) +/* STATIC */ +void NimBLEDevice::onReset(int reason) { if(!m_synced) { return; @@ -709,7 +806,8 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { /** * @brief Host resynced with controller, all clear to make calls to the stack. */ -/* STATIC */ void NimBLEDevice::onSync(void) +/* STATIC */ +void NimBLEDevice::onSync(void) { NIMBLE_LOGI(LOG_TAG, "NimBle host synced."); // This check is needed due to potentially being called multiple times in succession @@ -722,6 +820,14 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { int rc = ble_hs_util_ensure_addr(0); assert(rc == 0); +#ifndef ESP_PLATFORM + rc = ble_hs_id_infer_auto(m_own_addr_type, &m_own_addr_type); + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "error determining address type; rc=%d", rc); + return; + } +#endif + // Yield for houskeeping before returning to operations. // Occasionally triggers exception without. taskYIELD(); @@ -747,9 +853,11 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { /** * @brief The main host task. */ -/* STATIC */ void NimBLEDevice::host_task(void *param) +/* STATIC */ +void NimBLEDevice::host_task(void *param) { NIMBLE_LOGI(LOG_TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ nimble_port_run(); @@ -761,9 +869,11 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { * @brief Initialize the %BLE environment. * @param [in] deviceName The device name of the device. */ -/* STATIC */ void NimBLEDevice::init(const std::string &deviceName) { +/* STATIC */ +void NimBLEDevice::init(const std::string &deviceName) { if(!initialized){ int rc=0; +#ifdef ESP_PLATFORM esp_err_t errRc = ESP_OK; #ifdef CONFIG_ENABLE_ARDUINO_DEPENDS @@ -783,7 +893,7 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); -#ifdef CONFIG_IDF_TARGET_ESP32C3 +#if defined (CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) bt_cfg.bluetooth_mode = ESP_BT_MODE_BLE; #else bt_cfg.mode = ESP_BT_MODE_BLE; @@ -795,6 +905,7 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg)); ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE)); ESP_ERROR_CHECK(esp_nimble_hci_init()); +#endif nimble_port_init(); // Setup callbacks for host events @@ -819,9 +930,10 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { nimble_port_freertos_init(NimBLEDevice::host_task); } + // Wait for host and controller to sync before returning and accepting new tasks while(!m_synced){ - vTaskDelay(1 / portTICK_PERIOD_MS); + taskYIELD(); } initialized = true; // Set the initialization flag to ensure we are only initialized once. @@ -833,16 +945,17 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { * @param [in] clearAll If true, deletes all server/advertising/scan/client objects after deinitializing. * @note If clearAll is true when called, any references to the created objects become invalid. */ -/* STATIC */ void NimBLEDevice::deinit(bool clearAll) { +/* STATIC */ +void NimBLEDevice::deinit(bool clearAll) { int ret = nimble_port_stop(); if (ret == 0) { nimble_port_deinit(); - +#ifdef ESP_PLATFORM ret = esp_nimble_hci_and_controller_deinit(); if (ret != ESP_OK) { NIMBLE_LOGE(LOG_TAG, "esp_nimble_hci_and_controller_deinit() failed with error: %d", ret); } - +#endif initialized = false; m_synced = false; @@ -889,6 +1002,7 @@ NimBLEAddress NimBLEDevice::getWhiteListAddress(size_t index) { * @brief Check if the initialization is complete. * @return true if initialized. */ +/*STATIC*/ bool NimBLEDevice::getInitialized() { return initialized; } // getInitialized @@ -900,7 +1014,8 @@ bool NimBLEDevice::getInitialized() { * @param mitm If true we are capable of man in the middle protection, false if not. * @param sc If true we will perform secure connection pairing, false we will use legacy pairing. */ -/*STATIC*/ void NimBLEDevice::setSecurityAuth(bool bonding, bool mitm, bool sc) { +/*STATIC*/ +void NimBLEDevice::setSecurityAuth(bool bonding, bool mitm, bool sc) { NIMBLE_LOGD(LOG_TAG, "Setting bonding: %d, mitm: %d, sc: %d",bonding,mitm,sc); ble_hs_cfg.sm_bonding = bonding; ble_hs_cfg.sm_mitm = mitm; @@ -917,7 +1032,8 @@ bool NimBLEDevice::getInitialized() { * * 0x08 BLE_SM_PAIR_AUTHREQ_SC * * 0x10 BLE_SM_PAIR_AUTHREQ_KEYPRESS - not yet supported. */ -/*STATIC*/void NimBLEDevice::setSecurityAuth(uint8_t auth_req) { +/*STATIC*/ +void NimBLEDevice::setSecurityAuth(uint8_t auth_req) { NimBLEDevice::setSecurityAuth((auth_req & BLE_SM_PAIR_AUTHREQ_BOND)>0, (auth_req & BLE_SM_PAIR_AUTHREQ_MITM)>0, (auth_req & BLE_SM_PAIR_AUTHREQ_SC)>0); @@ -933,7 +1049,8 @@ bool NimBLEDevice::getInitialized() { * * 0x03 BLE_HS_IO_NO_INPUT_OUTPUT NoInputNoOutput IO capability * * 0x04 BLE_HS_IO_KEYBOARD_DISPLAY KeyboardDisplay Only IO capability */ -/*STATIC*/ void NimBLEDevice::setSecurityIOCap(uint8_t iocap) { +/*STATIC*/ +void NimBLEDevice::setSecurityIOCap(uint8_t iocap) { ble_hs_cfg.sm_io_cap = iocap; } // setSecurityIOCap @@ -947,7 +1064,8 @@ bool NimBLEDevice::getInitialized() { * * 0x04: BLE_SM_PAIR_KEY_DIST_SIGN * * 0x08: BLE_SM_PAIR_KEY_DIST_LINK */ -/*STATIC*/void NimBLEDevice::setSecurityInitKey(uint8_t init_key) { +/*STATIC*/ +void NimBLEDevice::setSecurityInitKey(uint8_t init_key) { ble_hs_cfg.sm_our_key_dist = init_key; } // setsSecurityInitKey @@ -961,7 +1079,8 @@ bool NimBLEDevice::getInitialized() { * * 0x04: BLE_SM_PAIR_KEY_DIST_SIGN * * 0x08: BLE_SM_PAIR_KEY_DIST_LINK */ -/*STATIC*/void NimBLEDevice::setSecurityRespKey(uint8_t resp_key) { +/*STATIC*/ +void NimBLEDevice::setSecurityRespKey(uint8_t resp_key) { ble_hs_cfg.sm_their_key_dist = resp_key; } // setsSecurityRespKey @@ -970,7 +1089,8 @@ bool NimBLEDevice::getInitialized() { * @brief Set the passkey the server will ask for when pairing. * @param [in] pin The passkey to use. */ -/*STATIC*/void NimBLEDevice::setSecurityPasskey(uint32_t pin) { +/*STATIC*/ +void NimBLEDevice::setSecurityPasskey(uint32_t pin) { m_passkey = pin; } // setSecurityPasskey @@ -979,7 +1099,8 @@ bool NimBLEDevice::getInitialized() { * @brief Get the current passkey used for pairing. * @return The current passkey. */ -/*STATIC*/uint32_t NimBLEDevice::getSecurityPasskey() { +/*STATIC*/ +uint32_t NimBLEDevice::getSecurityPasskey() { return m_passkey; } // getSecurityPasskey @@ -989,11 +1110,13 @@ bool NimBLEDevice::getInitialized() { * @param [in] callbacks Pointer to NimBLESecurityCallbacks class * @deprecated For backward compatibility, New code should use client/server callback methods. */ +/*STATIC*/ void NimBLEDevice::setSecurityCallbacks(NimBLESecurityCallbacks* callbacks) { NimBLEDevice::m_securityCallbacks = callbacks; } // setSecurityCallbacks +#ifdef ESP_PLATFORM /** * @brief Set the own address type. * @param [in] own_addr_type Own Bluetooth Device address type.\n @@ -1004,6 +1127,7 @@ void NimBLEDevice::setSecurityCallbacks(NimBLESecurityCallbacks* callbacks) { * * 0x03: BLE_OWN_ADDR_RPA_RANDOM_DEFAULT * @param [in] useNRPA If true, and address type is random, uses a non-resolvable random address. */ +/*STATIC*/ void NimBLEDevice::setOwnAddrType(uint8_t own_addr_type, bool useNRPA) { m_own_addr_type = own_addr_type; switch (own_addr_type) { @@ -1027,18 +1151,15 @@ void NimBLEDevice::setOwnAddrType(uint8_t own_addr_type, bool useNRPA) { break; } } // setOwnAddrType - +#endif /** * @brief Start the connection securing and authorization for this connection. * @param conn_id The connection id of the peer device. * @returns NimBLE stack return code, 0 = success. */ -/* STATIC */int NimBLEDevice::startSecurity(uint16_t conn_id) { - /* if(m_securityCallbacks != nullptr) { - m_securityCallbacks->onSecurityRequest(); - } - */ +/* STATIC */ +int NimBLEDevice::startSecurity(uint16_t conn_id) { int rc = ble_gap_security_initiate(conn_id); if(rc != 0){ NIMBLE_LOGE(LOG_TAG, "ble_gap_security_initiate: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); @@ -1053,7 +1174,8 @@ void NimBLEDevice::setOwnAddrType(uint8_t own_addr_type, bool useNRPA) { * @param [in] address The address to look for. * @return True if ignoring. */ -/*STATIC*/ bool NimBLEDevice::isIgnored(const NimBLEAddress &address) { +/*STATIC*/ +bool NimBLEDevice::isIgnored(const NimBLEAddress &address) { for(auto &it : m_ignoreList) { if(it.equals(address)){ return true; @@ -1068,7 +1190,8 @@ void NimBLEDevice::setOwnAddrType(uint8_t own_addr_type, bool useNRPA) { * @brief Add a device to the ignore list. * @param [in] address The address of the device we want to ignore. */ -/*STATIC*/ void NimBLEDevice::addIgnored(const NimBLEAddress &address) { +/*STATIC*/ +void NimBLEDevice::addIgnored(const NimBLEAddress &address) { m_ignoreList.push_back(address); } @@ -1077,7 +1200,8 @@ void NimBLEDevice::setOwnAddrType(uint8_t own_addr_type, bool useNRPA) { * @brief Remove a device from the ignore list. * @param [in] address The address of the device we want to remove from the list. */ -/*STATIC*/void NimBLEDevice::removeIgnored(const NimBLEAddress &address) { +/*STATIC*/ +void NimBLEDevice::removeIgnored(const NimBLEAddress &address) { for(auto it = m_ignoreList.begin(); it != m_ignoreList.end(); ++it) { if((*it).equals(address)){ m_ignoreList.erase(it); @@ -1091,6 +1215,7 @@ void NimBLEDevice::setOwnAddrType(uint8_t own_addr_type, bool useNRPA) { * @brief Set a custom callback for gap events. * @param [in] handler The function to call when gap events occur. */ +/*STATIC*/ void NimBLEDevice::setCustomGapHandler(gap_event_handler handler) { m_customGapHandler = handler; int rc = ble_gap_event_listener_register(&m_listener, m_customGapHandler, NULL); @@ -1102,5 +1227,4 @@ void NimBLEDevice::setCustomGapHandler(gap_event_handler handler) { } } // setCustomGapHandler - #endif // CONFIG_BT_ENABLED diff --git a/src/NimBLEDevice.h b/src/NimBLEDevice.h index 7523656..a549eac 100644 --- a/src/NimBLEDevice.h +++ b/src/NimBLEDevice.h @@ -14,17 +14,20 @@ #ifndef MAIN_NIMBLEDEVICE_H_ #define MAIN_NIMBLEDEVICE_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" +#if defined(CONFIG_BT_ENABLED) #if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) #include "NimBLEScan.h" #endif #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) -#include "NimBLEAdvertising.h" +# if CONFIG_BT_NIMBLE_EXT_ADV +# include "NimBLEExtAdvertising.h" +# else +# include "NimBLEAdvertising.h" +# endif #endif #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) @@ -41,7 +44,9 @@ #include "NimBLESecurity.h" #include "NimBLEAddress.h" -#include "esp_bt.h" +#ifdef ESP_PLATFORM +# include "esp_bt.h" +#endif #include #include @@ -115,8 +120,17 @@ public: static NimBLEMeshNode* createMeshNode(NimBLEUUID uuid, uint8_t type); static NimBLEMeshNode* getMeshNode(); +#ifdef ESP_PLATFORM static void setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType=ESP_BLE_PWR_TYPE_DEFAULT); static int getPower(esp_ble_power_type_t powerType=ESP_BLE_PWR_TYPE_DEFAULT); + static void setOwnAddrType(uint8_t own_addr_type, bool useNRPA=false); + static void setScanDuplicateCacheSize(uint16_t cacheSize); + static void setScanFilterMode(uint8_t type); +#else + static void setPower(int dbm); + static int getPower(); +#endif + static void setCustomGapHandler(gap_event_handler handler); static void setSecurityAuth(bool bonding, bool mitm, bool sc); static void setSecurityAuth(uint8_t auth_req); @@ -126,20 +140,26 @@ public: static void setSecurityPasskey(uint32_t pin); static uint32_t getSecurityPasskey(); static void setSecurityCallbacks(NimBLESecurityCallbacks* pCallbacks); - static void setOwnAddrType(uint8_t own_addr_type, bool useNRPA=false); static int startSecurity(uint16_t conn_id); static int setMTU(uint16_t mtu); static uint16_t getMTU(); static bool isIgnored(const NimBLEAddress &address); static void addIgnored(const NimBLEAddress &address); static void removeIgnored(const NimBLEAddress &address); - static void setScanDuplicateCacheSize(uint16_t cacheSize); - static void setScanFilterMode(uint8_t type); #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) - static NimBLEAdvertising* getAdvertising(); - static void startAdvertising(); - static void stopAdvertising(); +# if CONFIG_BT_NIMBLE_EXT_ADV + static NimBLEExtAdvertising* getAdvertising(); + static bool startAdvertising(uint8_t inst_id, + int duration = 0, + int max_events = 0); + static bool stopAdvertising(uint8_t inst_id); + static bool stopAdvertising(); +# else + static NimBLEAdvertising* getAdvertising(); + static bool startAdvertising(); + static bool stopAdvertising(); +# endif #endif #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) @@ -176,6 +196,10 @@ private: #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) friend class NimBLEAdvertising; +# if CONFIG_BT_NIMBLE_EXT_ADV + friend class NimBLEExtAdvertising; + friend class NimBLEExtAdvertisement; +# endif #endif static void onReset(int reason); @@ -192,7 +216,11 @@ private: #endif #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +# if CONFIG_BT_NIMBLE_EXT_ADV + static NimBLEExtAdvertising* m_bleAdvertising; +# else static NimBLEAdvertising* m_bleAdvertising; +# endif #endif #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) @@ -204,8 +232,10 @@ private: static ble_gap_event_listener m_listener; static gap_event_handler m_customGapHandler; static uint8_t m_own_addr_type; +#ifdef ESP_PLATFORM static uint16_t m_scanDuplicateSize; static uint8_t m_scanFilterMode; +#endif static std::vector m_whiteList; static NimBLEMeshNode* m_pMeshNode; }; diff --git a/src/NimBLEEddystoneTLM.cpp b/src/NimBLEEddystoneTLM.cpp index a072942..255131c 100644 --- a/src/NimBLEEddystoneTLM.cpp +++ b/src/NimBLEEddystoneTLM.cpp @@ -11,12 +11,14 @@ * Created on: Mar 12, 2018 * Author: pcbreflux */ -#include "sdkconfig.h" + +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) #include "NimBLEEddystoneTLM.h" #include "NimBLELog.h" +#include #include #define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) @@ -124,30 +126,30 @@ std::string NimBLEEddystoneTLM::toString() { out += " C\n"; out += "Adv. Count "; - snprintf(val, sizeof(val), "%d", ENDIAN_CHANGE_U32(m_eddystoneData.advCount)); + snprintf(val, sizeof(val), "%" PRIu32, ENDIAN_CHANGE_U32(m_eddystoneData.advCount)); out += val; out += "\n"; out += "Time in seconds "; - snprintf(val, sizeof(val), "%d", rawsec/10); + snprintf(val, sizeof(val), "%" PRIu32, rawsec/10); out += val; out += "\n"; out += "Time "; - snprintf(val, sizeof(val), "%04d", rawsec / 864000); + snprintf(val, sizeof(val), "%04" PRIu32, rawsec / 864000); out += val; out += "."; - snprintf(val, sizeof(val), "%02d", (rawsec / 36000) % 24); + snprintf(val, sizeof(val), "%02" PRIu32, (rawsec / 36000) % 24); out += val; out += ":"; - snprintf(val, sizeof(val), "%02d", (rawsec / 600) % 60); + snprintf(val, sizeof(val), "%02" PRIu32, (rawsec / 600) % 60); out += val; out += ":"; - snprintf(val, sizeof(val), "%02d", (rawsec / 10) % 60); + snprintf(val, sizeof(val), "%02" PRIu32, (rawsec / 10) % 60); out += val; out += "\n"; diff --git a/src/NimBLEEddystoneTLM.h b/src/NimBLEEddystoneTLM.h index eb1cb07..265c81b 100644 --- a/src/NimBLEEddystoneTLM.h +++ b/src/NimBLEEddystoneTLM.h @@ -14,6 +14,7 @@ #ifndef _NimBLEEddystoneTLM_H_ #define _NimBLEEddystoneTLM_H_ + #include "NimBLEUUID.h" #include diff --git a/src/NimBLEEddystoneURL.cpp b/src/NimBLEEddystoneURL.cpp index 7c3194c..424df95 100644 --- a/src/NimBLEEddystoneURL.cpp +++ b/src/NimBLEEddystoneURL.cpp @@ -11,7 +11,7 @@ * Created on: Mar 12, 2018 * Author: pcbreflux */ -#include "sdkconfig.h" +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) #include "NimBLEEddystoneURL.h" diff --git a/src/NimBLEExtAdvertising.cpp b/src/NimBLEExtAdvertising.cpp new file mode 100644 index 0000000..b488e7f --- /dev/null +++ b/src/NimBLEExtAdvertising.cpp @@ -0,0 +1,870 @@ +/* + * NimBLEExtAdvertising.cpp + * + * Created: on February 6, 2022 + * Author H2zero + */ + +#include "nimconfig.h" +#if defined(CONFIG_BT_ENABLED) && \ + defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \ + CONFIG_BT_NIMBLE_EXT_ADV + +#if defined(CONFIG_NIMBLE_CPP_IDF) +#include "services/gap/ble_svc_gap.h" +#else +#include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h" +#endif +#include "NimBLEExtAdvertising.h" +#include "NimBLEDevice.h" +#include "NimBLEServer.h" +#include "NimBLEUtils.h" +#include "NimBLELog.h" + +static NimBLEExtAdvertisingCallbacks defaultCallbacks; +static const char* LOG_TAG = "NimBLEExtAdvertising"; + + +/** + * @brief Destructor: deletes callback instances if requested. + */ +NimBLEExtAdvertising::~NimBLEExtAdvertising() { + if(m_deleteCallbacks && m_pCallbacks != &defaultCallbacks) { + delete m_pCallbacks; + } +} + + +/** + * @brief Register the extended advertisement data. + * @param [in] inst_id The extended advertisement instance ID to assign to this data. + * @param [in] adv The extended advertisement instance with the data to set. + * @return True if advertising started successfully. + */ +bool NimBLEExtAdvertising::setInstanceData(uint8_t inst_id, NimBLEExtAdvertisement& adv) { + adv.m_params.sid = inst_id; + + // Legacy advertising as connectable requires the scannable flag also. + if (adv.m_params.legacy_pdu && adv.m_params.connectable) { + adv.m_params.scannable = true; + } + + // If connectable or not scannable disable the callback for scan response requests + if (adv.m_params.connectable || !adv.m_params.scannable) { + adv.m_params.scan_req_notif = false; + } + +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + NimBLEServer* pServer = NimBLEDevice::getServer(); + if (pServer != nullptr) { + if (!pServer->m_gattsStarted) { + pServer->start(); + } + } + + int rc = ble_gap_ext_adv_configure(inst_id, + &adv.m_params, + NULL, + (pServer != nullptr) ? NimBLEServer::handleGapEvent : + NimBLEExtAdvertising::handleGapEvent, + NULL); +#else + int rc = ble_gap_ext_adv_configure(inst_id, + &data.m_params, + NULL, + NimBLEExtAdvertising::handleGapEvent, + NULL); +#endif + + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Advertising config error: rc = %d", rc); + } else { + os_mbuf *buf; + buf = os_msys_get_pkthdr(adv.m_payload.size(), 0); + if (!buf) { + NIMBLE_LOGE(LOG_TAG, "Data buffer allocation failed"); + return false; + } + + rc = os_mbuf_append(buf, &adv.m_payload[0], adv.m_payload.size()); + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Unable to copy data: rc = %d", rc); + return false; + } else { + if (adv.m_params.scannable && !adv.m_params.legacy_pdu) { + rc = ble_gap_ext_adv_rsp_set_data(inst_id, buf); + } else { + rc = ble_gap_ext_adv_set_data(inst_id, buf); + } + + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Invalid advertisement data: rc = %d", rc); + } else { + if (adv.m_advAddress != NimBLEAddress("")) { + ble_addr_t addr; + memcpy(&addr.val, adv.m_advAddress.getNative(), 6); + // Custom advertising address must be random. + addr.type = BLE_OWN_ADDR_RANDOM; + rc = ble_gap_ext_adv_set_addr(inst_id, &addr); + } + + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Error setting advertisement address: rc = %d", rc); + return false; + } + } + } + } + + return (rc == 0); +} + + +/** + * @brief Set the scan response data for a legacy advertisement. + * @param [in] inst_id The extended advertisement instance ID to assign to this data. + * @param [in] lsr A reference to a NimBLEExtAdvertisement that contains the data. + */ +bool NimBLEExtAdvertising::setScanResponseData(uint8_t inst_id, NimBLEExtAdvertisement & lsr) { + os_mbuf *buf = os_msys_get_pkthdr(lsr.m_payload.size(), 0); + if (!buf) { + NIMBLE_LOGE(LOG_TAG, "Data buffer allocation failed"); + return false; + } + + int rc = os_mbuf_append(buf, &lsr.m_payload[0], lsr.m_payload.size()); + + if (rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Unable to copy scan data: rc = %d", rc); + return false; + } else { + rc = ble_gap_ext_adv_rsp_set_data(inst_id, buf); + } + return (rc == 0); +} + + +/** + * @brief Start extended advertising. + * @param [in] inst_id The extended advertisement instance ID to start. + * @param [in] duration How long to advertise for in milliseconds, 0 = forever (default). + * @param [in] max_events Maximum number of advertisement events to send, 0 = no limit (default). + * @return True if advertising started successfully. + */ +bool NimBLEExtAdvertising::start(uint8_t inst_id, int duration, int max_events) { + NIMBLE_LOGD(LOG_TAG, ">> Extended Advertising start"); + + // If Host is not synced we cannot start advertising. + if(!NimBLEDevice::m_synced) { + NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync."); + return false; + } + + int rc = ble_gap_ext_adv_start(inst_id, duration / 10, max_events); + + switch (rc) { + case 0: + m_advStatus[inst_id] = true; + break; + + case BLE_HS_EINVAL: + NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Value Error"); + break; + + case BLE_HS_EALREADY: + NIMBLE_LOGI(LOG_TAG, "Advertisement Already active"); + break; + + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_EOS: + case BLE_HS_ECONTROLLER: + case BLE_HS_ENOTSYNCED: + NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Host Reset"); + break; + + default: + NIMBLE_LOGE(LOG_TAG, "Error enabling advertising; rc=%d, %s", + rc, NimBLEUtils::returnCodeToString(rc)); + break; + } + + NIMBLE_LOGD(LOG_TAG, "<< Extended Advertising start"); + return (rc == 0 || rc == BLE_HS_EALREADY); +} // start + + +/** + * @brief Stop and remove this instance data from the advertisement set. + * @param [in] inst_id The extended advertisement instance to stop advertising. + * @return True if successful. + */ +bool NimBLEExtAdvertising::removeInstance(uint8_t inst_id) { + if (stop(inst_id)) { + int rc = ble_gap_ext_adv_remove(inst_id); + if (rc != 0 && rc != BLE_HS_EALREADY) { + NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_remove rc = %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); + return false; + } + return true; + } + + return false; +} // removeInstance + + +/** + * @brief Stop and remove all advertising instance data. + * @return True if successful. + */ +bool NimBLEExtAdvertising::removeAll() { + if (stop()) { + int rc = ble_gap_ext_adv_clear(); + if (rc == 0 || rc == BLE_HS_EALREADY) { + return true; + } else { + NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_clear rc = %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); + } + } + + return false; +} // removeAll + + +/** + * @brief Stop advertising this instance data. + * @param [in] inst_id The extended advertisement instance to stop advertising. + * @return True if successful. + */ +bool NimBLEExtAdvertising::stop(uint8_t inst_id) { + int rc = ble_gap_ext_adv_stop(inst_id); + if (rc != 0 && rc != BLE_HS_EALREADY) { + NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_stop rc = %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); + return false; + } + + m_advStatus[inst_id] = false; + return true; +} // stop + + +/** + * @brief Stop all advertisements. + * @return True if successful. + */ +bool NimBLEExtAdvertising::stop() { + int rc = ble_gap_ext_adv_clear(); + if (rc != 0 && rc != BLE_HS_EALREADY) { + NIMBLE_LOGE(LOG_TAG, "ble_gap_ext_adv_stop rc = %d %s", + rc, NimBLEUtils::returnCodeToString(rc)); + return false; + } + + for(auto it : m_advStatus) { + it = false; + } + + return true; +} // stop + + +/** + * @brief Set a callback to call when the advertisement stops. + * @param [in] pCallbacks A pointer to a callback to be invoked when an advertisment stops. + * @param [in] deleteCallbacks if true callback class will be deleted when advertising is destructed. + */ +void NimBLEExtAdvertising::setCallbacks(NimBLEExtAdvertisingCallbacks* pCallbacks, + bool deleteCallbacks) { + if (pCallbacks != nullptr){ + m_pCallbacks = pCallbacks; + m_deleteCallbacks = deleteCallbacks; + } else { + m_pCallbacks = &defaultCallbacks; + } +} // setCallbacks + + +/** + * @brief Check if currently advertising. + * @param [in] inst_id The instance ID of the advertised data to get the status of. + * @return True if advertising is active. + */ +bool NimBLEExtAdvertising::isActive(uint8_t inst_id) { + return m_advStatus[inst_id]; +} // isAdvertising + + +/** + * @brief Check if any instances are currently advertising. + * @return True if any instance is active. + */ +bool NimBLEExtAdvertising::isAdvertising() { + for (auto it : m_advStatus) { + if (it) { + return true; + } + } + return false; +} // isAdvertising + + +/* + * Host reset seems to clear advertising data, + * we need clear the flag so it reloads it. + */ +void NimBLEExtAdvertising::onHostSync() { + NIMBLE_LOGD(LOG_TAG, "Host re-synced"); + for(auto it : m_advStatus) { + it = false; + } +} // onHostSync + + +/** + * @brief Handler for gap events when not using peripheral role. + * @param [in] event the event data. + * @param [in] arg pointer to the advertising instance. + */ +/*STATIC*/ +int NimBLEExtAdvertising::handleGapEvent(struct ble_gap_event *event, void *arg) { + (void)arg; + NimBLEExtAdvertising* pAdv = NimBLEDevice::getAdvertising(); + + switch (event->type) { + case BLE_GAP_EVENT_ADV_COMPLETE: { + switch (event->adv_complete.reason) { + // Don't call the callback if host reset, we want to + // preserve the active flag until re-sync to restart advertising. + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_EOS: + case BLE_HS_ECONTROLLER: + case BLE_HS_ENOTSYNCED: + NIMBLE_LOGC(LOG_TAG, "host reset, rc = %d", event->adv_complete.reason); + NimBLEDevice::onReset(event->adv_complete.reason); + return 0; + default: + break; + } + pAdv->m_advStatus[event->adv_complete.instance] = false; + pAdv->m_pCallbacks->onStopped(pAdv, event->adv_complete.reason, + event->adv_complete.instance); + break; + } + + case BLE_GAP_EVENT_SCAN_REQ_RCVD: { + pAdv->m_pCallbacks->onScanRequest(pAdv, event->scan_req_rcvd.instance, + NimBLEAddress(event->scan_req_rcvd.scan_addr)); + break; + } + } + + return 0; +} // handleGapEvent + + +/** Default callback handlers */ +void NimBLEExtAdvertisingCallbacks::onStopped(NimBLEExtAdvertising *pAdv, + int reason, uint8_t inst_id) { + NIMBLE_LOGD("NimBLEExtAdvertisingCallbacks", "onStopped: Default"); +} // onStopped + + +void NimBLEExtAdvertisingCallbacks::onScanRequest(NimBLEExtAdvertising *pAdv, + uint8_t inst_id, NimBLEAddress addr) { + NIMBLE_LOGD("NimBLEExtAdvertisingCallbacks", "onScanRequest: Default"); +} // onScanRequest + + +/** + * @brief Construct a BLE extended advertisement. + * @param [in] priPhy The primary Phy to advertise on, can be one of: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_CODED + * @param [in] secPhy The secondary Phy to advertise on, can be one of: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_2M + * * BLE_HCI_LE_PHY_CODED + */ +NimBLEExtAdvertisement::NimBLEExtAdvertisement(uint8_t priPhy, uint8_t secPhy) +: m_advAddress("") +{ + memset (&m_params, 0, sizeof(m_params)); + m_params.own_addr_type = NimBLEDevice::m_own_addr_type; + m_params.primary_phy = priPhy; + m_params.secondary_phy = secPhy; + m_params.tx_power = 127; +} // NimBLEExtAdvertisement + + +/** + * @brief Sets wether the advertisement should use legacy (BLE 4.0, 31 bytes max) advertising. + * @param [in] val true = using legacy advertising. + */ +void NimBLEExtAdvertisement::setLegacyAdvertising(bool val) { + m_params.legacy_pdu = val; +} // setLegacyAdvertising + + +/** + * @brief Sets wether the advertisement has scan response data available. + * @param [in] val true = scan response is available. + */ +void NimBLEExtAdvertisement::setScannable(bool val) { + m_params.scannable = val; +} // setScannable + + + +/** + * @brief Sets the transmission power level for this advertisement. + * @param [in] dbm the transmission power to use in dbm. + * @details The allowable value range depends on device hardware. \n + * The ESP32C3 and ESP32S3 have a range of -27 to +18. + */ +void NimBLEExtAdvertisement::setTxPower(int8_t dbm) { + m_params.tx_power = dbm; +} + + +/** + * @brief Sets wether this advertisement should advertise as a connectable device. + * @param [in] val True = connectable. + */ +void NimBLEExtAdvertisement::setConnectable(bool val) { +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + m_params.connectable = val; +#endif +} // setConnectable + + +/** + * @brief Set the address to use for this advertisement. + * @param [in] addr The address to use. + */ +void NimBLEExtAdvertisement::setAddress(const NimBLEAddress & addr) { + m_advAddress = addr; + // Must use random address type. + m_params.own_addr_type = BLE_OWN_ADDR_RANDOM; +} + + +/** + * @brief Sets The primary channels to advertise on. + * @param [in] ch37 Advertise on channel 37. + * @param [in] ch38 Advertise on channel 38. + * @param [in] ch39 Advertise on channel 39. + * @details This will set a bitmask using the input parameters to allow different \n + * combinations. If all inputs are false then all 3 channels will be used. + */ +void NimBLEExtAdvertisement::setPrimaryChannels(bool ch37, bool ch38, bool ch39) { + m_params.channel_map = (ch37 | (ch38 << 1) | (ch39 << 2)); +} // setPrimaryChannels + + +/** + * @brief Set the filtering for the scan filter. + * @param [in] scanRequestWhitelistOnly If true, only allow scan requests from those on the white list. + * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. + */ +void NimBLEExtAdvertisement::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { + if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { + m_params.filter_policy = BLE_HCI_ADV_FILT_NONE; + return; + } + if (scanRequestWhitelistOnly && !connectWhitelistOnly) { + m_params.filter_policy = BLE_HCI_ADV_FILT_SCAN; + return; + } + if (!scanRequestWhitelistOnly && connectWhitelistOnly) { + m_params.filter_policy = BLE_HCI_ADV_FILT_CONN; + return; + } + if (scanRequestWhitelistOnly && connectWhitelistOnly) { + m_params.filter_policy = BLE_HCI_ADV_FILT_BOTH; + return; + } +} // setScanFilter + + +/** + * @brief Sets the peer to directly advertise to. + * @param [in] addr The address of the peer to direct the advertisements. + */ +void NimBLEExtAdvertisement::setDirectedPeer(const NimBLEAddress & addr) { + ble_addr_t peerAddr; + memcpy(&peerAddr.val, addr.getNative(), 6); + peerAddr.type = addr.getType(); + m_params.peer = peerAddr; +} // setDirectedPeer + + +/** + * @brief Enable or disable direct advertisements to the peer set with `NimBLEExtAdvertisement::setDirectedPeer` + * @param [in] val true = send directed advertisements to peer. + * @param [in] high_duty true = use fast advertising rate, default - true. + */ +void NimBLEExtAdvertisement::setDirected(bool val, bool high_duty) { + m_params.directed = val; + m_params.high_duty_directed = high_duty; +} // setDirected + + +/** + * @brief Set the minimum advertising interval. + * @param [in] mininterval Minimum value for advertising interval in 0.625ms units, 0 = use default. + */ +void NimBLEExtAdvertisement::setMinInterval(uint32_t mininterval) { + m_params.itvl_min = mininterval; +} // setMinInterval + + +/** + * @brief Set the maximum advertising interval. + * @param [in] maxinterval Maximum value for advertising interval in 0.625ms units, 0 = use default. + */ +void NimBLEExtAdvertisement::setMaxInterval(uint32_t maxinterval) { + m_params.itvl_max = maxinterval; +} // setMaxInterval + + +/** + * @brief Set the primary advertising PHY to use + * @param [in] phy Can be one of following constants: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_CODED + */ +void NimBLEExtAdvertisement::setPrimaryPhy(uint8_t phy) { + m_params.primary_phy = phy; +} // setPrimaryPhy + + +/** + * @brief Set the secondary advertising PHY to use + * @param [in] phy Can be one of following constants: + * * BLE_HCI_LE_PHY_1M + * * BLE_HCI_LE_PHY_2M + * * BLE_HCI_LE_PHY_CODED + */ +void NimBLEExtAdvertisement::setSecondaryPhy(uint8_t phy) { + m_params.secondary_phy = phy; +} // setSecondaryPhy + + +/** + * @brief Sets whether the advertisement should be anonymous. + * @param [in] val Set to true to enable anonymous advertising. + * + * @details Anonymous advertising omits the device's address from the advertisement. + */ +void NimBLEExtAdvertisement::setAnonymous(bool val) { + m_params.anonymous = val; +} // setAnonymous + + +/** + * @brief Sets whether the scan response request callback should be called. + * @param [in] enable If true the scan response request callback will be called for this advertisement. + */ +void NimBLEExtAdvertisement::enableScanRequestCallback(bool enable) { + m_params.scan_req_notif = enable; +} // enableScanRequestCallback + + +/** + * @brief Clears the data stored in this instance, does not change settings. + * @details This will clear all data but preserves advertising parameter settings. + */ +void NimBLEExtAdvertisement::clearData() { + std::vector swap; + std::swap(m_payload, swap); +} + + +/** + * @brief Get the size of the current data. + */ +size_t NimBLEExtAdvertisement::getDataSize() { + return m_payload.size(); +} // getDataSize + + +/** + * @brief Set the advertisement data. + * @param [in] data The data to be set as the payload. + * @param [in] length The size of data. + * @details This will completely replace any data that was previously set. + */ +void NimBLEExtAdvertisement::setData(const uint8_t * data, size_t length) { + m_payload.assign(data, data + length); +} // setData + + +/** + * @brief Add data to the payload to be advertised. + * @param [in] data The data to be added to the payload. + */ +void NimBLEExtAdvertisement::addData(const std::string &data) { + addData((uint8_t*)data.data(), data.length()); +} // addData + + +/** + * @brief Add data to the payload to be advertised. + * @param [in] data The data to be added to the payload. + * @param [in] length The size of data to be added to the payload. + */ +void NimBLEExtAdvertisement::addData(const uint8_t * data, size_t length) { + m_payload.insert(m_payload.end(), data, data + length); +} // addData + + +/** + * @brief Set the appearance. + * @param [in] appearance The appearance code value. + * + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml + */ +void NimBLEExtAdvertisement::setAppearance(uint16_t appearance) { + char cdata[2]; + cdata[0] = 3; + cdata[1] = BLE_HS_ADV_TYPE_APPEARANCE; // 0x19 + addData(std::string(cdata, 2) + std::string((char*) &appearance, 2)); +} // setAppearance + + +/** + * @brief Set the advertisement flags. + * @param [in] flag The flags to be set in the advertisement. + * * BLE_HS_ADV_F_DISC_LTD + * * BLE_HS_ADV_F_DISC_GEN + * * BLE_HS_ADV_F_BREDR_UNSUP - must always use with NimBLE + */ +void NimBLEExtAdvertisement::setFlags(uint8_t flag) { + char cdata[3]; + cdata[0] = 2; + cdata[1] = BLE_HS_ADV_TYPE_FLAGS; // 0x01 + cdata[2] = flag | BLE_HS_ADV_F_BREDR_UNSUP; + addData(std::string(cdata, 3)); +} // setFlags + + +/** + * @brief Set manufacturer specific data. + * @param [in] data The manufacturer data to advertise. + */ +void NimBLEExtAdvertisement::setManufacturerData(const std::string &data) { + char cdata[2]; + cdata[0] = data.length() + 1; + cdata[1] = BLE_HS_ADV_TYPE_MFG_DATA ; // 0xff + addData(std::string(cdata, 2) + data); +} // setManufacturerData + + +/** + * @brief Set the URI to advertise. + * @param [in] uri The uri to advertise. + */ +void NimBLEExtAdvertisement::setURI(const std::string &uri) { + char cdata[2]; + cdata[0] = uri.length() + 1; + cdata[1] = BLE_HS_ADV_TYPE_URI; + addData(std::string(cdata, 2) + uri); +} // setURI + + +/** + * @brief Set the complete name of this device. + * @param [in] name The name to advertise. + */ +void NimBLEExtAdvertisement::setName(const std::string &name) { + char cdata[2]; + cdata[0] = name.length() + 1; + cdata[1] = BLE_HS_ADV_TYPE_COMP_NAME; // 0x09 + addData(std::string(cdata, 2) + name); +} // setName + + +/** + * @brief Set a single service to advertise as a complete list of services. + * @param [in] uuid The service to advertise. + */ +void NimBLEExtAdvertisement::setCompleteServices(const NimBLEUUID &uuid) { + setServices(true, uuid.bitSize(), {uuid}); +} // setCompleteServices + + +/** + * @brief Set the complete list of 16 bit services to advertise. + * @param [in] v_uuid A vector of 16 bit UUID's to advertise. + */ +void NimBLEExtAdvertisement::setCompleteServices16(const std::vector& v_uuid) { + setServices(true, 16, v_uuid); +} // setCompleteServices16 + + +/** + * @brief Set the complete list of 32 bit services to advertise. + * @param [in] v_uuid A vector of 32 bit UUID's to advertise. + */ +void NimBLEExtAdvertisement::setCompleteServices32(const std::vector& v_uuid) { + setServices(true, 32, v_uuid); +} // setCompleteServices32 + + +/** + * @brief Set a single service to advertise as a partial list of services. + * @param [in] uuid The service to advertise. + */ +void NimBLEExtAdvertisement::setPartialServices(const NimBLEUUID &uuid) { + setServices(false, uuid.bitSize(), {uuid}); +} // setPartialServices + + +/** + * @brief Set the partial list of services to advertise. + * @param [in] v_uuid A vector of 16 bit UUID's to advertise. + */ +void NimBLEExtAdvertisement::setPartialServices16(const std::vector& v_uuid) { + setServices(false, 16, v_uuid); +} // setPartialServices16 + + +/** + * @brief Set the partial list of services to advertise. + * @param [in] v_uuid A vector of 32 bit UUID's to advertise. + */ +void NimBLEExtAdvertisement::setPartialServices32(const std::vector& v_uuid) { + setServices(false, 32, v_uuid); +} // setPartialServices32 + + +/** + * @brief Utility function to create the list of service UUID's from a vector. + * @param [in] complete If true the vector is the complete set of services. + * @param [in] size The bit size of the UUID's in the vector. (16, 32, or 128). + * @param [in] v_uuid The vector of service UUID's to advertise. + */ +void NimBLEExtAdvertisement::setServices(const bool complete, const uint8_t size, + const std::vector &v_uuid) +{ + char cdata[2]; + cdata[0] = (size / 8) * v_uuid.size() + 1; + switch(size) { + case 16: + cdata[1] = complete ? BLE_HS_ADV_TYPE_COMP_UUIDS16 : BLE_HS_ADV_TYPE_INCOMP_UUIDS16; + break; + case 32: + cdata[1] = complete ? BLE_HS_ADV_TYPE_COMP_UUIDS32 : BLE_HS_ADV_TYPE_INCOMP_UUIDS32; + break; + case 128: + cdata[1] = complete ? BLE_HS_ADV_TYPE_COMP_UUIDS128 : BLE_HS_ADV_TYPE_INCOMP_UUIDS128; + break; + default: + return; + } + + std::string uuids; + + for(auto &it : v_uuid){ + if(it.bitSize() != size) { + NIMBLE_LOGE(LOG_TAG, "Service UUID(%d) invalid", size); + return; + } else { + switch(size) { + case 16: + uuids += std::string((char*)&it.getNative()->u16.value, 2); + break; + case 32: + uuids += std::string((char*)&it.getNative()->u32.value, 4); + break; + case 128: + uuids += std::string((char*)&it.getNative()->u128.value, 16); + break; + default: + return; + } + } + } + + addData(std::string(cdata, 2) + uuids); +} // setServices + + +/** + * @brief Set the service data (UUID + data) + * @param [in] uuid The UUID to set with the service data. + * @param [in] data The data to be associated with the service data advertised. + */ +void NimBLEExtAdvertisement::setServiceData(const NimBLEUUID &uuid, const std::string &data) { + char cdata[2]; + switch (uuid.bitSize()) { + case 16: { + // [Len] [0x16] [UUID16] data + cdata[0] = data.length() + 3; + cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID16; // 0x16 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u16.value, 2) + data); + break; + } + + case 32: { + // [Len] [0x20] [UUID32] data + cdata[0] = data.length() + 5; + cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID32; // 0x20 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u32.value, 4) + data); + break; + } + + case 128: { + // [Len] [0x21] [UUID128] data + cdata[0] = data.length() + 17; + cdata[1] = BLE_HS_ADV_TYPE_SVC_DATA_UUID128; // 0x21 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->u128.value, 16) + data); + break; + } + + default: + return; + } +} // setServiceData + + +/** + * @brief Set the short name. + * @param [in] name The short name of the device. + */ +void NimBLEExtAdvertisement::setShortName(const std::string &name) { + char cdata[2]; + cdata[0] = name.length() + 1; + cdata[1] = BLE_HS_ADV_TYPE_INCOMP_NAME; // 0x08 + addData(std::string(cdata, 2) + name); +} // setShortName + + +/** + * @brief Adds Tx power level to the advertisement data. + */ +void NimBLEExtAdvertisement::addTxPower() { + m_params.include_tx_power = 1; +} // addTxPower + + +/** + * @brief Set the preferred connection interval parameters. + * @param [in] min The minimum interval desired. + * @param [in] max The maximum interval desired. + */ +void NimBLEExtAdvertisement::setPreferredParams(uint16_t min, uint16_t max) { + uint8_t data[6]; + data[0] = BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1; + data[1] = BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE; + data[2] = min; + data[3] = min >> 8; + data[4] = max; + data[5] = max >> 8; + addData(data, 6); +} // setPreferredParams + +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && CONFIG_BT_NIMBLE_EXT_ADV */ diff --git a/src/NimBLEExtAdvertising.h b/src/NimBLEExtAdvertising.h new file mode 100644 index 0000000..b1f21fc --- /dev/null +++ b/src/NimBLEExtAdvertising.h @@ -0,0 +1,152 @@ +/* + * NimBLEExtAdvertising.h + * + * Created: on February 6, 2022 + * Author H2zero + */ + +#ifndef MAIN_BLEEXTADVERTISING_H_ +#define MAIN_BLEEXTADVERTISING_H_ +#include "nimconfig.h" +#if defined(CONFIG_BT_ENABLED) && \ + defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) && \ + CONFIG_BT_NIMBLE_EXT_ADV + +# if defined(CONFIG_NIMBLE_CPP_IDF) +# include "host/ble_gap.h" +# else +# include "nimble/nimble/host/include/host/ble_gap.h" +# endif + +/**** FIX COMPILATION ****/ +#undef min +#undef max +/**************************/ + +#include "NimBLEAddress.h" +#include "NimBLEUUID.h" + +#include + +class NimBLEExtAdvertisingCallbacks; + + +/** + * @brief Extended advertisement data + */ +class NimBLEExtAdvertisement { +public: + NimBLEExtAdvertisement(uint8_t priPhy = BLE_HCI_LE_PHY_1M, + uint8_t secPhy = BLE_HCI_LE_PHY_1M); + void setAppearance(uint16_t appearance); + void setCompleteServices(const NimBLEUUID &uuid); + void setCompleteServices16(const std::vector &v_uuid); + void setCompleteServices32(const std::vector &v_uuid); + void setFlags(uint8_t flag); + void setManufacturerData(const std::string &data); + void setURI(const std::string &uri); + void setName(const std::string &name); + void setPartialServices(const NimBLEUUID &uuid); + void setPartialServices16(const std::vector &v_uuid); + void setPartialServices32(const std::vector &v_uuid); + void setServiceData(const NimBLEUUID &uuid, const std::string &data); + void setShortName(const std::string &name); + void setData(const uint8_t * data, size_t length); + void addData(const std::string &data); + void addData(const uint8_t * data, size_t length); + void addTxPower(); + void setPreferredParams(uint16_t min, uint16_t max); + void setLegacyAdvertising(bool val); + void setConnectable(bool val); + void setScannable(bool val); + void setMinInterval(uint32_t mininterval); + void setMaxInterval(uint32_t maxinterval); + void setPrimaryPhy(uint8_t phy); + void setSecondaryPhy(uint8_t phy); + void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly); + void setDirectedPeer(const NimBLEAddress & addr); + void setDirected(bool val, bool high_duty = true); + void setAnonymous(bool val); + void setPrimaryChannels(bool ch37, bool ch38, bool ch39); + void setTxPower(int8_t dbm); + void setAddress(const NimBLEAddress & addr); + void enableScanRequestCallback(bool enable); + void clearData(); + size_t getDataSize(); + +private: + friend class NimBLEExtAdvertising; + + void setServices(const bool complete, const uint8_t size, + const std::vector &v_uuid); + + std::vector m_payload; + ble_gap_ext_adv_params m_params; + NimBLEAddress m_advAddress; +}; // NimBLEExtAdvertisement + + +/** + * @brief Extended advertising class. + */ +class NimBLEExtAdvertising { +public: + /** + * @brief Construct an extended advertising object. + */ + NimBLEExtAdvertising() :m_advStatus(CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES + 1, false) {} + ~NimBLEExtAdvertising(); + bool start(uint8_t inst_id, int duration = 0, int max_events = 0); + bool setInstanceData(uint8_t inst_id, NimBLEExtAdvertisement& adv); + bool setScanResponseData(uint8_t inst_id, NimBLEExtAdvertisement & data); + bool removeInstance(uint8_t inst_id); + bool removeAll(); + bool stop(uint8_t inst_id); + bool stop(); + bool isActive(uint8_t inst_id); + bool isAdvertising(); + void setCallbacks(NimBLEExtAdvertisingCallbacks* callbacks, + bool deleteCallbacks = true); + +private: + friend class NimBLEDevice; + friend class NimBLEServer; + + void onHostSync(); + static int handleGapEvent(struct ble_gap_event *event, void *arg); + + bool m_scanResp; + bool m_deleteCallbacks; + NimBLEExtAdvertisingCallbacks* m_pCallbacks; + ble_gap_ext_adv_params m_advParams; + std::vector m_advStatus; +}; + + +/** + * @brief Callbacks associated with NimBLEExtAdvertising class. + */ +class NimBLEExtAdvertisingCallbacks { +public: + virtual ~NimBLEExtAdvertisingCallbacks() {}; + + /** + * @brief Handle an advertising stop event. + * @param [in] pAdv A convenience pointer to the extended advertising interface. + * @param [in] reason The reason code for stopping the advertising. + * @param [in] inst_id The instance ID of the advertisement that was stopped. + */ + virtual void onStopped(NimBLEExtAdvertising *pAdv, int reason, uint8_t inst_id); + + /** + * @brief Handle a scan response request. + * This is called when a scanning device requests a scan response. + * @param [in] pAdv A convenience pointer to the extended advertising interface. + * @param [in] inst_id The instance ID of the advertisement that the scan response request was made. + * @param [in] addr The address of the device making the request. + */ + virtual void onScanRequest(NimBLEExtAdvertising *pAdv, uint8_t inst_id, NimBLEAddress addr); +}; // NimBLEExtAdvertisingCallbacks + +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER && CONFIG_BT_NIMBLE_EXT_ADV */ +#endif /* MAIN_BLEADVERTISING_H_ */ diff --git a/src/NimBLEHIDDevice.cpp b/src/NimBLEHIDDevice.cpp index 37d0f52..78c8fea 100644 --- a/src/NimBLEHIDDevice.cpp +++ b/src/NimBLEHIDDevice.cpp @@ -11,11 +11,9 @@ * Created on: Jan 03, 2018 * Author: chegewara */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #include "NimBLEHIDDevice.h" #include "NimBLE2904.h" @@ -29,7 +27,7 @@ NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) { * Here we create mandatory services described in bluetooth specification */ m_deviceInfoService = server->createService(NimBLEUUID((uint16_t) 0x180a)); - m_hidService = server->createService(NimBLEUUID((uint16_t) 0x1812), 40); + m_hidService = server->createService(NimBLEUUID((uint16_t) 0x1812)); m_batteryService = server->createService(NimBLEUUID((uint16_t) 0x180f)); /* @@ -247,5 +245,4 @@ NimBLEService* NimBLEHIDDevice::batteryService() { return m_batteryService; } -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif // #if defined(CONFIG_BT_ENABLED) +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ diff --git a/src/NimBLEHIDDevice.h b/src/NimBLEHIDDevice.h index 6ed7c2b..ef2ed73 100644 --- a/src/NimBLEHIDDevice.h +++ b/src/NimBLEHIDDevice.h @@ -15,11 +15,8 @@ #ifndef _BLEHIDDEVICE_H_ #define _BLEHIDDEVICE_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) #include "NimBLECharacteristic.h" #include "NimBLEService.h" @@ -84,6 +81,6 @@ private: NimBLECharacteristic* m_protocolModeCharacteristic; //0x2a4e NimBLECharacteristic* m_batteryLevelCharacteristic; //0x2a19 }; -#endif // CONFIG_BT_NIMBLE_ROLE_BROADCASTER -#endif // CONFIG_BT_ENABLED + +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_BROADCASTER */ #endif /* _BLEHIDDEVICE_H_ */ diff --git a/src/NimBLELog.h b/src/NimBLELog.h index 8c13146..dda9073 100644 --- a/src/NimBLELog.h +++ b/src/NimBLELog.h @@ -8,59 +8,73 @@ #ifndef MAIN_NIMBLELOG_H_ #define MAIN_NIMBLELOG_H_ -#include "sdkconfig.h" +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) -#ifdef ARDUINO_ARCH_ESP32 -#include "syscfg/syscfg.h" -#include "modlog/modlog.h" +#if defined(CONFIG_NIMBLE_CPP_IDF) // using esp-idf +# include "esp_log.h" +# ifndef CONFIG_NIMBLE_CPP_LOG_LEVEL +# define CONFIG_NIMBLE_CPP_LOG_LEVEL 0 +# endif -// If Arduino is being used, strip out the colors and ignore log printing below ui setting. -// Note: because CONFIG_LOG_DEFAULT_LEVEL is set at ERROR in Arduino we must use MODLOG_DFLT(ERROR -// otherwise no messages will be printed above that level. +# define NIMBLE_CPP_LOG_PRINT(level, tag, format, ...) do { \ + if (CONFIG_NIMBLE_CPP_LOG_LEVEL >= level) \ + ESP_LOG_LEVEL_LOCAL(level, tag, format, ##__VA_ARGS__); \ + } while(0) -#ifndef CORE_DEBUG_LEVEL -#define CORE_DEBUG_LEVEL CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL -#endif +# define NIMBLE_LOGD(tag, format, ...) \ + NIMBLE_CPP_LOG_PRINT(ESP_LOG_DEBUG, tag, format, ##__VA_ARGS__) -#if CORE_DEBUG_LEVEL >= 4 -#define NIMBLE_LOGD( tag, format, ... ) MODLOG_DFLT(ERROR, "D %s: "#format"\n",tag,##__VA_ARGS__) -#else -#define NIMBLE_LOGD( tag, format, ... ) (void)tag -#endif +# define NIMBLE_LOGI(tag, format, ...) \ + NIMBLE_CPP_LOG_PRINT(ESP_LOG_INFO, tag, format, ##__VA_ARGS__) -#if CORE_DEBUG_LEVEL >= 3 -#define NIMBLE_LOGI( tag, format, ... ) MODLOG_DFLT(ERROR, "I %s: "#format"\n",tag,##__VA_ARGS__) -#else -#define NIMBLE_LOGI( tag, format, ... ) (void)tag -#endif +# define NIMBLE_LOGW(tag, format, ...) \ + NIMBLE_CPP_LOG_PRINT(ESP_LOG_WARN, tag, format, ##__VA_ARGS__) -#if CORE_DEBUG_LEVEL >= 2 -#define NIMBLE_LOGW( tag, format, ... ) MODLOG_DFLT(ERROR, "W %s: "#format"\n",tag,##__VA_ARGS__) -#else -#define NIMBLE_LOGW( tag, format, ... ) (void)tag -#endif +# define NIMBLE_LOGE(tag, format, ...) \ + NIMBLE_CPP_LOG_PRINT(ESP_LOG_ERROR, tag, format, ##__VA_ARGS__) -#if CORE_DEBUG_LEVEL >= 1 -#define NIMBLE_LOGE( tag, format, ... ) MODLOG_DFLT(ERROR, "E %s: "#format"\n",tag,##__VA_ARGS__) -#else -#define NIMBLE_LOGE( tag, format, ... ) (void)tag -#endif +# define NIMBLE_LOGC(tag, format, ...) \ + NIMBLE_CPP_LOG_PRINT(ESP_LOG_ERROR, tag, format, ##__VA_ARGS__) -#define NIMBLE_LOGC( tag, format, ... ) MODLOG_DFLT(CRITICAL, "CRIT %s: "#format"\n",tag,##__VA_ARGS__) +#else // using Arduino +# include "nimble/porting/nimble/include/syscfg/syscfg.h" +# include "nimble/console/console.h" +# ifndef CONFIG_NIMBLE_CPP_LOG_LEVEL +# if defined(ARDUINO_ARCH_ESP32) && defined(CORE_DEBUG_LEVEL) +# define CONFIG_NIMBLE_CPP_LOG_LEVEL CORE_DEBUG_LEVEL +# else +# define CONFIG_NIMBLE_CPP_LOG_LEVEL 0 +# endif +# endif -#else +# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 4 +# define NIMBLE_LOGD( tag, format, ... ) console_printf("D %s: " format "\n", tag, ##__VA_ARGS__) +# else +# define NIMBLE_LOGD( tag, format, ... ) (void)tag +# endif -#include "esp_log.h" +# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 3 +# define NIMBLE_LOGI( tag, format, ... ) console_printf("I %s: " format "\n", tag, ##__VA_ARGS__) +# else +# define NIMBLE_LOGI( tag, format, ... ) (void)tag +# endif -#define NIMBLE_LOGE(tag, format, ...) ESP_LOGE(tag, format, ##__VA_ARGS__) -#define NIMBLE_LOGW(tag, format, ...) ESP_LOGW(tag, format, ##__VA_ARGS__) -#define NIMBLE_LOGI(tag, format, ...) ESP_LOGI(tag, format, ##__VA_ARGS__) -#define NIMBLE_LOGD(tag, format, ...) ESP_LOGD(tag, format, ##__VA_ARGS__) -#define NIMBLE_LOGC(tag, format, ...) ESP_LOGE(tag, format, ##__VA_ARGS__) +# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 2 +# define NIMBLE_LOGW( tag, format, ... ) console_printf("W %s: " format "\n", tag, ##__VA_ARGS__) +# else +# define NIMBLE_LOGW( tag, format, ... ) (void)tag +# endif -#endif /*ARDUINO_ARCH_ESP32*/ +# if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 1 +# define NIMBLE_LOGE( tag, format, ... ) console_printf("E %s: " format "\n", tag, ##__VA_ARGS__) +# define NIMBLE_LOGC( tag, format, ... ) console_printf("CRIT %s: " format "\n", tag, ##__VA_ARGS__) +# else +# define NIMBLE_LOGE( tag, format, ... ) (void)tag +# define NIMBLE_LOGC( tag, format, ... ) (void)tag +# endif -#endif /*CONFIG_BT_ENABLED*/ -#endif /*MAIN_NIMBLELOG_H_*/ \ No newline at end of file +#endif /* CONFIG_NIMBLE_CPP_IDF */ +#endif /* CONFIG_BT_ENABLED */ +#endif /* MAIN_NIMBLELOG_H_ */ diff --git a/src/NimBLERemoteCharacteristic.cpp b/src/NimBLERemoteCharacteristic.cpp index 85c8549..6cca615 100644 --- a/src/NimBLERemoteCharacteristic.cpp +++ b/src/NimBLERemoteCharacteristic.cpp @@ -12,16 +12,15 @@ * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #include "NimBLERemoteCharacteristic.h" #include "NimBLEUtils.h" #include "NimBLELog.h" +#include + static const char* LOG_TAG = "NimBLERemoteCharacteristic"; /** @@ -59,8 +58,6 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic"; m_charProp = chr->properties; m_pRemoteService = pRemoteService; m_notifyCallback = nullptr; - m_timestamp = 0; - m_valMux = portMUX_INITIALIZER_UNLOCKED; NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteCharacteristic(): %s", m_uuid.toString().c_str()); } // NimBLERemoteCharacteristic @@ -241,7 +238,8 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filt } int rc = 0; - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; // If we don't know the end handle of this characteristic retrieve the next one in the service // The end handle is the next characteristic definition handle -1. @@ -256,6 +254,10 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filt return false; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); if (taskData.rc != 0) { @@ -264,6 +266,10 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filt } } + if (m_handle == m_endHandle) { + return true; + } + desc_filter_t filter = {uuid_filter, &taskData}; rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(), @@ -277,6 +283,10 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filt return false; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); if (taskData.rc != 0) { @@ -310,14 +320,31 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU return m_descriptorVector.back(); } - // If the request was successful but 16/32 bit descriptor not found + // If the request was successful but 16/32 bit uuid not found // try again with the 128 bit uuid. if(uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) { NimBLEUUID uuid128(uuid); uuid128.to128(); - return getDescriptor(uuid128); + if(retrieveDescriptors(&uuid128)) { + if(m_descriptorVector.size() > prev_size) { + return m_descriptorVector.back(); + } + } + } else { + // If the request was successful but the 128 bit uuid not found + // try again with the 16 bit uuid. + NimBLEUUID uuid16(uuid); + uuid16.to16(); + // if the uuid was 128 bit but not of the BLE base type this check will fail + if (uuid16.bitSize() == BLE_UUID_TYPE_16) { + if(retrieveDescriptors(&uuid16)) { + if(m_descriptorVector.size() > prev_size) { + return m_descriptorVector.back(); + } + } + } } } @@ -407,15 +434,12 @@ NimBLEUUID NimBLERemoteCharacteristic::getUUID() { * @param [in] timestamp A pointer to a time_t struct to store the time the value was read. * @return The value of the remote characteristic. */ -std::string NimBLERemoteCharacteristic::getValue(time_t *timestamp) { - portENTER_CRITICAL(&m_valMux); - std::string value = m_value; +NimBLEAttValue NimBLERemoteCharacteristic::getValue(time_t *timestamp) { if(timestamp != nullptr) { - *timestamp = m_timestamp; + *timestamp = m_value.getTimeStamp(); } - portEXIT_CRITICAL(&m_valMux); - return value; + return m_value; } @@ -463,12 +487,12 @@ float NimBLERemoteCharacteristic::readFloat() { * @param [in] timestamp A pointer to a time_t struct to store the time the value was read. * @return The value of the remote characteristic. */ -std::string NimBLERemoteCharacteristic::readValue(time_t *timestamp) { +NimBLEAttValue NimBLERemoteCharacteristic::readValue(time_t *timestamp) { NIMBLE_LOGD(LOG_TAG, ">> readValue(): uuid: %s, handle: %d 0x%.2x", getUUID().toString().c_str(), getHandle(), getHandle()); NimBLEClient* pClient = getRemoteService()->getClient(); - std::string value; + NimBLEAttValue value; if (!pClient->isConnected()) { NIMBLE_LOGE(LOG_TAG, "Disconnected"); @@ -477,7 +501,8 @@ std::string NimBLERemoteCharacteristic::readValue(time_t *timestamp) { int rc = 0; int retryCount = 1; - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(),0, &value}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, &value}; do { rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0, @@ -489,6 +514,10 @@ std::string NimBLERemoteCharacteristic::readValue(time_t *timestamp) { return value; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); rc = taskData.rc; @@ -514,14 +543,11 @@ std::string NimBLERemoteCharacteristic::readValue(time_t *timestamp) { } } while(rc != 0 && retryCount--); - time_t t = time(nullptr); - portENTER_CRITICAL(&m_valMux); + value.setTimeStamp(); m_value = value; - m_timestamp = t; if(timestamp != nullptr) { - *timestamp = m_timestamp; + *timestamp = value.getTimeStamp(); } - portEXIT_CRITICAL(&m_valMux); NIMBLE_LOGD(LOG_TAG, "<< readValue length: %d rc=%d", value.length(), rc); return value; @@ -546,16 +572,17 @@ int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle, NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle); - std::string *strBuf = (std::string*)pTaskData->buf; + NimBLEAttValue *valBuf = (NimBLEAttValue*)pTaskData->buf; int rc = error->status; if(rc == 0) { if(attr) { - if(((*strBuf).length() + attr->om->om_len) > BLE_ATT_ATTR_MAX_LEN) { + uint16_t data_len = OS_MBUF_PKTLEN(attr->om); + if((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) { rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } else { - NIMBLE_LOGD(LOG_TAG, "Got %d bytes", attr->om->om_len); - (*strBuf) += std::string((char*) attr->om->om_data, attr->om->om_len); + NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len); + valBuf->append(attr->om->om_data, data_len); return 0; } } @@ -706,22 +733,33 @@ std::string NimBLERemoteCharacteristic::toString() { /** - * @brief Write the new value for the characteristic. - * @param [in] newValue The new value to write. - * @param [in] response Do we expect a response? - * @return false if not connected or cant perform write for some reason. + * @brief Write a new value to the remote characteristic from a std::vector. + * @param [in] vec A std::vector value to write to the remote characteristic. + * @param [in] response Whether we require a response from the write. + * @return false if not connected or otherwise cannot perform write. */ -bool NimBLERemoteCharacteristic::writeValue(const std::string &newValue, bool response) { - return writeValue((uint8_t*)newValue.c_str(), newValue.length(), response); +bool NimBLERemoteCharacteristic::writeValue(const std::vector& vec, bool response) { + return writeValue((uint8_t*)&vec[0], vec.size(), response); } // writeValue /** - * @brief Write the new value for the characteristic from a data buffer. + * @brief Write a new value to the remote characteristic from a const char*. + * @param [in] char_s A character string to write to the remote characteristic. + * @param [in] response Whether we require a response from the write. + * @return false if not connected or otherwise cannot perform write. + */ +bool NimBLERemoteCharacteristic::writeValue(const char* char_s, bool response) { + return writeValue((uint8_t*)char_s, strlen(char_s), response); +} // writeValue + + +/** + * @brief Write a new value to the remote characteristic from a data buffer. * @param [in] data A pointer to a data buffer. * @param [in] length The length of the data in the data buffer. * @param [in] response Whether we require a response from the write. - * @return false if not connected or cant perform write for some reason. + * @return false if not connected or otherwise cannot perform write. */ bool NimBLERemoteCharacteristic::writeValue(const uint8_t* data, size_t length, bool response) { @@ -745,7 +783,8 @@ bool NimBLERemoteCharacteristic::writeValue(const uint8_t* data, size_t length, return (rc==0); } - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; do { if(length > mtu) { @@ -765,6 +804,10 @@ bool NimBLERemoteCharacteristic::writeValue(const uint8_t* data, size_t length, return false; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); rc = taskData.rc; @@ -819,6 +862,4 @@ int NimBLERemoteCharacteristic::onWriteCB(uint16_t conn_handle, return 0; } - -#endif // #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ diff --git a/src/NimBLERemoteCharacteristic.h b/src/NimBLERemoteCharacteristic.h index 39e6d40..353d832 100644 --- a/src/NimBLERemoteCharacteristic.h +++ b/src/NimBLERemoteCharacteristic.h @@ -14,17 +14,16 @@ #ifndef COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ #define COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #include "NimBLERemoteService.h" #include "NimBLERemoteDescriptor.h" #include #include +#include "NimBLELog.h" class NimBLERemoteService; class NimBLERemoteDescriptor; @@ -62,47 +61,15 @@ public: uint16_t getHandle(); uint16_t getDefHandle(); NimBLEUUID getUUID(); - std::string readValue(time_t *timestamp = nullptr); - - /** - * @brief A template to convert the remote characteristic data to . - * @tparam T The type to convert the data to. - * @param [in] timestamp A pointer to a time_t struct to store the time the value was read. - * @param [in] skipSizeCheck If true it will skip checking if the data size is less than sizeof(). - * @return The data converted to or NULL if skipSizeCheck is false and the data is - * less than sizeof(). - * @details Use: readValue(×tamp, skipSizeCheck); - */ - template - T readValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { - std::string value = readValue(timestamp); - if(!skipSizeCheck && value.size() < sizeof(T)) return T(); - const char *pData = value.data(); - return *((T *)pData); - } + NimBLEAttValue readValue(time_t *timestamp = nullptr); + std::string toString(); + NimBLERemoteService* getRemoteService(); uint8_t readUInt8() __attribute__ ((deprecated("Use template readValue()"))); uint16_t readUInt16() __attribute__ ((deprecated("Use template readValue()"))); uint32_t readUInt32() __attribute__ ((deprecated("Use template readValue()"))); float readFloat() __attribute__ ((deprecated("Use template readValue()"))); - std::string getValue(time_t *timestamp = nullptr); - - /** - * @brief A template to convert the remote characteristic data to . - * @tparam T The type to convert the data to. - * @param [in] timestamp A pointer to a time_t struct to store the time the value was read. - * @param [in] skipSizeCheck If true it will skip checking if the data size is less than sizeof(). - * @return The data converted to or NULL if skipSizeCheck is false and the data is - * less than sizeof(). - * @details Use: getValue(×tamp, skipSizeCheck); - */ - template - T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { - std::string value = getValue(timestamp); - if(!skipSizeCheck && value.size() < sizeof(T)) return T(); - const char *pData = value.data(); - return *((T *)pData); - } + NimBLEAttValue getValue(time_t *timestamp = nullptr); bool subscribe(bool notifications = true, notify_callback notifyCallback = nullptr, @@ -115,20 +82,74 @@ public: bool writeValue(const uint8_t* data, size_t length, bool response = false); - bool writeValue(const std::string &newValue, - bool response = false); + bool writeValue(const std::vector& v, bool response = false); + bool writeValue(const char* s, bool response = false); + + + /*********************** Template Functions ************************/ + /** - * @brief Convenience template to set the remote characteristic value to val. + * @brief Template to set the remote characteristic value to val. * @param [in] s The value to write. * @param [in] response True == request write response. + * @details Only used for non-arrays and types without a `c_str()` method. */ template - bool writeValue(const T &s, bool response = false) { +#ifdef _DOXYGEN_ + bool +#else + typename std::enable_if::value && !Has_c_str_len::value, bool>::type +#endif + writeValue(const T& s, bool response = false) { return writeValue((uint8_t*)&s, sizeof(T), response); } - std::string toString(); - NimBLERemoteService* getRemoteService(); + /** + * @brief Template to set the remote characteristic value to val. + * @param [in] s The value to write. + * @param [in] response True == request write response. + * @details Only used if the has a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + bool +#else + typename std::enable_if::value, bool>::type +#endif + writeValue(const T& s, bool response = false) { + return writeValue((uint8_t*)s.c_str(), s.length(), response); + } + + /** + * @brief Template to convert the remote characteristic data to . + * @tparam T The type to convert the data to. + * @param [in] timestamp A pointer to a time_t struct to store the time the value was read. + * @param [in] skipSizeCheck If true it will skip checking if the data size is less than sizeof(). + * @return The data converted to or NULL if skipSizeCheck is false and the data is + * less than sizeof(). + * @details Use: getValue(×tamp, skipSizeCheck); + */ + template + T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { + if(!skipSizeCheck && m_value.size() < sizeof(T)) return T(); + return *((T *)m_value.getValue(timestamp)); + } + + /** + * @brief Template to convert the remote characteristic data to . + * @tparam T The type to convert the data to. + * @param [in] timestamp A pointer to a time_t struct to store the time the value was read. + * @param [in] skipSizeCheck If true it will skip checking if the data size is less than sizeof(). + * @return The data converted to or NULL if skipSizeCheck is false and the data is + * less than sizeof(). + * @details Use: readValue(×tamp, skipSizeCheck); + */ + template + T readValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { + NimBLEAttValue value = readValue(); + if(!skipSizeCheck && value.size() < sizeof(T)) return T(); + return *((T *)value.getValue(timestamp)); + } private: @@ -158,15 +179,12 @@ private: uint16_t m_defHandle; uint16_t m_endHandle; NimBLERemoteService* m_pRemoteService; - std::string m_value; + NimBLEAttValue m_value; notify_callback m_notifyCallback; - time_t m_timestamp; - portMUX_TYPE m_valMux; // We maintain a vector of descriptors owned by this characteristic. std::vector m_descriptorVector; }; // NimBLERemoteCharacteristic -#endif // #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ */ diff --git a/src/NimBLERemoteDescriptor.cpp b/src/NimBLERemoteDescriptor.cpp index fc0f06b..cae9103 100644 --- a/src/NimBLERemoteDescriptor.cpp +++ b/src/NimBLERemoteDescriptor.cpp @@ -11,16 +11,16 @@ * Created on: Jul 8, 2017 * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #include "NimBLERemoteDescriptor.h" #include "NimBLEUtils.h" #include "NimBLELog.h" +#include + static const char* LOG_TAG = "NimBLERemoteDescriptor"; /** @@ -86,11 +86,7 @@ NimBLEUUID NimBLERemoteDescriptor::getUUID() { * @deprecated Use readValue(). */ uint8_t NimBLERemoteDescriptor::readUInt8() { - std::string value = readValue(); - if (value.length() >= 1) { - return (uint8_t) value[0]; - } - return 0; + return readValue(); } // readUInt8 @@ -100,11 +96,7 @@ uint8_t NimBLERemoteDescriptor::readUInt8() { * @deprecated Use readValue(). */ uint16_t NimBLERemoteDescriptor::readUInt16() { - std::string value = readValue(); - if (value.length() >= 2) { - return *(uint16_t*) value.data(); - } - return 0; + return readValue(); } // readUInt16 @@ -114,11 +106,7 @@ uint16_t NimBLERemoteDescriptor::readUInt16() { * @deprecated Use readValue(). */ uint32_t NimBLERemoteDescriptor::readUInt32() { - std::string value = readValue(); - if (value.length() >= 4) { - return *(uint32_t*) value.data(); - } - return 0; + return readValue(); } // readUInt32 @@ -126,11 +114,11 @@ uint32_t NimBLERemoteDescriptor::readUInt32() { * @brief Read the value of the remote descriptor. * @return The value of the remote descriptor. */ -std::string NimBLERemoteDescriptor::readValue() { +NimBLEAttValue NimBLERemoteDescriptor::readValue() { NIMBLE_LOGD(LOG_TAG, ">> Descriptor readValue: %s", toString().c_str()); NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient(); - std::string value; + NimBLEAttValue value; if (!pClient->isConnected()) { NIMBLE_LOGE(LOG_TAG, "Disconnected"); @@ -139,7 +127,8 @@ std::string NimBLERemoteDescriptor::readValue() { int rc = 0; int retryCount = 1; - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(),0, &value}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, &value}; do { rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0, @@ -151,6 +140,10 @@ std::string NimBLERemoteDescriptor::readValue() { return value; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); rc = taskData.rc; @@ -175,7 +168,7 @@ std::string NimBLERemoteDescriptor::readValue() { } } while(rc != 0 && retryCount--); - NIMBLE_LOGD(LOG_TAG, "<< Descriptor readValue(): length: %d rc=%d", value.length(), rc); + NIMBLE_LOGD(LOG_TAG, "<< Descriptor readValue(): length: %u rc=%d", value.length(), rc); return value; } // readValue @@ -188,6 +181,7 @@ int NimBLERemoteDescriptor::onReadCB(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { + (void)attr; ble_task_data_t *pTaskData = (ble_task_data_t*)arg; NimBLERemoteDescriptor* desc = (NimBLERemoteDescriptor*)pTaskData->pATT; uint16_t conn_id = desc->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId(); @@ -198,16 +192,17 @@ int NimBLERemoteDescriptor::onReadCB(uint16_t conn_handle, NIMBLE_LOGD(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle); - std::string *strBuf = (std::string*)pTaskData->buf; + NimBLEAttValue *valBuf = (NimBLEAttValue*)pTaskData->buf; int rc = error->status; if(rc == 0) { if(attr) { - if(((*strBuf).length() + attr->om->om_len) > BLE_ATT_ATTR_MAX_LEN) { + uint16_t data_len = OS_MBUF_PKTLEN(attr->om); + if((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) { rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } else { - NIMBLE_LOGD(LOG_TAG, "Got %d bytes", attr->om->om_len); - (*strBuf) += std::string((char*) attr->om->om_data, attr->om->om_len); + NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len); + valBuf->append(attr->om->om_data, data_len); return 0; } } @@ -260,11 +255,33 @@ int NimBLERemoteDescriptor::onWriteCB(uint16_t conn_handle, /** - * @brief Write data to the BLE Remote Descriptor. + * @brief Write a new value to a remote descriptor from a std::vector. + * @param [in] vec A std::vector value to write to the remote descriptor. + * @param [in] response Whether we require a response from the write. + * @return false if not connected or otherwise cannot perform write. + */ +bool NimBLERemoteDescriptor::writeValue(const std::vector& vec, bool response) { + return writeValue((uint8_t*)&vec[0], vec.size(), response); +} // writeValue + + +/** + * @brief Write a new value to the remote descriptor from a const char*. + * @param [in] char_s A character string to write to the remote descriptor. + * @param [in] response Whether we require a response from the write. + * @return false if not connected or otherwise cannot perform write. + */ +bool NimBLERemoteDescriptor::writeValue(const char* char_s, bool response) { + return writeValue((uint8_t*)char_s, strlen(char_s), response); +} // writeValue + + +/** + * @brief Write a new value to a remote descriptor. * @param [in] data The data to send to the remote descriptor. * @param [in] length The length of the data to send. * @param [in] response True if we expect a write response. - * @return True if successful + * @return false if not connected or otherwise cannot perform write. */ bool NimBLERemoteDescriptor::writeValue(const uint8_t* data, size_t length, bool response) { @@ -289,7 +306,8 @@ bool NimBLERemoteDescriptor::writeValue(const uint8_t* data, size_t length, bool return (rc == 0); } - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; do { if(length > mtu) { @@ -310,6 +328,10 @@ bool NimBLERemoteDescriptor::writeValue(const uint8_t* data, size_t length, bool return false; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); rc = taskData.rc; @@ -340,15 +362,4 @@ bool NimBLERemoteDescriptor::writeValue(const uint8_t* data, size_t length, bool } // writeValue -/** - * @brief Write data represented as a string to the BLE Remote Descriptor. - * @param [in] newValue The data to send to the remote descriptor. - * @param [in] response True if we expect a response. - * @return True if successful - */ -bool NimBLERemoteDescriptor::writeValue(const std::string &newValue, bool response) { - return writeValue((uint8_t*) newValue.data(), newValue.length(), response); -} // writeValue - -#endif // #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ diff --git a/src/NimBLERemoteDescriptor.h b/src/NimBLERemoteDescriptor.h index b52738e..28863df 100644 --- a/src/NimBLERemoteDescriptor.h +++ b/src/NimBLERemoteDescriptor.h @@ -14,11 +14,9 @@ #ifndef COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ #define COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #include "NimBLERemoteCharacteristic.h" @@ -31,10 +29,53 @@ public: uint16_t getHandle(); NimBLERemoteCharacteristic* getRemoteCharacteristic(); NimBLEUUID getUUID(); - std::string readValue(); + NimBLEAttValue readValue(); + + uint8_t readUInt8() __attribute__ ((deprecated("Use template readValue()"))); + uint16_t readUInt16() __attribute__ ((deprecated("Use template readValue()"))); + uint32_t readUInt32() __attribute__ ((deprecated("Use template readValue()"))); + std::string toString(void); + bool writeValue(const uint8_t* data, size_t length, bool response = false); + bool writeValue(const std::vector& v, bool response = false); + bool writeValue(const char* s, bool response = false); + + + /*********************** Template Functions ************************/ /** - * @brief A template to convert the remote descriptor data to . + * @brief Template to set the remote descriptor value to val. + * @param [in] s The value to write. + * @param [in] response True == request write response. + * @details Only used for non-arrays and types without a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + bool +#else + typename std::enable_if::value && !Has_c_str_len::value, bool>::type +#endif + writeValue(const T& s, bool response = false) { + return writeValue((uint8_t*)&s, sizeof(T), response); + } + + /** + * @brief Template to set the remote descriptor value to val. + * @param [in] s The value to write. + * @param [in] response True == request write response. + * @details Only used if the has a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + bool +#else + typename std::enable_if::value, bool>::type +#endif + writeValue(const T& s, bool response = false) { + return writeValue((uint8_t*)s.c_str(), s.length(), response); + } + + /** + * @brief Template to convert the remote descriptor data to . * @tparam T The type to convert the data to. * @param [in] skipSizeCheck If true it will skip checking if the data size is less than sizeof(). * @return The data converted to or NULL if skipSizeCheck is false and the data is @@ -42,28 +83,10 @@ public: * @details Use: readValue(skipSizeCheck); */ template - T readValue(bool skipSizeCheck = false) { - std::string value = readValue(); + T readValue(bool skipSizeCheck = false) { + NimBLEAttValue value = readValue(); if(!skipSizeCheck && value.size() < sizeof(T)) return T(); - const char *pData = value.data(); - return *((T *)pData); - } - - uint8_t readUInt8() __attribute__ ((deprecated("Use template readValue()"))); - uint16_t readUInt16() __attribute__ ((deprecated("Use template readValue()"))); - uint32_t readUInt32() __attribute__ ((deprecated("Use template readValue()"))); - std::string toString(void); - bool writeValue(const uint8_t* data, size_t length, bool response = false); - bool writeValue(const std::string &newValue, bool response = false); - - /** - * @brief Convenience template to set the remote descriptor value to val. - * @param [in] s The value to write. - * @param [in] response True == request write response. - */ - template - bool writeValue(const T &s, bool response = false) { - return writeValue((uint8_t*)&s, sizeof(T), response); + return *((T *)value.data()); } private: @@ -81,6 +104,5 @@ private: NimBLERemoteCharacteristic* m_pRemoteCharacteristic; }; -#endif // #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ */ diff --git a/src/NimBLERemoteService.cpp b/src/NimBLERemoteService.cpp index efb6662..f0cbed1 100644 --- a/src/NimBLERemoteService.cpp +++ b/src/NimBLERemoteService.cpp @@ -11,17 +11,17 @@ * Created on: Jul 8, 2017 * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #include "NimBLERemoteService.h" #include "NimBLEUtils.h" #include "NimBLEDevice.h" #include "NimBLELog.h" +#include + static const char* LOG_TAG = "NimBLERemoteService"; /** @@ -109,14 +109,31 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEU return m_characteristicVector.back(); } - // If the request was successful but 16/32 bit characteristic not found + // If the request was successful but 16/32 bit uuid not found // try again with the 128 bit uuid. if(uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) { NimBLEUUID uuid128(uuid); uuid128.to128(); - return getCharacteristic(uuid128); + if (retrieveCharacteristics(&uuid128)) { + if(m_characteristicVector.size() > prev_size) { + return m_characteristicVector.back(); + } + } + } else { + // If the request was successful but the 128 bit uuid not found + // try again with the 16 bit uuid. + NimBLEUUID uuid16(uuid); + uuid16.to16(); + // if the uuid was 128 bit but not of the BLE base type this check will fail + if (uuid16.bitSize() == BLE_UUID_TYPE_16) { + if(retrieveCharacteristics(&uuid16)) { + if(m_characteristicVector.size() > prev_size) { + return m_characteristicVector.back(); + } + } + } } } @@ -198,7 +215,8 @@ bool NimBLERemoteService::retrieveCharacteristics(const NimBLEUUID *uuid_filter) NIMBLE_LOGD(LOG_TAG, ">> retrieveCharacteristics() for service: %s", getUUID().toString().c_str()); int rc = 0; - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; if(uuid_filter == nullptr) { rc = ble_gattc_disc_all_chrs(m_pClient->getConnId(), @@ -220,6 +238,10 @@ bool NimBLERemoteService::retrieveCharacteristics(const NimBLEUUID *uuid_filter) return false; } +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); if(taskData.rc == 0){ @@ -234,7 +256,9 @@ bool NimBLERemoteService::retrieveCharacteristics(const NimBLEUUID *uuid_filter) } } - m_characteristicVector.back()->m_endHandle = getEndHandle(); + if (m_characteristicVector.size() > 0) { + m_characteristicVector.back()->m_endHandle = getEndHandle(); + } } NIMBLE_LOGD(LOG_TAG, "<< retrieveCharacteristics()"); @@ -386,6 +410,4 @@ std::string NimBLERemoteService::toString() { return res; } // toString - -#endif // #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ diff --git a/src/NimBLERemoteService.h b/src/NimBLERemoteService.h index 751c9ef..0443cfd 100644 --- a/src/NimBLERemoteService.h +++ b/src/NimBLERemoteService.h @@ -14,11 +14,9 @@ #ifndef COMPONENTS_NIMBLEREMOTESERVICE_H_ #define COMPONENTS_NIMBLEREMOTESERVICE_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #include "NimBLEClient.h" #include "NimBLEUUID.h" @@ -83,6 +81,5 @@ private: uint16_t m_endHandle; }; // NimBLERemoteService -#endif // #if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* COMPONENTS_NIMBLEREMOTESERVICE_H_ */ diff --git a/src/NimBLEScan.cpp b/src/NimBLEScan.cpp index 0370552..eb87df8 100644 --- a/src/NimBLEScan.cpp +++ b/src/NimBLEScan.cpp @@ -11,17 +11,16 @@ * Created on: Jul 1, 2017 * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) #include "NimBLEScan.h" #include "NimBLEDevice.h" #include "NimBLELog.h" #include +#include static const char* LOG_TAG = "NimBLEScan"; @@ -57,18 +56,27 @@ NimBLEScan::~NimBLEScan() { * @param [in] param Parameter data for this event. */ /*STATIC*/int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) { - - NimBLEScan* pScan = (NimBLEScan*)arg; + (void)arg; + NimBLEScan* pScan = NimBLEDevice::getScan(); switch(event->type) { + case BLE_GAP_EVENT_EXT_DISC: case BLE_GAP_EVENT_DISC: { if(pScan->m_ignoreResults) { NIMBLE_LOGI(LOG_TAG, "Scan op in progress - ignoring results"); return 0; } - - NimBLEAddress advertisedAddress(event->disc.addr); +#if CONFIG_BT_NIMBLE_EXT_ADV + const auto& disc = event->ext_disc; + const bool isLegacyAdv = disc.props & BLE_HCI_ADV_LEGACY_MASK; + const auto event_type = isLegacyAdv ? disc.legacy_event_type : disc.props; +#else + const auto& disc = event->disc; + const bool isLegacyAdv = true; + const auto event_type = disc.event_type; +#endif + NimBLEAddress advertisedAddress(disc.addr); // Examine our list of ignored addresses and stop processing if we don't want to see it or are already connected if(NimBLEDevice::isIgnored(advertisedAddress)) { @@ -80,7 +88,12 @@ NimBLEScan::~NimBLEScan() { // If we've seen this device before get a pointer to it from the vector for(auto &it: pScan->m_scanResults.m_advertisedDevicesVector) { - if(it->getAddress() == advertisedAddress) { +#if CONFIG_BT_NIMBLE_EXT_ADV + // Same address but different set ID should create a new advertised device. + if (it->getAddress() == advertisedAddress && it->getSetId() == disc.sid) { +#else + if (it->getAddress() == advertisedAddress) { +#endif advertisedDevice = it; break; } @@ -88,20 +101,27 @@ NimBLEScan::~NimBLEScan() { // If we haven't seen this device before; create a new instance and insert it in the vector. // Otherwise just update the relevant parameters of the already known device. - if(advertisedDevice == nullptr && event->disc.event_type != BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP){ + if (advertisedDevice == nullptr && + (!isLegacyAdv || event_type != BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP)) { // Check if we have reach the scan results limit, ignore this one if so. // We still need to store each device when maxResults is 0 to be able to append the scan results - if(pScan->m_maxResults > 0 && pScan->m_maxResults < 0xFF && - (pScan->m_scanResults.m_advertisedDevicesVector.size() >= pScan->m_maxResults)) - { + if (pScan->m_maxResults > 0 && pScan->m_maxResults < 0xFF && + (pScan->m_scanResults.m_advertisedDevicesVector.size() >= pScan->m_maxResults)) { return 0; } + advertisedDevice = new NimBLEAdvertisedDevice(); advertisedDevice->setAddress(advertisedAddress); - advertisedDevice->setAdvType(event->disc.event_type); + advertisedDevice->setAdvType(event_type, isLegacyAdv); +#if CONFIG_BT_NIMBLE_EXT_ADV + advertisedDevice->setSetId(disc.sid); + advertisedDevice->setPrimaryPhy(disc.prim_phy); + advertisedDevice->setSecondaryPhy(disc.sec_phy); + advertisedDevice->setPeriodicInterval(disc.periodic_adv_itvl); +#endif pScan->m_scanResults.m_advertisedDevicesVector.push_back(advertisedDevice); NIMBLE_LOGI(LOG_TAG, "New advertiser: %s", advertisedAddress.toString().c_str()); - } else if(advertisedDevice != nullptr) { + } else if (advertisedDevice != nullptr) { NIMBLE_LOGI(LOG_TAG, "Updated advertiser: %s", advertisedAddress.toString().c_str()); } else { // Scan response from unknown device @@ -109,14 +129,14 @@ NimBLEScan::~NimBLEScan() { } advertisedDevice->m_timestamp = time(nullptr); - advertisedDevice->setRSSI(event->disc.rssi); - advertisedDevice->setPayload(event->disc.data, event->disc.length_data, - event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP); + advertisedDevice->setRSSI(disc.rssi); + advertisedDevice->setPayload(disc.data, disc.length_data, (isLegacyAdv && + event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP)); if (pScan->m_pAdvertisedDeviceCallbacks) { // If not active scanning or scan response is not available - // report the result to the callback now. - if(pScan->m_scan_params.passive || + // or extended advertisement scanning, report the result to the callback now. + if(pScan->m_scan_params.passive || !isLegacyAdv || (advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_IND && advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_SCAN_IND)) { @@ -124,7 +144,7 @@ NimBLEScan::~NimBLEScan() { pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); // Otherwise, wait for the scan response so we can report the complete data. - } else if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) { + } else if (isLegacyAdv && event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) { advertisedDevice->m_callbackSent = true; pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); } @@ -284,7 +304,7 @@ bool NimBLEScan::isScanning() { * @return True if scan started or false if there was an error. */ bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResults), bool is_continue) { - NIMBLE_LOGD(LOG_TAG, ">> start(duration=%d)", duration); + NIMBLE_LOGD(LOG_TAG, ">> start: duration=%" PRIu32, duration); // Save the callback to be invoked when the scan completes. m_scanCompleteCB = scanCompleteCB; @@ -305,9 +325,28 @@ bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResul m_ignoreResults = true; } - int rc = ble_gap_disc(NimBLEDevice::m_own_addr_type, duration, &m_scan_params, - NimBLEScan::handleGapEvent, this); - +# if CONFIG_BT_NIMBLE_EXT_ADV + ble_gap_ext_disc_params scan_params; + scan_params.passive = m_scan_params.passive; + scan_params.itvl = m_scan_params.itvl; + scan_params.window = m_scan_params.window; + int rc = ble_gap_ext_disc(NimBLEDevice::m_own_addr_type, + duration/10, + 0, + m_scan_params.filter_duplicates, + m_scan_params.filter_policy, + m_scan_params.limited, + &scan_params, + &scan_params, + NimBLEScan::handleGapEvent, + NULL); +#else + int rc = ble_gap_disc(NimBLEDevice::m_own_addr_type, + duration, + &m_scan_params, + NimBLEScan::handleGapEvent, + NULL); +#endif switch(rc) { case 0: if(!is_continue) { @@ -358,10 +397,15 @@ NimBLEScanResults NimBLEScan::start(uint32_t duration, bool is_continue) { NIMBLE_LOGW(LOG_TAG, "Blocking scan called with duration = forever"); } - ble_task_data_t taskData = {nullptr, xTaskGetCurrentTaskHandle(),0, nullptr}; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {nullptr, cur_task, 0, nullptr}; m_pTaskData = &taskData; if(start(duration, nullptr, is_continue)) { +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); } @@ -534,5 +578,4 @@ NimBLEAdvertisedDevice *NimBLEScanResults::getDevice(const NimBLEAddress &addres return nullptr; } -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_OBSERVER */ diff --git a/src/NimBLEScan.h b/src/NimBLEScan.h index 49d67c8..76a1142 100644 --- a/src/NimBLEScan.h +++ b/src/NimBLEScan.h @@ -13,16 +13,18 @@ */ #ifndef COMPONENTS_NIMBLE_SCAN_H_ #define COMPONENTS_NIMBLE_SCAN_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) #include "NimBLEAdvertisedDevice.h" #include "NimBLEUtils.h" +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_gap.h" +#else +#include "nimble/nimble/host/include/host/ble_gap.h" +#endif #include @@ -97,6 +99,5 @@ private: uint8_t m_maxResults; }; -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED CONFIG_BT_NIMBLE_ROLE_OBSERVER */ #endif /* COMPONENTS_NIMBLE_SCAN_H_ */ diff --git a/src/NimBLESecurity.cpp b/src/NimBLESecurity.cpp index aa06296..df6d192 100644 --- a/src/NimBLESecurity.cpp +++ b/src/NimBLESecurity.cpp @@ -12,7 +12,7 @@ * Author: chegewara */ -#include "sdkconfig.h" +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) #include "NimBLESecurity.h" diff --git a/src/NimBLESecurity.h b/src/NimBLESecurity.h index 5a7619f..157577d 100644 --- a/src/NimBLESecurity.h +++ b/src/NimBLESecurity.h @@ -14,10 +14,16 @@ #ifndef COMPONENTS_NIMBLESECURITY_H_ #define COMPONENTS_NIMBLESECURITY_H_ -#include "sdkconfig.h" + +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_gap.h" +#else +#include "nimble/nimble/host/include/host/ble_gap.h" +#endif + /**** FIX COMPILATION ****/ #undef min #undef max diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 082c51a..f75dd62 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -12,19 +12,20 @@ * Author: kolban */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #include "NimBLEServer.h" #include "NimBLEDevice.h" #include "NimBLELog.h" +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "services/gap/ble_svc_gap.h" #include "services/gatt/ble_svc_gatt.h" - +#else +#include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h" +#include "nimble/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h" +#endif static const char* LOG_TAG = "NimBLEServer"; static NimBLEServerCallbacks defaultCallbacks; @@ -41,7 +42,9 @@ NimBLEServer::NimBLEServer() { // m_svcChgChrHdl = 0xffff; // Future Use m_pServerCallbacks = &defaultCallbacks; m_gattsStarted = false; +#if !CONFIG_BT_NIMBLE_EXT_ADV m_advertiseOnDisconnect = true; +#endif m_svcChanged = false; m_deleteCallbacks = true; } // NimBLEServer @@ -74,23 +77,19 @@ NimBLEService* NimBLEServer::createService(const char* uuid) { /** * @brief Create a %BLE Service. * @param [in] uuid The UUID of the new service. - * @param [in] numHandles The maximum number of handles associated with this service. - * @param [in] inst_id if we have multiple services with the same UUID we need - * to provide inst_id value different for each service. * @return A reference to the new service object. */ -NimBLEService* NimBLEServer::createService(const NimBLEUUID &uuid, uint32_t numHandles, uint8_t inst_id) { +NimBLEService* NimBLEServer::createService(const NimBLEUUID &uuid) { NIMBLE_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str()); - // TODO: add functionality to use inst_id for multiple services with same uuid - (void)inst_id; + // Check that a service with the supplied UUID does not already exist. if(getServiceByUUID(uuid) != nullptr) { NIMBLE_LOGW(LOG_TAG, "Warning creating a duplicate service UUID: %s", std::string(uuid).c_str()); } - NimBLEService* pService = new NimBLEService(uuid, numHandles, this); - m_svcVec.push_back(pService); // Save a reference to this service being on this server. + NimBLEService* pService = new NimBLEService(uuid); + m_svcVec.push_back(pService); serviceChanged(); NIMBLE_LOGD(LOG_TAG, "<< createService"); @@ -142,15 +141,26 @@ NimBLEService *NimBLEServer::getServiceByHandle(uint16_t handle) { return nullptr; } + +#if CONFIG_BT_NIMBLE_EXT_ADV +/** + * @brief Retrieve the advertising object that can be used to advertise the existence of the server. + * @return An advertising object. + */ +NimBLEExtAdvertising* NimBLEServer::getAdvertising() { + return NimBLEDevice::getAdvertising(); +} // getAdvertising +#endif + +#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_) /** * @brief Retrieve the advertising object that can be used to advertise the existence of the server. - * * @return An advertising object. */ NimBLEAdvertising* NimBLEServer::getAdvertising() { return NimBLEDevice::getAdvertising(); } // getAdvertising - +#endif /** * @brief Sends a service changed notification and resets the GATT server. @@ -181,7 +191,7 @@ void NimBLEServer::start() { abort(); } -#if CONFIG_LOG_DEFAULT_LEVEL > 3 || (ARDUINO_ARCH_ESP32 && CORE_DEBUG_LEVEL >= 4) +#if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 4 ble_gatts_show_local(); #endif /*** Future use *** @@ -243,6 +253,7 @@ int NimBLEServer::disconnect(uint16_t connId, uint8_t reason) { } // disconnect +#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_) /** * @brief Set the server to automatically start advertising when a client disconnects. * @param [in] aod true == advertise, false == don't advertise. @@ -250,7 +261,7 @@ int NimBLEServer::disconnect(uint16_t connId, uint8_t reason) { void NimBLEServer::advertiseOnDisconnect(bool aod) { m_advertiseOnDisconnect = aod; } // advertiseOnDisconnect - +#endif /** * @brief Return the number of connected clients. @@ -326,8 +337,9 @@ NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) { * @param [in] param * */ -/*STATIC*/int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) { - NimBLEServer* server = (NimBLEServer*)arg; +/*STATIC*/ +int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) { + NimBLEServer* server = NimBLEDevice::getServer(); NIMBLE_LOGD(LOG_TAG, ">> handleGapEvent: %s", NimBLEUtils::gapEventToString(event->type)); int rc = 0; @@ -339,7 +351,9 @@ NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) { if (event->connect.status != 0) { /* Connection failed; resume advertising */ NIMBLE_LOGE(LOG_TAG, "Connection failed"); +#if !CONFIG_BT_NIMBLE_EXT_ADV NimBLEDevice::startAdvertising(); +#endif } else { server->m_connectedPeersVec.push_back(event->connect.conn_handle); @@ -384,9 +398,11 @@ NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) { server->m_pServerCallbacks->onDisconnect(server); server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn); +#if !CONFIG_BT_NIMBLE_EXT_ADV if(server->m_advertiseOnDisconnect) { server->startAdvertising(); } +#endif return 0; } // BLE_GAP_EVENT_DISCONNECT @@ -474,11 +490,15 @@ NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) { return 0; } // BLE_GAP_EVENT_NOTIFY_TX - case BLE_GAP_EVENT_ADV_COMPLETE: { - NIMBLE_LOGD(LOG_TAG, "Advertising Complete"); - NimBLEDevice::getAdvertising()->advCompleteCB(); - return 0; - } + + case BLE_GAP_EVENT_ADV_COMPLETE: +#if CONFIG_BT_NIMBLE_EXT_ADV + case BLE_GAP_EVENT_SCAN_REQ_RCVD: + return NimBLEExtAdvertising::handleGapEvent(event, arg); +#else + return NimBLEAdvertising::handleGapEvent(event, arg); +#endif + // BLE_GAP_EVENT_ADV_COMPLETE | BLE_GAP_EVENT_SCAN_REQ_RCVD case BLE_GAP_EVENT_CONN_UPDATE: { NIMBLE_LOGD(LOG_TAG, "Connection parameters updated."); @@ -537,7 +557,7 @@ NimBLEConnInfo NimBLEServer::getPeerIDInfo(uint16_t id) { NIMBLE_LOGD(LOG_TAG, "BLE_SM_IOACT_DISP; ble_sm_inject_io result: %d", rc); } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { - NIMBLE_LOGD(LOG_TAG, "Passkey on device's display: %d", event->passkey.params.numcmp); + NIMBLE_LOGD(LOG_TAG, "Passkey on device's display: %" PRIu32, event->passkey.params.numcmp); pkey.action = event->passkey.params.action; // Compatibility only - Do not use, should be removed the in future if(NimBLEDevice::m_securityCallbacks != nullptr) { @@ -655,7 +675,9 @@ void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) { service->m_removed = deleteSvc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE; serviceChanged(); +#if !CONFIG_BT_NIMBLE_EXT_ADV NimBLEDevice::getAdvertising()->removeServiceUUID(service->getUUID()); +#endif } @@ -718,23 +740,53 @@ void NimBLEServer::resetGATT() { } +#if CONFIG_BT_NIMBLE_EXT_ADV /** * @brief Start advertising. - * - * Start the server advertising its existence. This is a convenience function and is equivalent to + * @param [in] inst_id The extended advertisement instance ID to start. + * @param [in] duration How long to advertise for in milliseconds, 0 = forever (default). + * @param [in] max_events Maximum number of advertisement events to send, 0 = no limit (default). + * @return True if advertising started successfully. + * @details Start the server advertising its existence. This is a convenience function and is equivalent to * retrieving the advertising object and invoking start upon it. */ -void NimBLEServer::startAdvertising() { - NimBLEDevice::startAdvertising(); +bool NimBLEServer::startAdvertising(uint8_t inst_id, + int duration, + int max_events) { + return getAdvertising()->start(inst_id, duration, max_events); } // startAdvertising +/** + * @brief Convenience function to stop advertising a data set. + * @param [in] inst_id The extended advertisement instance ID to stop advertising. + * @return True if advertising stopped successfully. + */ +bool NimBLEServer::stopAdvertising(uint8_t inst_id) { + return getAdvertising()->stop(inst_id); +} // stopAdvertising +#endif + +#if !CONFIG_BT_NIMBLE_EXT_ADV|| defined(_DOXYGEN_) +/** + * @brief Start advertising. + * @return True if advertising started successfully. + * @details Start the server advertising its existence. This is a convenience function and is equivalent to + * retrieving the advertising object and invoking start upon it. + */ +bool NimBLEServer::startAdvertising() { + return getAdvertising()->start(); +} // startAdvertising +#endif + + /** * @brief Stop advertising. + * @return True if advertising stopped successfully. */ -void NimBLEServer::stopAdvertising() { - NimBLEDevice::stopAdvertising(); -} // startAdvertising +bool NimBLEServer::stopAdvertising() { + return getAdvertising()->stop(); +} // stopAdvertising /** @@ -772,7 +824,31 @@ void NimBLEServer::updateConnParams(uint16_t conn_handle, if(rc != 0) { NIMBLE_LOGE(LOG_TAG, "Update params error: %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); } -}// updateConnParams +} // updateConnParams + + +/** + * @brief Request an update of the data packet length. + * * Can only be used after a connection has been established. + * @details Sends a data length update request to the peer. + * The Data Length Extension (DLE) allows to increase the Data Channel Payload from 27 bytes to up to 251 bytes. + * The peer needs to support the Bluetooth 4.2 specifications, to be capable of DLE. + * @param [in] conn_handle The connection handle of the peer to send the request to. + * @param [in] tx_octets The preferred number of payload octets to use (Range 0x001B-0x00FB). + */ +void NimBLEServer::setDataLen(uint16_t conn_handle, uint16_t tx_octets) { +#if defined(CONFIG_NIMBLE_CPP_IDF) && !defined(ESP_IDF_VERSION) || \ + (ESP_IDF_VERSION_MAJOR * 100 + ESP_IDF_VERSION_MINOR * 10 + ESP_IDF_VERSION_PATCH) < 432 + return; +#else + uint16_t tx_time = (tx_octets + 14) * 8; + + int rc = ble_gap_set_data_len(conn_handle, tx_octets, tx_time); + if(rc != 0) { + NIMBLE_LOGE(LOG_TAG, "Set data length error: %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); + } +#endif +} // setDataLen bool NimBLEServer::setIndicateWait(uint16_t conn_handle) { @@ -842,6 +918,4 @@ bool NimBLEServerCallbacks::onConfirmPIN(uint32_t pin){ return true; } - -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif // CONFIG_BT_ENABLED +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ diff --git a/src/NimBLEServer.h b/src/NimBLEServer.h index ebcf39f..5b946f0 100644 --- a/src/NimBLEServer.h +++ b/src/NimBLEServer.h @@ -14,18 +14,22 @@ #ifndef MAIN_NIMBLESERVER_H_ #define MAIN_NIMBLESERVER_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #define NIMBLE_ATT_REMOVE_HIDE 1 #define NIMBLE_ATT_REMOVE_DELETE 2 +#define onMtuChanged onMTUChange + #include "NimBLEUtils.h" #include "NimBLEAddress.h" +#if CONFIG_BT_NIMBLE_EXT_ADV +#include "NimBLEExtAdvertising.h" +#else #include "NimBLEAdvertising.h" +#endif #include "NimBLEService.h" #include "NimBLESecurity.h" #include "NimBLEConnInfo.h" @@ -43,15 +47,22 @@ class NimBLEServer { public: size_t getConnectedCount(); NimBLEService* createService(const char* uuid); - NimBLEService* createService(const NimBLEUUID &uuid, uint32_t numHandles=15, - uint8_t inst_id=0); + NimBLEService* createService(const NimBLEUUID &uuid); void removeService(NimBLEService* service, bool deleteSvc = false); void addService(NimBLEService* service); - NimBLEAdvertising* getAdvertising(); void setCallbacks(NimBLEServerCallbacks* pCallbacks, bool deleteCallbacks = true); - void startAdvertising(); - void stopAdvertising(); +#if CONFIG_BT_NIMBLE_EXT_ADV + NimBLEExtAdvertising* getAdvertising(); + bool startAdvertising(uint8_t inst_id, + int duration = 0, + int max_events = 0); + bool stopAdvertising(uint8_t inst_id); +#else + NimBLEAdvertising* getAdvertising(); + bool startAdvertising(); +#endif + bool stopAdvertising(); void start(); NimBLEService* getServiceByUUID(const char* uuid, uint16_t instanceId = 0); NimBLEService* getServiceByUUID(const NimBLEUUID &uuid, uint16_t instanceId = 0); @@ -61,12 +72,15 @@ public: void updateConnParams(uint16_t conn_handle, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout); + void setDataLen(uint16_t conn_handle, uint16_t tx_octets); uint16_t getPeerMTU(uint16_t conn_id); std::vector getPeerDevices(); NimBLEConnInfo getPeerInfo(size_t index); NimBLEConnInfo getPeerInfo(const NimBLEAddress& address); NimBLEConnInfo getPeerIDInfo(uint16_t id); +#if !CONFIG_BT_NIMBLE_EXT_ADV || defined(_DOXYGEN_) void advertiseOnDisconnect(bool); +#endif private: NimBLEServer(); @@ -75,9 +89,15 @@ private: friend class NimBLEService; friend class NimBLEDevice; friend class NimBLEAdvertising; +#if CONFIG_BT_NIMBLE_EXT_ADV + friend class NimBLEExtAdvertising; + friend class NimBLEExtAdvertisementData; +#endif bool m_gattsStarted; +#if !CONFIG_BT_NIMBLE_EXT_ADV bool m_advertiseOnDisconnect; +#endif bool m_svcChanged; NimBLEServerCallbacks* m_pServerCallbacks; bool m_deleteCallbacks; @@ -168,7 +188,5 @@ public: virtual bool onConfirmPIN(uint32_t pin); }; // NimBLEServerCallbacks - -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /* MAIN_NIMBLESERVER_H_ */ diff --git a/src/NimBLEService.cpp b/src/NimBLEService.cpp index 9c43e90..b124fcb 100644 --- a/src/NimBLEService.cpp +++ b/src/NimBLEService.cpp @@ -14,12 +14,10 @@ // A service is identified by a UUID. A service is also the container for one or more characteristics. -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#include "NimBLEDevice.h" #include "NimBLEService.h" #include "NimBLEUtils.h" #include "NimBLELog.h" @@ -34,25 +32,19 @@ static const char* LOG_TAG = "NimBLEService"; // Tag for logging. /** * @brief Construct an instance of the NimBLEService * @param [in] uuid The UUID of the service. - * @param [in] numHandles The maximum number of handles associated with the service. - * @param [in] pServer A pointer to the server instance that this service belongs to. */ -NimBLEService::NimBLEService(const char* uuid, uint16_t numHandles, NimBLEServer* pServer) -: NimBLEService(NimBLEUUID(uuid), numHandles, pServer) { +NimBLEService::NimBLEService(const char* uuid) +: NimBLEService(NimBLEUUID(uuid)) { } /** * @brief Construct an instance of the BLEService * @param [in] uuid The UUID of the service. - * @param [in] numHandles The maximum number of handles associated with the service. - * @param [in] pServer A pointer to the server instance that this service belongs to. */ -NimBLEService::NimBLEService(const NimBLEUUID &uuid, uint16_t numHandles, NimBLEServer* pServer) { +NimBLEService::NimBLEService(const NimBLEUUID &uuid) { m_uuid = uuid; m_handle = NULL_HANDLE; - m_pServer = pServer; - m_numHandles = numHandles; m_pSvcDef = nullptr; m_removed = 0; @@ -121,6 +113,12 @@ bool NimBLEService::start() { // Rebuild the service definition if the server attributes have changed. if(getServer()->m_svcChanged && m_pSvcDef != nullptr) { + if(m_pSvcDef[0].characteristics) { + if(m_pSvcDef[0].characteristics[0].descriptors) { + delete(m_pSvcDef[0].characteristics[0].descriptors); + } + delete(m_pSvcDef[0].characteristics); + } delete(m_pSvcDef); m_pSvcDef = nullptr; } @@ -258,10 +256,11 @@ uint16_t NimBLEService::getHandle() { * @brief Create a new BLE Characteristic associated with this service. * @param [in] uuid - The UUID of the characteristic. * @param [in] properties - The properties of the characteristic. + * @param [in] max_len - The maximum length in bytes that the characteristic value can hold. * @return The new BLE characteristic. */ -NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint32_t properties) { - return createCharacteristic(NimBLEUUID(uuid), properties); +NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint32_t properties, uint16_t max_len) { + return createCharacteristic(NimBLEUUID(uuid), properties, max_len); } @@ -269,10 +268,11 @@ NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint * @brief Create a new BLE Characteristic associated with this service. * @param [in] uuid - The UUID of the characteristic. * @param [in] properties - The properties of the characteristic. + * @param [in] max_len - The maximum length in bytes that the characteristic value can hold. * @return The new BLE characteristic. */ -NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID &uuid, uint32_t properties) { - NimBLECharacteristic* pCharacteristic = new NimBLECharacteristic(uuid, properties, this); +NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID &uuid, uint32_t properties, uint16_t max_len) { + NimBLECharacteristic* pCharacteristic = new NimBLECharacteristic(uuid, properties, max_len, this); if (getCharacteristic(uuid) != nullptr) { NIMBLE_LOGD(LOG_TAG, "<< Adding a duplicate characteristic with UUID: %s", @@ -429,8 +429,7 @@ std::string NimBLEService::toString() { * @return The BLEServer associated with this service. */ NimBLEServer* NimBLEService::getServer() { - return m_pServer; + return NimBLEDevice::getServer(); }// getServer -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif // CONFIG_BT_ENABLED +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ diff --git a/src/NimBLEService.h b/src/NimBLEService.h index ebf913d..21ec1af 100644 --- a/src/NimBLEService.h +++ b/src/NimBLEService.h @@ -14,11 +14,9 @@ #ifndef MAIN_NIMBLESERVICE_H_ #define MAIN_NIMBLESERVICE_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" -#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) +#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #include "NimBLEServer.h" #include "NimBLECharacteristic.h" @@ -36,8 +34,8 @@ class NimBLECharacteristic; class NimBLEService { public: - NimBLEService(const char* uuid, uint16_t numHandles, NimBLEServer* pServer); - NimBLEService(const NimBLEUUID &uuid, uint16_t numHandles, NimBLEServer* pServer); + NimBLEService(const char* uuid); + NimBLEService(const NimBLEUUID &uuid); ~NimBLEService(); NimBLEServer* getServer(); @@ -52,12 +50,14 @@ public: NimBLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties = NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE); + NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); NimBLECharacteristic* createCharacteristic(const NimBLEUUID &uuid, uint32_t properties = NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE); + NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); void addCharacteristic(NimBLECharacteristic* pCharacteristic); void removeCharacteristic(NimBLECharacteristic* pCharacteristic, bool deleteChr = false); @@ -76,16 +76,12 @@ private: friend class NimBLEDevice; uint16_t m_handle; - NimBLEServer* m_pServer; NimBLEUUID m_uuid; - uint16_t m_numHandles; ble_gatt_svc_def* m_pSvcDef; uint8_t m_removed; std::vector m_chrVec; }; // NimBLEService - -#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#endif // CONFIG_BT_ENABLED +#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /* MAIN_NIMBLESERVICE_H_ */ diff --git a/src/NimBLEUUID.cpp b/src/NimBLEUUID.cpp index 9338d7d..255f771 100644 --- a/src/NimBLEUUID.cpp +++ b/src/NimBLEUUID.cpp @@ -11,7 +11,8 @@ * Created on: Jun 21, 2017 * Author: kolban */ -#include "sdkconfig.h" + +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) #include "NimBLEUtils.h" @@ -234,8 +235,8 @@ const ble_uuid_any_t* NimBLEUUID::getNative() const { /** * @brief Convert a UUID to its 128 bit representation. - * @details A UUID can be internally represented as 16bit, 32bit or the full 128bit. This method - * will convert 16 or 32 bit representations to the full 128bit. + * @details A UUID can be internally represented as 16bit, 32bit or the full 128bit. + * This method will convert 16 or 32bit representations to the full 128bit. * @return The NimBLEUUID converted to 128bit. */ const NimBLEUUID &NimBLEUUID::to128() { @@ -256,6 +257,29 @@ const NimBLEUUID &NimBLEUUID::to128() { } // to128 +/** + * @brief Convert 128 bit UUID to its 16 bit representation. + * @details A UUID can be internally represented as 16bit, 32bit or the full 128bit. + * This method will convert a 128bit uuid to 16bit if it contains the ble base uuid. + * @return The NimBLEUUID converted to 16bit if successful, otherwise the original uuid. + */ +const NimBLEUUID& NimBLEUUID::to16() { + if (!m_valueSet || m_uuid.u.type == BLE_UUID_TYPE_16) { + return *this; + } + + if (m_uuid.u.type == BLE_UUID_TYPE_128) { + uint8_t base128[] = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, + 0x00, 0x80, 0x00, 0x10, 0x00, 0x00}; + if (memcmp(m_uuid.u128.value, base128, sizeof(base128)) == 0 ) { + *this = NimBLEUUID(*(uint16_t*)(m_uuid.u128.value + 12)); + } + } + + return *this; +} + + /** * @brief Get a string representation of the UUID. * @details diff --git a/src/NimBLEUUID.h b/src/NimBLEUUID.h index 982f9c3..2c24971 100644 --- a/src/NimBLEUUID.h +++ b/src/NimBLEUUID.h @@ -14,10 +14,16 @@ #ifndef COMPONENTS_NIMBLEUUID_H_ #define COMPONENTS_NIMBLEUUID_H_ -#include "sdkconfig.h" + +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_uuid.h" +#else +#include "nimble/nimble/host/include/host/ble_uuid.h" +#endif + /**** FIX COMPILATION ****/ #undef min #undef max @@ -42,6 +48,7 @@ public: bool equals(const NimBLEUUID &uuid) const; const ble_uuid_any_t* getNative() const; const NimBLEUUID & to128(); + const NimBLEUUID& to16(); std::string toString() const; static NimBLEUUID fromString(const std::string &uuid); diff --git a/src/NimBLEUtils.cpp b/src/NimBLEUtils.cpp index e528914..cbc5cff 100644 --- a/src/NimBLEUtils.cpp +++ b/src/NimBLEUtils.cpp @@ -6,12 +6,13 @@ * */ -#include "sdkconfig.h" +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) #include "NimBLEUtils.h" #include "NimBLELog.h" -#include "nimconfig.h" + +#include static const char* LOG_TAG = "NimBLEUtils"; @@ -358,6 +359,7 @@ const char* NimBLEUtils::returnCodeToString(int rc) { return "Unknown"; } #else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT) + (void)rc; return ""; #endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT) } @@ -385,6 +387,7 @@ const char* NimBLEUtils::advTypeToString(uint8_t advType) { return "Unknown flag"; } #else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT) + (void)advType; return ""; #endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT) } // adFlagsToString @@ -432,8 +435,11 @@ char* NimBLEUtils::buildHexData(uint8_t* target, const uint8_t* source, uint8_t * @param [in] arg Unused. */ void NimBLEUtils::dumpGapEvent(ble_gap_event *event, void *arg){ + (void)arg; #if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT) NIMBLE_LOGD(LOG_TAG, "Received a GAP event: %s", gapEventToString(event->type)); +#else + (void)event; #endif } @@ -520,6 +526,7 @@ const char* NimBLEUtils::gapEventToString(uint8_t eventType) { return "Unknown event type"; } #else // #if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT) + (void)eventType; return ""; #endif // #if defined(CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT) } // gapEventToString diff --git a/src/NimBLEUtils.h b/src/NimBLEUtils.h index ac90bc7..1a2fe99 100644 --- a/src/NimBLEUtils.h +++ b/src/NimBLEUtils.h @@ -8,10 +8,15 @@ #ifndef COMPONENTS_NIMBLEUTILS_H_ #define COMPONENTS_NIMBLEUTILS_H_ -#include "sdkconfig.h" + +#include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) +#if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_gap.h" +#else +#include "nimble/nimble/host/include/host/ble_gap.h" +#endif /**** FIX COMPILATION ****/ #undef min @@ -24,7 +29,7 @@ typedef struct { void *pATT; TaskHandle_t task; int rc; - std::string *buf; + void *buf; } ble_task_data_t; diff --git a/src/nimconfig.h b/src/nimconfig.h index 01fd124..24155ea 100644 --- a/src/nimconfig.h +++ b/src/nimconfig.h @@ -10,21 +10,68 @@ #include "sdkconfig.h" #include "nimconfig_rename.h" +#if defined(CONFIG_BT_ENABLED) + +// Allows cpp wrapper to select the correct include paths when using esp-idf +#define CONFIG_NIMBLE_CPP_IDF + +/* Cannot use client without scan */ +#if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) && !defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) +#define CONFIG_BT_NIMBLE_ROLE_OBSERVER +#endif + +/* Cannot use server without advertise */ +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) && !defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) +#define CONFIG_BT_NIMBLE_ROLE_BROADCASTER +#endif + +/* Enables the use of Arduino String class for attribute values */ +#if defined __has_include +# if __has_include () +# define NIMBLE_CPP_ARDUINO_STRING_AVAILABLE +# endif +#endif + +#endif /* CONFIG_BT_ENABLED */ + #ifdef _DOXYGEN_ /** @brief Un-comment to change the number of simultaneous connections (esp controller max is 9) */ #define CONFIG_BT_NIMBLE_MAX_CONNECTIONS 3 +/** @brief Un-comment to enable storing the timestamp when an attribute value is updated\n + * This allows for checking the last update time using getTimeStamp() or getValue(time_t*)\n + * If disabled, the timestamp returned from these functions will be 0.\n + * Disabling timestamps will reduce the memory used for each value.\n + * 1 = Enabled, 0 = Disabled; Default = Disabled + */ +#define CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED 0 + +/** @brief Uncomment to set the default allocation size (bytes) for each attribute if\n + * not specified when the constructor is called. This is also the size used when a remote\n + * characteristic or descriptor is constructed before a value is read/notifed.\n + * Increasing this will reduce reallocations but increase memory footprint.\n + * Default value is 20. Range: 1 : 512 (BLE_ATT_ATTR_MAX_LEN) + */ +#define CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH 20 + /** @brief Un-comment to change the default MTU size */ #define CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU 255 /** @brief Un-comment to change default device name */ #define CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME "nimble" -/** @brief Un-comment to see debug log messages from the NimBLE host +/** @brief Un-comment to set the debug log messages level from the NimBLE host stack.\n + * Values: 0 = DEBUG, 1 = INFO, 2 = WARNING, 3 = ERROR, 4 = CRITICAL, 5+ = NONE\n * Uses approx. 32kB of flash memory. */ -#define CONFIG_BT_NIMBLE_DEBUG +#define CONFIG_BT_NIMBLE_LOG_LEVEL 5 + +/** @brief Un-comment to set the debug log messages level from the NimBLE CPP Wrapper.\n + * Values: 0 = NONE, 1 = ERROR, 2 = WARNING, 3 = INFO, 4+ = DEBUG\n + * Uses approx. 32kB of flash memory. + */ +#define CONFIG_NIMBLE_CPP_LOG_LEVEL 0 /** @brief Un-comment to see NimBLE host return codes as text debug log messages. * Uses approx. 7kB of flash memory.