diff --git a/.gitmodules b/.gitmodules index 808bb35..b80c8c0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,12 @@ [submodule "modules/cxxopts"] path = modules/cxxopts url = https://github.com/jarro2783/cxxopts.git [submodule "modules/json"] path = modules/json url = https://github.com/nlohmann/json.git +[submodule "modules/paho.mqtt.cpp"] + path = modules/paho.mqtt.cpp + url = https://github.com/eclipse/paho.mqtt.cpp.git +[submodule "modules/paho.mqtt.c"] + path = modules/paho.mqtt.c + url = https://github.com/eclipse/paho.mqtt.c.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e530b1..7959e10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,221 +1,229 @@ -cmake_minimum_required(VERSION 3.10.0) +cmake_minimum_required(VERSION 3.15) # Start project, set version here. project("RoSA") # NOTE: Adjust the variables version and release in docs/conf.py according to # version changes here. set(ROSA_VERSION_MAJOR 0) set(ROSA_VERSION_MINOR 1) set(ROSA_VERSION_PATCH 0) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Add path for custom modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ) # Package information if( NOT PACKAGE_VERSION ) set(PACKAGE_VERSION "${ROSA_VERSION_MAJOR}.${ROSA_VERSION_MINOR}.${ROSA_VERSION_PATCH}") endif() set(PACKAGE_NAME "Research on Self-Awareness Framework") set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") set(PACKAGE_BUGREPORT "david.juhasz@tuwien.ac.at") #TODO: cpack? # Sanity check we are to make out-of-tree build if( CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR ) message(FATAL_ERROR "In-source builds are not allowed.") endif() string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) # Set various paths set(ROSA_MAIN_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(ROSA_MAIN_INCLUDE_DIR ${ROSA_MAIN_SRC_DIR}/include) set(ROSA_MAIN_BIN_DIR ${ROSA_MAIN_SRC_DIR}/bin) set(ROSA_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(ROSA_RUNTIME_OUTPUT_INTDIR ${ROSA_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin) set(ROSA_LIBRARY_OUTPUT_INTDIR ${ROSA_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib) set(ROSA_EXEC_BINARY_DIR ${ROSA_BINARY_DIR}/bin) set(ROSA_LIBRARY_DIR ${ROSA_BINARY_DIR}/lib) set(ROSA_INCLUDE_DIR ${ROSA_BINARY_DIR}/include) set(ROSA_MAIN_MODULE_DIR ${ROSA_MAIN_SRC_DIR}/modules) # Add some generic helpers. include(AddCMakeTools) # Set build options option(ROSA_INCLUDE_TOOLS "Generate build targets for RoSA tools." ON) option(ROSA_INCLUDE_EXAMPLES "Generate build targets for RoSA examples." ON) option(ROSA_ENABLE_PEDANTIC "Compile with pedantic enabled." ON) # Assertions are always enabled for Debug builds, this option is respected only # for non-Debug builds. option(ROSA_ENABLE_ASSERTIONS "Enable assertions for non-Debug builds." OFF) option(ROSA_ENABLE_CLANG_TIDY "Run clang-tidy checks when building RoSA." OFF) option(ROSA_INCLUDE_CLANG_FORMAT "Generate build target for formatting RoSA sources with clang-format." OFF) option(ROSA_INCLUDE_DOCS "Generate build targets for RoSA documentation." ON) option(ROSA_BUILD_DOCS "Build RoSA documentation." OFF) option(ROSA_ENABLE_DOXYGEN "Use doxygen to generate RoSA API documentation." OFF) option(ROSA_ENABLE_SPHINX "Use Sphinx to generate RoSA documentation." OFF) set(ROSA_LOG_LEVEL "" CACHE STRING "Level of logging to be used.") set(ROSA_INCLUDE_APPS "" CACHE STRING "Generate build targets for the defined RoSA applications.") # All options referred to from HandleROSAOptions have to be specified # BEFORE this include, otherwise options will not be correctly set on # first cmake run include(config-ix) include(HandleROSAOptions) # Configure the ROSA configuration header file. if( NOT ROSA_LOG_LEVEL STREQUAL "") if( ${ROSA_LOG_LEVEL} EQUAL 5 ) set(ROSA_LOG_LEVEL_INT -1) else() set(ROSA_LOG_LEVEL_INT ${ROSA_LOG_LEVEL}) endif() else() set(ROSA_LOG_LEVEL_INT -1) endif() configure_file( ${ROSA_MAIN_INCLUDE_DIR}/rosa/config/rosa_config.h.cmake ${ROSA_INCLUDE_DIR}/rosa/config/rosa_config.h ) ################## # Handling modules -# Set configuration of modules +# Submodules added to this list will be excluded from the project. +# Use it if a submodule is not used under some conditions or built separately. +set(ROSA_EXCLUDE_SUBMODULES "") set(CXXOPTS_BUILD_TESTS OFF) set(JSON_BuildTests OFF) set(JSON_Install OFF) set(JSON_MultipleHeaders OFF) # Fetch and configure modules add_subdirectory(modules) # "Install" headers for RoSA build file(COPY ${ROSA_MAIN_MODULE_DIR}/cxxopts/include/cxxopts.hpp DESTINATION ${ROSA_INCLUDE_DIR}/cxxopts ) file(COPY ${ROSA_MAIN_MODULE_DIR}/json/single_include/nlohmann DESTINATION ${ROSA_INCLUDE_DIR} ) -################## +SET(PAHO_BUILD_STATIC TRUE) +SET(PAHO_BUILD_WITH_SSL FALSE) +SET(PAHO_ENABLE_TESTING FALSE) +include(cmake/pahomqttc.cmake) +include(cmake/pahomqttcpp.cmake) +# These libraries will be built as external projects, so exclude them from this project. +list(APPEND ROSA_EXCLUDE_SUBMODULES paho.mqtt.c paho.mqtt.cpp) # They are not referenced. See set_output_directory(). set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${ROSA_EXEC_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${ROSA_LIBRARY_DIR}) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${ROSA_LIBRARY_DIR}) # Set include directories set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories(${ROSA_INCLUDE_DIR} ${ROSA_MAIN_INCLUDE_DIR}) # Set up YCM set(CMAKE_EXPORT_COMPILE_COMMANDS 1) message("Set compilation_database_folder in .ymc_extra_conf.py in the source directory if you want to use YMC.") # Add parts of the project include(AddROSATools) add_subdirectory(lib) add_subdirectory(apps) if( ROSA_INCLUDE_TOOLS ) add_subdirectory(tools) endif() if( ROSA_INCLUDE_EXAMPLES ) add_subdirectory(examples) endif() if( ROSA_INCLUDE_DOCS ) add_subdirectory(docs) endif() #TODO: install? # Print summary set(ROSA_ENABLE_ASSERTIONS_STR "OFF") if( ROSA_ENABLE_ASSERTIONS_INT EQUAL 1 ) set(ROSA_ENABLE_ASSERTIONS_STR "ON") endif() set(LOG_LEVEL_STR "disabled") if( NOT ROSA_LOG_LEVEL STREQUAL "" ) if( ${ROSA_LOG_LEVEL} EQUAL 0 ) set(LOG_LEVEL_STR "ERROR") elseif( ${ROSA_LOG_LEVEL} EQUAL 1 ) set(LOG_LEVEL_STR "WARNING") elseif( ${ROSA_LOG_LEVEL} EQUAL 2 ) set(LOG_LEVEL_STR "INFO") elseif( ${ROSA_LOG_LEVEL} EQUAL 3 ) set(LOG_LEVEL_STR "DEBUG") elseif( ${ROSA_LOG_LEVEL} EQUAL 4 ) set(LOG_LEVEL_STR "TRACE") elseif( ${ROSA_LOG_LEVEL} EQUAL 5 ) set(LOG_LEVEL_STR "disabled") else() set(LOG_LEVEL_STR "invalid") endif() endif() message(STATUS "\n====================| Build Summary |====================" "\n" "\nRoSA version: ${PACKAGE_VERSION}" "\n" "\nBuild type: ${CMAKE_BUILD_TYPE}" "\nAssertions: ${ROSA_ENABLE_ASSERTIONS_STR}" "\nClang-tidy: ${ROSA_ENABLE_CLANG_TIDY}" "\nClang-format: ${ROSA_INCLUDE_CLANG_FORMAT}" "\nLog level: ${LOG_LEVEL_STR}" "\n" "\nBuild apps: ${ROSA_INCLUDE_APPS}" "\nBuild tools: ${ROSA_INCLUDE_TOOLS}" "\nBuild examples: ${ROSA_INCLUDE_EXAMPLES}" "\nInclude docs: ${ROSA_INCLUDE_DOCS}" "\nBuild docs: ${ROSA_BUILD_DOCS}" "\n -enable doxygen: ${ROSA_ENABLE_DOXYGEN}" "\n -enable Sphinx: ${ROSA_ENABLE_SPHINX}" "\n" "\nCC: ${CMAKE_C_COMPILER}" "\nCFLAGS: ${CMAKE_C_FLAGS}" "\nCXX: ${CMAKE_CXX_COMPILER}" "\nCXXFLAGS: ${CMAKE_CXX_FLAGS}" "\nLIBRARIES: ${LD_FLAGS}" "\n" "\nSource directory: ${ROSA_MAIN_SRC_DIR}" "\nBuild directory: ${ROSA_BINARY_DIR}" "\nExecutable path: ${ROSA_EXEC_BINARY_DIR}" "\nLibrary path: ${ROSA_LIBRARY_DIR}" "\nGenerator: ${CMAKE_GENERATOR}" "\n" "\n===========================================================\n") diff --git a/cmake/config-ix.cmake b/cmake/config-ix.cmake index fe592e2..bfc96c0 100755 --- a/cmake/config-ix.cmake +++ b/cmake/config-ix.cmake @@ -1,106 +1,102 @@ # available programs checks function(rosa_find_program name) string(TOUPPER ${name} NAME) string(REGEX REPLACE "\\." "_" NAME ${NAME}) find_program(ROSA_PATH_${NAME} NAMES ${ARGV}) mark_as_advanced(ROSA_PATH_${NAME}) if(ROSA_PATH_${NAME}) set(HAVE_${NAME} 1 CACHE INTERNAL "Is ${name} available ?") mark_as_advanced(HAVE_${NAME}) else(ROSA_PATH_${NAME}) set(HAVE_${NAME} "" CACHE INTERNAL "Is ${name} available ?") endif(ROSA_PATH_${NAME}) endfunction() if (ROSA_ENABLE_CLANG_TIDY) message(STATUS "Clang-tidy enabled.") - # Make sure CMake knows about clang-tidy - cmake_minimum_required(VERSION 3.6.0) - # clang-tidy specific options set(ROSA_CLANG_TIDY_PATH "" CACHE STRING "Custom path for clang-tidy executable.") option(ROSA_CLANG_TIDY_FIX "Apply suggested clang-tidy fixes to the sources." OFF) find_package(ClangTidy REQUIRED) else() message(STATUS "Clang-tidy disabled.") endif() if (ROSA_INCLUDE_CLANG_FORMAT) message(STATUS "Clang-format enabled.") # Disable clang-format for Windows hosts because of unix-style path # separators used below. if ("${CMAKE_HOST_SYSTEM}" MATCHES ".*Windows.*") set(ROSA_INCLUDE_CLANG_FORMAT OFF) message(WARNING "Disabling clang-format on a Windows host.") else() #clang-format specific options set(ROSA_CLANG_FORMAT_PATH "" CACHE STRING "Custom path for clang-format executable.") # Find clang-format find_package(ClangFormat REQUIRED) file(GLOB_RECURSE ALL_SOURCE_FILES *.[ch]pp *.c[c] *.h[h]) add_custom_target(format-rosa) foreach (SOURCE_FILE ${ALL_SOURCE_FILES}) string(REGEX REPLACE "^${ROSA_MAIN_SRC_DIR}/" "" SOURCE_RELPATH ${SOURCE_FILE}) string(REPLACE "/" "_" SOURCE_TARGET ${SOURCE_RELPATH}) add_custom_target( format-${SOURCE_TARGET} COMMAND ${CLANGFORMAT_EXECUTABLE} -i -style=file ${SOURCE_FILE} ) add_dependencies(format-rosa format-${SOURCE_TARGET}) endforeach() endif() else() message(STATUS "Clang-format disabled.") endif() if (ROSA_ENABLE_DOXYGEN) rosa_find_program(dot) endif () if (ROSA_ENABLE_DOXYGEN) message(STATUS "Doxygen enabled.") find_package(Doxygen REQUIRED) if (DOXYGEN_FOUND) # If we find doxygen and we want to enable doxygen by default create a # global aggregate doxygen target for generating llvm and any/all # subprojects doxygen documentation. if (ROSA_BUILD_DOCS) add_custom_target(doxygen ALL) endif() option(ROSA_DOXYGEN_EXTERNAL_SEARCH "Enable doxygen external search." OFF) if (ROSA_DOXYGEN_EXTERNAL_SEARCH) set(ROSA_DOXYGEN_SEARCHENGINE_URL "" CACHE STRING "URL to use for external search.") set(ROSA_DOXYGEN_SEARCH_MAPPINGS "" CACHE STRING "Doxygen Search Mappings.") endif() endif() else() message(STATUS "Doxygen disabled.") endif() if (ROSA_ENABLE_SPHINX) message(STATUS "Sphinx enabled.") find_package(Sphinx REQUIRED) if (ROSA_BUILD_DOCS) add_custom_target(sphinx ALL) endif() else() message(STATUS "Sphinx disabled.") endif() - diff --git a/cmake/fix_pahomqttcpp.cmake.in b/cmake/fix_pahomqttcpp.cmake.in new file mode 100644 index 0000000..b3aaa02 --- /dev/null +++ b/cmake/fix_pahomqttcpp.cmake.in @@ -0,0 +1,2 @@ +set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} @PAHOMQTTC_INCLUDE_DIR@) +set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} @PAHOMQTTC_LIBRARY_DIR@) diff --git a/cmake/modules/AddCMakeTools.cmake b/cmake/modules/AddCMakeTools.cmake index 0db8fde..7855fe6 100644 --- a/cmake/modules/AddCMakeTools.cmake +++ b/cmake/modules/AddCMakeTools.cmake @@ -1,19 +1,23 @@ # Stores a list of subdirectories of directory ${curdir} into result. macro(SUBDIRLIST result curdir) file(GLOB children RELATIVE ${curdir} ${curdir}/*) set(dirlist "") foreach( child ${children} ) if( IS_DIRECTORY ${curdir}/${child} ) list(APPEND dirlist ${child}) endif() endforeach() set(${result} ${dirlist}) endmacro() # Adds all subdirs of the current list directory. +# Values that are passed as optional arguments will be excluded. macro(ADDALLSUBDIRS) SUBDIRLIST(subdirs ${CMAKE_CURRENT_SOURCE_DIR}) - foreach( subdir ${subdirs}) - add_subdirectory(${subdir}) - endforeach() + if( subdirs ) + list(REMOVE_ITEM subdirs "" ${ARGN}) + foreach( subdir ${subdirs}) + add_subdirectory(${subdir}) + endforeach() + endif() endmacro() diff --git a/cmake/pahomqttc.cmake b/cmake/pahomqttc.cmake new file mode 100644 index 0000000..a5a88b9 --- /dev/null +++ b/cmake/pahomqttc.cmake @@ -0,0 +1,83 @@ +# +# Builds the paho.mqtt.c library +# +# This step is necessary to provide the libraries and +# headers for paho.mqtt.cpp project +# +include(ExternalProject) +set(PAHOMQTTC_DIR ${CMAKE_SOURCE_DIR}/modules/paho.mqtt.c) +set(PAHOMQTTC_TARGET_DIR ${CMAKE_BINARY_DIR}/modules/paho.mqtt.c) + +if( WIN32 ) + set(PAHOMQTTC_CLIENT_STATIC_LIB ${PAHOMQTTC_TARGET_DIR}/lib/paho-mqtt3c-static.lib) + set(PAHOMQTTC_ASYNC_STATIC_LIB ${PAHOMQTTC_TARGET_DIR}/lib/paho-mqtt3a-static.lib) + + set(PAHOMQTTC_INSTALL_BYPRODUCTS ${PAHOMQTTC_CLIENT_STATIC_LIB} ${PAHOMQTTC_ASYNC_STATIC_LIB}) +else() + # '-static' suffix is removed from library names if not win32. + # That is to be fixed because paho.mqtt.cpp expects the suffix. + set(PAHOMQTTC_CLIENT_STATIC_WRONGLIB ${PAHOMQTTC_TARGET_DIR}/lib/libpaho-mqtt3c.a) + set(PAHOMQTTC_ASYNC_STATIC_WRONGLIB ${PAHOMQTTC_TARGET_DIR}/lib/libpaho-mqtt3a.a) + + set(PAHOMQTTC_CLIENT_STATIC_LIB ${PAHOMQTTC_TARGET_DIR}/lib/libpaho-mqtt3c-static.a) + set(PAHOMQTTC_ASYNC_STATIC_LIB ${PAHOMQTTC_TARGET_DIR}/lib/libpaho-mqtt3a-static.a) + + set(PAHOMQTTC_INSTALL_BYPRODUCTS ${PAHOMQTTC_CLIENT_STATIC_WRONGLIB} ${PAHOMQTTC_ASYNC_STATIC_WRONGLIB}) +endif() +set(PAHOMQTTC_BYPRODUCTS ${PAHOMQTTC_CLIENT_STATIC_LIB} ${PAHOMQTTC_ASYNC_STATIC_LIB}) + +file(MAKE_DIRECTORY ${PAHOMQTTC_TARGET_DIR}/include) + +ExternalProject_Add( + pahomqttc + PREFIX ${PAHOMQTTC_TARGET_DIR} + SOURCE_DIR ${PAHOMQTTC_DIR} + CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PAHOMQTTC_TARGET_DIR} + CMAKE_ARGS -DPAHO_WITH_SSL=${PAHO_BUILD_WITH_SSL} + CMAKE_ARGS -DPAHO_BUILD_STATIC=${PAHO_BUILD_STATIC} + CMAKE_ARGS -DPAHO_ENABLE_TESTING=${PAHO_ENABLE_TESTING} + BUILD_COMMAND cmake --build . --config $ + INSTALL_COMMAND cmake --install . --config $ + BYPRODUCTS ${PAHOMQTTC_INSTALL_BYPRODUCTS} +) + +if( NOT WIN32 ) + ExternalProject_Add_Step(pahomqttc libfix + COMMAND ${CMAKE_COMMAND} -E copy ${PAHOMQTTC_CLIENT_STATIC_WRONGLIB} ${PAHOMQTTC_CLIENT_STATIC_LIB} + COMMAND ${CMAKE_COMMAND} -E copy ${PAHOMQTTC_ASYNC_STATIC_WRONGLIB} ${PAHOMQTTC_ASYNC_STATIC_LIB} + COMMENT "Fixing paho.mqtt.c static library names" + DEPENDS ${PAHOMQTTC_INSTALL_BYPRODUCTS} + BYPRODUCTS ${PAHOMQTTC_BYPRODUCTS} + ) +endif() + +# The paho.mqtt.cpp project needs to know where the paho.mqtt.c libraries +# and headers are installed, so we provide this information in an additional +# cmake file which is passed to the paho.mqtt.cpp project build +set(PAHOMQTTC_INCLUDE_DIR ${PAHOMQTTC_TARGET_DIR}/include) +set(PAHOMQTTC_LIBRARY_DIR ${PAHOMQTTC_TARGET_DIR}/lib) + +configure_file(${CMAKE_SOURCE_DIR}/cmake/fix_pahomqttcpp.cmake.in + ${CMAKE_BINARY_DIR}/cmake/fix_pahomqttcpp.cmake @ONLY) + + +add_library(paho-mqttc3::MQTTClient STATIC IMPORTED GLOBAL) +set_target_properties(paho-mqttc3::MQTTClient PROPERTIES + IMPORTED_LOCATION ${PAHOMQTTC_CLIENT_STATIC_LIB} + INTERFACE_INCLUDE_DIRECTORIES ${PAHOMQTTC_TARGET_DIR}/include) +add_library(paho-mqttc3::MQTTAsync STATIC IMPORTED GLOBAL) +set_target_properties(paho-mqttc3::MQTTAsync PROPERTIES + IMPORTED_LOCATION ${PAHOMQTTC_ASYNC_STATIC_LIB} + INTERFACE_INCLUDE_DIRECTORIES ${PAHOMQTTC_TARGET_DIR}/include) + +if( NOT WIN32 ) + # pthread is used and the link dependency needs to be explicitly specified here + set_target_properties(paho-mqttc3::MQTTClient paho-mqttc3::MQTTAsync PROPERTIES + INTERFACE_LINK_LIBRARIES pthread) +else() + # WIN32 + # winsocks are used and the link dependency needs to be explicitly specified here + set_target_properties(paho-mqttc3::MQTTClient paho-mqttc3::MQTTAsync PROPERTIES + INTERFACE_LINK_LIBRARIES ws2_32) +endif() diff --git a/cmake/pahomqttcpp.cmake b/cmake/pahomqttcpp.cmake new file mode 100644 index 0000000..0f0f355 --- /dev/null +++ b/cmake/pahomqttcpp.cmake @@ -0,0 +1,52 @@ +# +# Builds the paho.mqtt.cpp library +# +# Outputs the following target: +# paho-mqttpp3 +# +# When depending on paho-mqttp3, must also depend on +# either paho-mqttc3::MQTTClient or paho-mqttc3::MQTTAsync +# for using the blocking or the asynchrounous client, respectively. +# +include(ExternalProject) +set(PAHOMQTTCPP_DIR ${CMAKE_SOURCE_DIR}/modules/paho.mqtt.cpp) +set(PAHOMQTTCPP_TARGET_DIR ${CMAKE_BINARY_DIR}/modules/paho.mqtt.cpp) + +if( WIN32) + set(PAHOMQTTCPP_STATIC_LIB ${PAHOMQTTCPP_TARGET_DIR}/lib/paho-mqttpp3-static.lib) +else() + set(PAHOMQTTCPP_STATIC_LIB ${PAHOMQTTCPP_TARGET_DIR}/lib/libpaho-mqttpp3.a) +endif() + +set(PAHOMQTTCPP_BYPRODUCTS ${PAHOMQTTCPP_STATIC_LIB}) + +file(MAKE_DIRECTORY ${PAHOMQTTCPP_TARGET_DIR}/include) + +set(PAHO_BUILD_SHARED FALSE) +if(NOT ${PAHO_BUILD_STATIC}) + set(PAHO_BUILD_SHARED TRUE) +endif() + +ExternalProject_Add( + pahomqttcpp + DEPENDS pahomqttc + PREFIX ${PAHOMQTTCPP_TARGET_DIR} + SOURCE_DIR ${PAHOMQTTCPP_DIR} + CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + CMAKE_ARGS -DCMAKE_PROJECT_paho-mqtt-cpp_INCLUDE=${CMAKE_BINARY_DIR}/cmake/fix_pahomqttcpp.cmake + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PAHOMQTTCPP_TARGET_DIR} + CMAKE_ARGS -DPAHO_WITH_SSL=${PAHO_BUILD_WITH_SSL} + CMAKE_ARGS -DPAHO_BUILD_STATIC=${PAHO_BUILD_STATIC} + CMAKE_ARGS -DPAHO_BUILD_SHARED=${PAHO_BUILD_SHARED} + BUILD_COMMAND cmake --build . --config $ + INSTALL_COMMAND cmake --install . --config $ + BUILD_BYPRODUCTS ${PAHOMQTTCPP_BYPRODUCTS} +) + +add_library(paho-mqttpp3 STATIC IMPORTED GLOBAL) + +add_dependencies(paho-mqttpp3 pahomqttcpp) + +set_target_properties(paho-mqttpp3 PROPERTIES + IMPORTED_LOCATION ${PAHOMQTTCPP_STATIC_LIB} + INTERFACE_INCLUDE_DIRECTORIES ${PAHOMQTTCPP_TARGET_DIR}/include) diff --git a/docs/Build.rst b/docs/Build.rst index c0e19f4..02cc829 100755 --- a/docs/Build.rst +++ b/docs/Build.rst @@ -1,322 +1,321 @@ =========================== Building the RoSA Framework =========================== .. contents:: :local: .. _build_deps: Build Dependencies ================== In order to build RoSA, the following tools are required: -* `CMake `_ (minimum version 3.6.0 if clang-tidy is used, - 3.3.0 otherwise); +* `CMake `_ (minimum version 3.15.0); * Build system of your choice that can be targeted by CMake -- including a compiler supporting *C++17*. * Clang/LLVM -- minimum version 5.0.0 * Visual Studio 2017 -- minimum version 15.7 The following additional tools are required to generate documentation: * `Doxygen `_ -- for generating API documentation; * `Graphviz `_ -- not necessary, but the API documentation has nicer graphics when `dot` is available; * `Sphinx `_ (with *Python 2*) -- for generating documentation. The following additional tools are required to check and possibly enforce coding standard: * `clang-tidy `_ * `clang-format `_ General notes ============= * The framework is delivered with a CMake project, which can be used to generate build projects to build the framework. * The provided CMake project supports out-of-tree builds only, i.e. one must use a separate build directory outside of the RoSA source directory. .. _doxygen-warnings: * Doxygen warnings -- typically indicating actual errors -- are printed to `stderr`, do check that when generating API documentation. .. _cmake-variables: CMake Variables =============== Beyond the usual CMake variables, the following project-related options are available: `ROSA_INCLUDE_APPS` Generate build targets for the defined RoSA applications. The option is a string that is a list of app names. All apps that are present in the option are built, others are ignored. All apps are built by default. `ROSA_INCLUDE_TOOLS` Generate build targets for RoSA tools. The tools are also built when the option is set to `ON`, which is the default setting. `ROSA_INCLUDE_EXAMPLES` Generate build targets for RoSA examples. The examples are also built when the option is set to `ON`, which is the default setting. `ROSA_ENABLE_PEDANTIC` Compile the framework with using `-pedantic` for GCC-compatible compilers, otherwise ignored. The option defaults to `ON`. `ROSA_ENABLE_ASSERTIONS` Enable assertions for non-Debug builds, which defaults to `OFF`. Note that assertions are always enabled for Debug builds and this option is ignored for such builds. .. _CMake_clang_tidy: `ROSA_ENABLE_CLANG_TIDY` Run *clang-tidy* checks when building RoSA, which defaults to `OFF`. When the variable is enabled, build targets created by *Makefile* and *Ninja* generators include calls for clang-tidy. Other generators ignore this option. Note that CMake variables `CMAKE__CLANG_TIDY` are set when enabled. Settings for clang-tidy are defined by the file `.clang-tidy` in the RoSA source directory. Consider the following options when the option is enabled: `ROSA_CLANG_TIDY_PATH` Custom path for `clang-tidy` executable. In order to use clang-tidy, CMake needs to find the `clang-tidy` executable. If `clang-tidy` to be used is available via `PATH`, just leave the option empty -- which is default. Set the absolute path of the directory containing the `clang-tidy` executable otherwise, in which case no default path is searched for `clang-tidy`. `ROSA_CLANG_TIDY_FIX` Apply suggested clang-tidy fixes to the sources, which defaults to `OFF`. Enable the option only if you know what you are doing. .. _CMake_clang_format: `ROSA_INCLUDE_CLANG_FORMAT` **[experimental]** Generate build target -- `format-rosa` -- for formatting RoSA sources with *clang-format*, which defatuls to `OFF`. When the variable is enabled and *CMake is not running on a Windows host*, a build target is generated which can be used to have all the RoSA sources formatted with clang-format. Settings for clang-format are defined by the file `.clang-format` in the RoSA source directory. Note that executing build target `format-rosa` will reformat all the source files *inplace*. Consider the following option when a build target for clang-format is to be generated: `ROSA_CLANG_FORMAT_PATH` Custom path for `clang-format` executable. In order to use clang-format, CMake needs to find the `clang-format` executable. If `clang-format` to be used is available via `PATH`, just leave the option empty -- which is default. Set the absolute path of the directory containing the `clang-format` executable otherwise, in which case no default path is search for `clang-format`. `ROSA_LOG_LEVEL` Level of logging to be used, use one of the following valid integer values. ======== ========= Variable Log Level ======== ========= `0` `ERROR` `1` `WARNING` `2` `INFO` `3` `DEBUG` `4` `TRACE` `5` *disabled* ======== ========= Level of logging defaults to *disabled*. `ROSA_INCLUDE_DOCS` Generate build targets for RoSA documentation, defaults to `ON`. Note that the automatic execution of the generated build targets is controlled by the option `ROSA_BUILD_DOCS`. The actual documentations to build are controlled by the options `ROSA_ENABLE_DOXYGEN` and `ROSA_ENABLE_SPHINX`. `ROSA_BUILD_DOCS` Build RoSA documentation automatically as part of the build process. The option defaults to `OFF` and takes effect only if the option `ROSA_INCLUDE_DOCS` is enabled. .. _CMake_doxygen: `ROSA_ENABLE_DOXYGEN` Use *doxygen* to generate RoSA API documentation. The option defaults to `OFF` and takes effect only if the option `ROSA_INCLUDE_DOCS` is enabled. Doxygen documentation may be generated by executing build target `doxygen-rosa`, which is done as part of the default build process if `ROSA_BUILD_DOCS` is enabled. Doxygen must be available via `PATH` if the option is enabled. The following options are also available to tune doxygen: `ROSA_DOXYGEN_SVG` Use *svg* instead of *png* files for doxygen graphs. The option defaults to `OFF` and takes effect if the tool *dot* is available via `PATH` to be used to generated graph images. `ROSA_DOXYGEN_EXTERNAL_SEARCH` Enable doxygen external search, which defatuls to `OFF`. The following options need to be set if the option is enabled: `ROSA_DOXYGEN_SEARCHENGINE_URL` URL to use for external search. `ROSA_DOXYGEN_SEARCH_MAPPINGS` Doxygen Search Mappings. .. _CMake_sphinx: `ROSA_ENABLE_SPHINX` Use *Sphinx* to generate RoSA documentation. The option defaults to `OFF` and takes effect only if the option `ROSA_INCLUDE_DOCS` is enabled. Sphinx must be available via `PATH` if the option is enabled. The following options are also available to tune Sphinx: `SPHINX_OUTPUT_HTML` Output standalone HTML files. The option defaults to `ON`. Documentation may be generated by executing build target `docs-rosa-html`, which is done as part of the default build process if `ROSA_BUILD_DOCS` is enabled. `SPHINX_OUTPUT_MAN` Output man pages for RoSA applications and tools. The option defaults to `ON`. Man pages may be generated by executing build target `docs-rosa-man`, which is done as part of the default build process if `ROSA_BUILD_DOCS` is enabled. `SPHINX_WARNINGS_AS_ERRORS` When building documentation, treat Sphinx warnings as errors. The option defaults to `ON`. Building RoSA Step-by-Step ========================== Building on Linux with Make --------------------------- Configuring and building the framework on Linux using *Make* is a straightforward process which does not require performing any tricks. Set C and C++ compilers with the variables `CC` and `CXX`, respectively. Use the CMake variable `CMAKE_BUILD_TYPE` to set the type of build: `Debug`, `Release`. Follows an example on building the framework with all options turned on. CMake variables may be skipped as necessary. You need to have RoSA sources on your computer.:: rosa-src$ cd .. $ mkdir rosa-build $ cd rosa-build rosa-build$ CC= CXX= -G "Unix Makefiles" -DROSA_ENABLE_CLANG_TIDY=ON -DROSA_CLANG_TIDY_PATH= -DROSA_INCLUDE_CLANG_FORMAT=ON -DROSA_CLANG_FORMAT_PATH= -DROSA_LOG_LEVEL=4 -DROSA_ENABLE_DOXYGEN=ON -DROSA_DOXYGEN_SVG=ON -DROSA_ENABLE_SPHINX=ON -DCMAKE_BUILD_TYPE=Debug ../rosa-src [CMake configures and generates without errors] $ make [Make builds the project] You just need to re-run Make in order to re-build the project after changing the source code and the CMake project. In case the CMake project is changed, Make automatically calls CMake to update the build project. In order to build documentation and enforce coding standard, refer to corresponding :ref:`cmake-variables`. .. _Build_VS: Building on Windows with Visual Studio -------------------------------------- This is how to use the native MSVC compiler to build RoSA. Having your build system prepared (see `Build Dependencies`_) and RoSA sources fetched to your computer, configure and build the framework like this: #. Generate Visual Studio solution with CMake: #. Start CMake. #. Define *source directory* and a separate *build directory* in CMake. #. Click *Configure*. #. Select the proper *generator* for your version of Visual Studio. #. Click *Finish*. #. Tune CMake variables as you wish. * Note that Visual Studio Generators are multi-configuration generators, hence you cannot set `CMAKE_BUILD_TYPE`. You need to select a configuration to build in Visual Studio. #. Click *Generate*. #. Build the framework with Visual Studio: #. Open the generated `RoSA.sln` from the build directory with Visual Studio. #. Build the project `ALL_BUILD`. You just need to re-build the project in Visual Studio after changing the source code and the CMake project. In case the CMake project is changed, Visual Studio automatically calls CMake the update the build project. Build Result ============ The build process works in the build directory. After a successful build, one can find the following final outputs there -- besides some intermediate files. .. _Build_Result_Software: Software -------- In the build directory, `include` contains header files which are generated by CMake and provide configuration-specific information. The build process generates static libraries in `lib` and executables -- examples, apps, and tools -- in `bin`. Projects generated by a multi-configuration generator result in the actual libraries and executables being located in subdirectories corresponding to different build configurations. .. _Build_Result_Documentation: Documentation ------------- Documentation is generated in `docs`. The general documentation can be found in `docs/html`. Man pages for apps and tools can be found in `docs/man`. The API documentation can be found in `docs/doxygen/html`. .. rubric:: Footnotes diff --git a/docs/Dev.rst b/docs/Dev.rst index 9e88053..fdfeac3 100755 --- a/docs/Dev.rst +++ b/docs/Dev.rst @@ -1,389 +1,393 @@ ============================= Developing the RoSA Framework ============================= .. contents:: :local: This document provides information that might be useful for contributing to RoSA. Please also consult :doc:`Build`. .. _Dev_Source_Directory: The Source Directory ==================== The source directory consists of the following subdirectories: `cmake` Contains files used for configuring the `CMake Project`_. `docs` Contains `Documentation`_-related files. `examples` Contains `Examples`_ on using the public API. `include/rosa` Contains the RoSA public API -- that is the interface of RoSA `Libraries`_. The directory `include` is to be used as include directory and RoSA header files are to be included in C++ sources as `"rosa/"`. `lib` Contains the implementation of the RoSA public API -- that is the implementation of RoSA `Libraries`_. `apps` Contains `Apps`_ based on RoSA features. `tools` Contains `Tools`_ based on RoSA features. `modules` Contains third-party `Modules`_ that are not part of RoSA but used by `Apps`_ Software Sources ================ The section describes the `Logical Structure`_ of the software sources and what `Coding Standards`_ are supposed to be followed for the implementation. Logical Structure ----------------- Various features provided by RoSA are sorted into different `Libraries`_. `Examples`_ , `Apps`_, and `Tools`_ using those `Libraries`_ are separated from the implementation of the RoSA features into different directories. `Examples`_ , `Apps`_, and `Tools`_ may also use third-party libraries, called `Modules`_. Libraries ~~~~~~~~~ The framework consists of separate libraries providing different features. The public interfaces for RoSA libraries are defined in `include/rosa`, while corresponding implementation is in `lib`. Each library has its own subdirectory in the mentioned directories. RoSA provides the following libraries: `config` Provides information on the configuration used to build the framework, e.g., version number, log level, assertions, and debugging. `support` Provides general features -- template metaprograms dealing with types, for instance -- for implementing other libraries. `core` Provides the basic RoSA features, like systems managing agents passing messages. `agent` Provides features to be used for implementing agents. `app` Provides a modular interface for defining applications with RoSA. .. _Library_Dependencies: Dependencies '''''''''''' The following table summarizes dependencies among libraries. A marking in a row denotes that the library in the beginning of the row depends on the library in the head of the given column. +---------+--------+---------+------+-----+-------+ | | config | support | core | app | agent | +=========+========+=========+======+=====+=======+ | config | | | | | | +---------+--------+---------+------+-----+-------+ | support | | | | | | +---------+--------+---------+------+-----+-------+ | core | | × | | | | +---------+--------+---------+------+-----+-------+ | app | | | × | | | +---------+--------+---------+------+-----+-------+ | agent | | | | | | +---------+--------+---------+------+-----+-------+ Examples ~~~~~~~~ Some simple samples are provided in `examples` to demonstrate how to to use different parts of the RoSA API. Apps ~~~~ Apps, actual applications based on the RoSA libraries, are implemented in `apps`. Tools ~~~~~ Tools, programs based on the RoSA libraries and providing standalone functionalities, are implemented in `tools`. Modules ~~~~~~~ This directory contains third-party modules that can be used inside RoSA. These include: * cxxopts: https://github.com/jarro2783/cxxopts Example usage can be seen in modules/cxxopts/src/example.cpp * json: https://github.com/nlohmann/json Description in modules/json/README.md + * paho.mqtt.c: https://github.com/eclipse/paho.mqtt.c + An MQTT client implemented in C + * paho.mqtt.cpp: https://github.com/eclipse/paho.mqtt.cpp + An MQTT client for C++, which wraps and so depends on paho.mqtt.c .. _Coding_Standards: Coding Standards ---------------- RoSA is implemented in standard *C++17* code. All the software sources are to be written in accordance to the `LLVM Coding Standards`_. .. _nortti-noexceptions: Feature Restrictions ~~~~~~~~~~~~~~~~~~~~ Pay attention `not to use RTTI and Exceptions`_. Those features are disabled in the CMake project. Documentation Comments ~~~~~~~~~~~~~~~~~~~~~~ It is important to remember to document source code using `doxygen comments`_ as `API Documentation`_ is generated directly from sources. Note that the syntax of documentation comments is checked during compilation -- at least when using a GCC-compatible compiler. Further, check :ref:`Doxygen warnings ` as issues not being detected by the compiler may be found when actually generating the documentation. Whenever you work on a source file, make sure your name is in the author-list defined in the header comment of the file. Each author should be defined with a separate `\\author` command so that recent authors come first. Authors not participating in further development of a file anymore may be marked with the period of their contribution. If declarations belonging to a namespace are spread to more than one source files, document the namespace in a separate `namespace.h` in the directory belonging to the library. Otherwise, document the namespace in the only file in which entities of the namespace are declared. Header Files ~~~~~~~~~~~~ Follow the recommendations on public and private header files and the usage of `#include` from the `LLVM Coding Standards`_. Use `.h` and `.hpp` extensions to indicate the content of the header file: * header files containing any *definition* -- template or inline definition -- or including another header file with `.hpp` extension have `.hpp` extension; * header files containing only *declarations* and including only header files with `.h` extension have `.h` extension. It may happen that a header file does not need any corresponding implementation in a `.cpp` file. Nevertheless, do create a corresponding `.cpp` file which only includes the header file in this case. That makes sure that the header file is compiled and hence checked for errors, and also a corresponding entry in the compilation database is generated. Checking and Enforcing the Coding Standards ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The tools `clang-tidy `_ and `clang-format `_ can be used to check and enforce the coding standards. The two tools are integrated into the CMake project, refer to CMake variables :ref:`ROSA_ENABLE_CLANG_TIDY ` and :ref:`ROSA_INCLUDE_CLANG_FORMAT `. Note that there may be situations when `clang-tidy` checks result in false positives -- for example, for some cases of the order of `#include` directives. One can order `clang-tidy` to suppress warnings for a line of code by marking that line with:: // NOLINT It may be preferred to diverge from the standard formatting -- for example for the sake of readability of static definition of arrays following some structure. One can disable `clang-format` for some lines of code by designating a range with two special comments as:: // clang-format off ... clang-format is disabled here ... // clang-format on Documentation ============= The RoSA Framework is delivered with two kinds of documentation: `General Documentation`_ and `API Documentation`_, generation of both of which is integrated into the CMake project. References between the two documentations are relative addresses corresponding to the directory structure of the :ref:`generated documentation `. General Documentation --------------------- General documentation is written as `reStructuredText `_ compiled with `Sphinx `_. For build integration, refer to the CMake variable :ref:`ROSA_ENABLE_SPHINX `. Documentation files are located in `docs` with extension `.rst`. The main page of the documentation is `docs/index.rst`. Configuration for building the documentation is `docs/conf.py`. The directory `docs/CommandGuide` contains documentation for each separate tool. Those pages are included in the HTML documentation via `docs/CommandGuide/index.rst`. Moreover, man pages can be generated from those tool documentation pages. API Documentation ----------------- API documentation is directly generated from sources with `Doxygen `_. For build integration, refer to the CMake variable :ref:`ROSA_ENABLE_DOXYGEN `. The main page used for the API documentation is `docs/doxygen-mainpage.dox`. Configuration for generating the API documentation is `docs/doxygen.cfg.in`. .. _CMake Project: Managing the CMake Project ========================== This section briefly summarizes when and how to modify CMake files during the development process. No general discussion on CMake features is provided here. When modifying `Documentation`_, no need to update the CMake files. Software -------- One needs to modify the CMake files only if source files are to be added or removed from the project. Here follows some typical scenarios. Source Files ~~~~~~~~~~~~ Each library and executable target has its own directory and its own definition as a file called `CMakeLists.txt` in that directory. When adding or removing a source file -- both headers and `.cpp` files -- to a library or executable, locate the corresponding `CMakeLists.txt` file. The file is typically in the same directory where the file to be added or removed is located. Except for header files of the public API, for which the corresponding CMake target is defined in a `lib` subdirectory corresponding to the library the header files belongs to. Update the source list in the argument of the `add_library` or `add_executable` command in the `CMakeLists.txt`, for libraries and executables, respectively. A library and executable may use features provided by another library. Such a dependency is to be defined in the `CMakeLists.txt` file of the dependent target by using the `ROSA_add_library_dependencies` command. CMake Libraries ~~~~~~~~~~~~~~~ When adding or removing a library, add or remove the corresponding directories from `include` and `lib`. If you have already had generated a build project with CMake, `touch` [#]_ `lib/CMakeLists.txt` to make CMake rescan subdirectories on next build. When defining a new library, the new subdirectory under `lib` needs to contain a `CMakeLists.txt`, which needs to contain at least an `add_library` command defining the name of the library and the source files belonging to it. CMake Executables ~~~~~~~~~~~~~~~~~ When adding or removing an executable, add or remove the corresponding directory from `apps`, `examples`, or `tools`. If you have already had generated a build project with CMake, `touch` `CMakeLists.txt` in the containing directory (like in the case of libraries) to make CMake rescan subdirectories on next build. When defining a new executable, the new subdirectory needs to contain a `CMakeLists.txt`, which needs to contain at least an `add_executable` command defining the name of the executable and the source files belonging to it. .. _Dev Managing Sources: Managing Sources ================ Consider the followings before committing changes to the repository: * your code complies with the `Coding Standards`_ as much as possible; * your code is well documented; * your code is not bloated with unusued code and/or comments; * your changes do not break building and executing the framework: * test all of the supported platforms if possible, * look into the generated documentation if you have edited `General Documentation`_; * you do not pollute the repository with unused and generated files. When committing changes to the repository, provide a concise log message with your commit. Miscellaneous Concerns ====================== Using YCM --------- If you happen to use `YCM `_, just make a copy of the provided `ycm_extra_conf.py.template` file as `.ycm_extra_conf.py` in the RoSA source directory, and set the following two variables in it: `compilation_database_folder` the absolute path of your build directory `extra_system_include_dirs` any system include directory which might not be searched by `libclang` [#]_. You probably want compile with Clang if you use YCM, so run CMake with environment variables `CC=clang` and `CXX=clang++` set. Also note that header files in the `include` directory are compiled for YCM with the compiler flags of a corresponding source file in the `lib` directory, if any. Header files in other locations are supposed to have a corresponding source file in the same directory. Notes ~~~~~ * If the project's include directory (`include/rosa`) would ever be changed, then the YCM configuration file needs to be adjusted accordingly. .. rubric:: Footnotes .. [#] Set the last modified time of the file to the current time. .. [#] See: https://github.com/Valloric/YouCompleteMe/issues/303; use the following command to figure out the used system directories: echo | clang -std=c++11 -v -E -x c++ - .. _`LLVM Coding Standards`: http://llvm.org/docs/CodingStandards.html .. _`not to use RTTI and Exceptions`: http://llvm.org/docs/CodingStandards.html#do-not-use-rtti-or-exceptions .. _`doxygen comments`: http://llvm.org/docs/CodingStandards.html#doxygen-use-in-documentation-comments diff --git a/examples/mqtt-client/CMakeLists.txt b/examples/mqtt-client/CMakeLists.txt new file mode 100644 index 0000000..c7d9920 --- /dev/null +++ b/examples/mqtt-client/CMakeLists.txt @@ -0,0 +1,17 @@ +# Allow exceptions for cxxopts and paho.mqtt.cpp by removing restricting flag. +if ( ROSA_COMPILER_IS_GCC_COMPATIBLE ) + remove("-fno-exceptions" CMAKE_CXX_FLAGS) +endif() + +# Allow warnings for paho.mqtt.cpp +if ( ROSA_COMPILER_IS_GCC_COMPATIBLE ) + remove("-Werror" CMAKE_CXX_FLAGS) +elseif ( MSVC ) + remove("/WX" CMAKE_CXX_FLAGS) +endif() + +add_executable(mqtt-client mqtt-client.cpp) +ROSA_add_library_dependencies(mqtt-client ROSAConfig) +ROSA_add_library_dependencies(mqtt-client ROSASupport) +ROSA_add_library_dependencies(mqtt-client paho-mqttpp3) +ROSA_add_library_dependencies(mqtt-client paho-mqttc3::MQTTAsync) diff --git a/examples/mqtt-client/mqtt-client.cpp b/examples/mqtt-client/mqtt-client.cpp new file mode 100644 index 0000000..8d4dddc --- /dev/null +++ b/examples/mqtt-client/mqtt-client.cpp @@ -0,0 +1,151 @@ +//===-- examples/mqtt-client/mqtt-client.cpp --------------------*- C++ -*-===// +// +// The RoSA Framework +// +// Distributed under the terms and conditions of the Boost Software License 1.0. +// See accompanying file LICENSE. +// +// If you did not receive a copy of the license file, see +// http://www.boost.org/LICENSE_1_0.txt. +// +//===----------------------------------------------------------------------===// +/// +/// \file examples/mqtt-client/mqtt-client.cpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2020 +/// +/// \brief An example showcasing the basic usage of paho-mqttpp3. +/// +//===----------------------------------------------------------------------===// + +#include "rosa/config/version.h" + +#include "rosa/support/log.h" +#include "rosa/support/terminal_colors.h" + +#include "cxxopts/cxxopts.hpp" + +#include "mqtt/async_client.h" + +#include +#include +#include + +using namespace rosa; +using namespace rosa::terminal; + +/** + * Local callback & listener class for use with the client connection. + * This implementation is to receive messages but the mqtt::callback interface + * allows further actions to be defined. + */ +class MQTTCallback : public virtual mqtt::callback { + + /** Callback for when a message arrives. + * @param Msg Pointer for the MQTT message + **/ + void message_arrived(mqtt::const_message_ptr Msg) override { + std::string Topic = Msg->get_topic(); + std::string Message = Msg->to_string(); + LOG_INFO_STREAM << "[Message @ " << Topic << "] " << Message << std::endl; + } + +public: + /** Constructor **/ + MQTTCallback(void) noexcept = default; +}; + +/// Helper function to print an error message in red color to the terminal and +/// exit from the application. +/// +/// \note The function never returns as it calles `exit()`. +/// +/// \param Error error message +/// \param ExitCode exit code to return from the application +void logErrorAndExit(const std::string &Error, const int ExitCode) { + LOG_ERROR_STREAM << Color::Red << Error << Color::Default << std::endl; + exit(ExitCode); +} + +int main(int argc, char *argv[]) { + /// Host name of the MQTT server + std::string ServerHost = "localhost"; + + /// Port number of the MQTT server + uint16_t ServerPort = 1883; + + /// MQTT topic + std::string Topic = "test"; + + /// Message to publish. Subscribe and log messages otherwise. + std::string Message = ""; + + /// How long to receive messages. + const size_t TimeoutSecs = 60; + + // Handle command-line arguments. + try { + cxxopts::Options Options(argv[0], + library_string() + " -- eMQTT5 Client Example"); + Options.add_options()( + "host", "Server host", + cxxopts::value(ServerHost)->default_value("localhost"))( + "port", "Server port", + cxxopts::value(ServerPort)->default_value("1883"))( + "t,topic", "MQTT topic", cxxopts::value(Topic)->default_value("test"))( + "m,message", + "Message to publish (subscribe and log messages otherwise)", + cxxopts::value(Message))("h,help", "Print usage"); + + auto Args = Options.parse(argc, argv); + + if (Args.count("help")) { + LOG_INFO_STREAM << '\n' << Options.help() << std::endl; + exit(0); + } + + } catch (const cxxopts::OptionException &e) { + logErrorAndExit(e.what(), 1); + } catch (const std::invalid_argument &e) { + logErrorAndExit(e.what(), 1); + } + + try { + std::stringstream ss; + ss << "tcp://" << ServerHost << ":" << ServerPort; + const std::string ServerURI = ss.str(); + LOG_INFO_STREAM << "Initializing for " << ServerURI << std::endl; + mqtt::async_client Client(ServerURI, ""); + + LOG_INFO_STREAM << "Connecting to server" << std::endl; + Client.connect()->wait(); + + if (!Message.empty()) { + LOG_INFO_STREAM << "Publishing message '" << Message << "' to topic '" + << Topic << "'" << std::endl; + mqtt::topic T(Client, Topic); + T.publish(Message.c_str())->wait(); + + } else { + LOG_INFO_STREAM << "Receiving messages from topic '" << Topic + << "' for a short while..." << std::endl; + MQTTCallback Callback; + Client.set_callback(Callback); + Client.subscribe(Topic, {}); + + std::this_thread::sleep_for(std::chrono::seconds(TimeoutSecs)); + } + + LOG_INFO_STREAM << "Disconnecting from the server" << std::endl; + Client.disconnect()->wait(); + + } catch (const mqtt::exception &e) { + logErrorAndExit(e.what(), 1); + } + + LOG_INFO_STREAM << "Done." << std::endl; + + return 0; +} diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 6979d5e..b1b8c21 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,19 +1,19 @@ if ( ROSA_COMPILER_IS_GCC_COMPATIBLE ) # Allow exceptions by removing restricting flag (needed by cxxopts) remove("-fno-exceptions" CMAKE_CXX_FLAGS) # Allow dynamic casts by removing restriction flag (needed by cxxopts) remove("-fno-rtti" CMAKE_CXX_FLAGS) elseif ( MSVC ) # Exceptions enabled for MSVC by default but need to allow RTTI # (needed by cxxopts) remove("/GR-" CMAKE_CXX_FLAGS) endif() execute_process(COMMAND git submodule update --init --recursive WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) # Add the different subdirectories -ADDALLSUBDIRS() +ADDALLSUBDIRS(${ROSA_EXCLUDE_SUBMODULES}) diff --git a/modules/paho.mqtt.c b/modules/paho.mqtt.c new file mode 160000 index 0000000..2d3d094 --- /dev/null +++ b/modules/paho.mqtt.c @@ -0,0 +1 @@ +Subproject commit 2d3d0941a9233889d4d5cc37f5182bbefea261ad diff --git a/modules/paho.mqtt.cpp b/modules/paho.mqtt.cpp new file mode 160000 index 0000000..0e44dd3 --- /dev/null +++ b/modules/paho.mqtt.cpp @@ -0,0 +1 @@ +Subproject commit 0e44dd31ff725d66df4a928d50a26309626dcfd5