From 4e0d7ba1e191b687faf708cb3b9d1e8467b8dfba Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 14 Oct 2025 15:20:25 +0200 Subject: [PATCH 01/55] mqtt: new DataDescriptor approach (serialization/deserialization) for the module client side --- .../mqtt_receiver_fb_impl.h | 2 +- .../src/mqtt_receiver_fb_impl.cpp | 23 +++++++++++-------- .../src/mqtt_streaming_device_impl.cpp | 8 ++++--- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h index 94b4c492..6eec5c1a 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -41,7 +41,7 @@ class MqttReceiverFbImpl final : public FunctionBlock std::unordered_map outputDomainSignals; std::shared_ptr subscriber; - DictObjectPtr subscribedSignals; + DictObjectPtr subscribedSignals; std::mutex sync; diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index 7a4e7edf..644ef574 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -76,15 +76,17 @@ void MqttReceiverFbImpl::initProperties(const PropertyObjectPtr& config) void MqttReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); - auto prop = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtr(); - subscribedSignals = Dict(); - for (const auto& [topic, descriptor] : prop) - { - auto signalId = topic.asPtr(); - if (signalId.assigned()) - { - LOG_I("Signal in list: {}", signalId.toStdString()); - subscribedSignals.set(signalId, descriptor); + subscribedSignals = Dict(); + if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) { + auto prop = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtrOrNull(); + if (prop.assigned()) { + for (const auto& [topic, descriptor] : prop) { + auto signalId = topic.asPtr(); + if (signalId.assigned()) { + LOG_I("Signal in list: {}", signalId.toStdString()); + subscribedSignals.set(signalId, descriptor); + } + } } } } @@ -133,7 +135,8 @@ void MqttReceiverFbImpl::createSignals() std::string signalName = topic; boost::replace_all(signalName, "/", "_"); - auto signalDsc = descriptor; + auto signalDsc = JsonDeserializer().deserialize(descriptor).asPtrOrNull(); + auto getEpoch = []() ->std::string { const std::time_t epochTime = std::chrono::system_clock::to_time_t(std::chrono::time_point{}); char buf[48]; diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index b459aae0..f3d8eadc 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -122,7 +122,7 @@ DictPtr MqttStreamingDeviceImpl::onGetAvailableFunc for (const auto& device : deviceMap) { auto defaultConfig = PropertyObject(); - auto signalDict = Dict(); + auto signalDict = Dict(); for (const auto& signal : device.second) { auto builder = DataDescriptorBuilder().setSampleType(SampleType::Float64); if (!signal.name.empty()) @@ -139,8 +139,10 @@ DictPtr MqttStreamingDeviceImpl::onGetAvailableFunc quantity = signal.unit[2]; builder.setUnit(Unit(symbol, -1, name, quantity)); } - auto signalDsc = builder.build(); - signalDict.set(signal.topic, signalDsc); + + const auto serializer = JsonSerializer(); + builder.build().serialize(serializer); + signalDict.set(signal.topic, serializer.getOutput()); } defaultConfig.addProperty(DictProperty(PROPERTY_NAME_SIGNAL_LIST, signalDict)); From 019a8a65a58a4508833de27aa95ba29bef78806b Mon Sep 17 00:00:00 2001 From: "nikolai.shipilov" Date: Tue, 14 Oct 2025 17:25:58 +0200 Subject: [PATCH 02/55] Disable openSLL dependency by default --- CMakeLists.txt | 16 ++++++++++------ external/mqtt/CMakeLists.txt | 8 +++++++- .../src/MqttAsyncPublisher.cpp | 4 ++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eee2515e..fb519dbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ set(CMAKE_POLICY_VERSION_MINIMUM 3.5) cmake_minimum_required(VERSION 3.25) +set(SDK_TARGET_NAMESPACE daq) set(REPO_NAME mqtt_module) set(REPO_OPTION_PREFIX MQTT_MODULE) @@ -31,6 +32,7 @@ set(CMAKE_MESSAGE_CONTEXT_SHOW ON CACHE BOOL "Show CMake message context") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") option(OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS "Enable building example applications" OFF) +option(OPENDAQ_MQTT_MODULE_ENABLE_SSL "Enable building with openSSL" OFF) include(CommonUtils) setup_repo(${REPO_OPTION_PREFIX}) @@ -39,12 +41,14 @@ if(OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS) set(DAQMODULES_REF_DEVICE_MODULE ON CACHE BOOL "" FORCE) endif() - -find_package(OpenSSL REQUIRED) -if (OPENSSL_FOUND) - message(STATUS "Found OpenSSL ${OPENSSL_VERSION}") -else() - message(STATUS "OpenSSL Not Found") +if(OPENDAQ_MQTT_MODULE_ENABLE_SSL) + find_package(OpenSSL REQUIRED) + if (OPENSSL_FOUND) + message(STATUS "Found OpenSSL ${OPENSSL_VERSION}") + else() + message(FATAL_ERROR "OpenSSL Not Found") + endif() + add_compile_definitions(OPENDAQ_MQTT_MODULE_ENABLE_SSL) endif() add_subdirectory(external) diff --git a/external/mqtt/CMakeLists.txt b/external/mqtt/CMakeLists.txt index cea3bc6b..adc02ce0 100644 --- a/external/mqtt/CMakeLists.txt +++ b/external/mqtt/CMakeLists.txt @@ -12,7 +12,13 @@ FetchContent_Populate(paho_mqtt_c) # Set build options BEFORE add_subdirectory set(PAHO_HIGH_PERFORMANCE OFF CACHE BOOL "Enable PAHO High performance" FORCE) -set(PAHO_WITH_SSL ON CACHE BOOL "Enable SSL support" FORCE) + +if(OPENDAQ_MQTT_MODULE_ENABLE_SSL) + set(PAHO_WITH_SSL ON CACHE BOOL "Enable SSL support" FORCE) +else() + set(PAHO_WITH_SSL OFF CACHE BOOL "Enable SSL support" FORCE) +endif() + set(PAHO_BUILD_STATIC ON CACHE BOOL "Build static paho library" FORCE) set(PAHO_BUILD_SHARED OFF CACHE BOOL "Build dynamic paho library" FORCE) set(CMAKE_POSITION_INDEPENDENT_CODE ${PAHO_BUILD_STATIC} CACHE BOOL "" FORCE) diff --git a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp index 6b637703..95ee1b3a 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp @@ -37,8 +37,12 @@ MqttAsyncPublisher::MqttAsyncPublisher(std::string serverUrl, this->connOpts.minRetryInterval = 1; this->connOpts.maxRetryInterval = 10; this->createOpts = MQTTAsync_createOptions_initializer; + +#ifdef OPENDAQ_MQTT_MODULE_ENABLE_SSL this->ssl_opts = MQTTAsync_SSLOptions_initializer; this->connOpts.ssl = &this->ssl_opts; +#endif + this->client = nullptr; setServerURL(serverUrl); } From f158bf33e3ddacee67e622b8d8260d722d635e93 Mon Sep 17 00:00:00 2001 From: "nikolai.shipilov" Date: Tue, 14 Oct 2025 17:26:24 +0200 Subject: [PATCH 03/55] Fix includes --- mqtt_streaming_protocol/include/MqttAsyncPublisher.h | 1 + mqtt_streaming_protocol/include/MqttAsyncSubscriber.h | 1 + 2 files changed, 2 insertions(+) diff --git a/mqtt_streaming_protocol/include/MqttAsyncPublisher.h b/mqtt_streaming_protocol/include/MqttAsyncPublisher.h index 3c5a6d53..a835eef3 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncPublisher.h +++ b/mqtt_streaming_protocol/include/MqttAsyncPublisher.h @@ -6,6 +6,7 @@ #include "MQTTClientType.h" #include #include +#include namespace mqtt { diff --git a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h index f528c024..65453b78 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h +++ b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace mqtt { From 874e70618f9a9b3ff9c4c0523d7ca194cedac666 Mon Sep 17 00:00:00 2001 From: "nikolai.shipilov" Date: Tue, 14 Oct 2025 17:27:46 +0200 Subject: [PATCH 04/55] Remove include and make connection attempt verbose --- .../src/mqtt_streaming_server_impl.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp index 0125e546..c8f11aaf 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -171,7 +170,11 @@ void MqttStreamingServerImpl::setupMqttPublisher() publisher.setOnConnect([this]() { LOG_I("MQTT: Connection established"); }); LOG_I("MQTT: Trying to connect to MQTT broker ({})", connectionSettings.mqttUrl); - publisher.connect(); + if (!publisher.connect()) + { + LOG_E("MQTT: Connection failed"); + } + } void MqttStreamingServerImpl::sendData(const std::string& topic, const ChannelData& data, SizeT readAmount) From 640d154e7a84391ddc0fe4419caadccf750b9412 Mon Sep 17 00:00:00 2001 From: "nikolai.shipilov" Date: Tue, 14 Oct 2025 17:51:19 +0200 Subject: [PATCH 05/55] Stripped latest version of server module --- CMakeLists.txt | 32 +- examples/CMakeLists.txt | 3 - examples/ref-dev-mqtt-pub/CMakeLists.txt | 12 - examples/ref-dev-mqtt-pub/src/CMakeLists.txt | 7 - .../ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp | 94 ----- external/opendaq/CMakeLists.txt | 25 +- mqtt_streaming_protocol/CMakeLists.txt | 12 +- mqtt_streaming_protocol/include/IMqttBase.h | 87 ----- .../include/IMqttPublisher.h | 32 -- .../include/IMqttSubscriber.h | 22 -- .../include/MQTTClientType.h | 7 - .../include/MqttAsyncClient.h | 113 ++++++ .../include/MqttAsyncPublisher.h | 168 -------- .../include/MqttAsyncSubscriber.h | 271 ------------- .../include/MqttDataWrapper.h | 44 +++ mqtt_streaming_protocol/include/MqttMessage.h | 23 +- .../include/MqttSettings.h | 13 + mqtt_streaming_protocol/src/CMakeLists.txt | 28 +- .../src/MqttAsyncClient.cpp | 368 ++++++++++++++++++ .../src/MqttAsyncPublisher.cpp | 220 ----------- .../src/MqttAsyncSubscriber.cpp | 130 ------- .../src/MqttDataWrapper.cpp | 199 ++++++++++ mqtt_streaming_server_module/CMakeLists.txt | 9 +- .../mqtt_streaming_server_module/constants.h | 31 ++ .../mqtt_streaming_server_impl.h | 33 +- .../src/CMakeLists.txt | 6 + .../src/mqtt_streaming_server_impl.cpp | 237 ++--------- .../src/mqtt_streaming_server_module_impl.cpp | 5 +- 28 files changed, 890 insertions(+), 1341 deletions(-) delete mode 100644 examples/CMakeLists.txt delete mode 100644 examples/ref-dev-mqtt-pub/CMakeLists.txt delete mode 100644 examples/ref-dev-mqtt-pub/src/CMakeLists.txt delete mode 100644 examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp delete mode 100644 mqtt_streaming_protocol/include/IMqttBase.h delete mode 100644 mqtt_streaming_protocol/include/IMqttPublisher.h delete mode 100644 mqtt_streaming_protocol/include/IMqttSubscriber.h delete mode 100644 mqtt_streaming_protocol/include/MQTTClientType.h create mode 100644 mqtt_streaming_protocol/include/MqttAsyncClient.h delete mode 100644 mqtt_streaming_protocol/include/MqttAsyncPublisher.h delete mode 100644 mqtt_streaming_protocol/include/MqttAsyncSubscriber.h create mode 100644 mqtt_streaming_protocol/include/MqttDataWrapper.h create mode 100644 mqtt_streaming_protocol/include/MqttSettings.h create mode 100644 mqtt_streaming_protocol/src/MqttAsyncClient.cpp delete mode 100644 mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp delete mode 100644 mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp create mode 100644 mqtt_streaming_protocol/src/MqttDataWrapper.cpp create mode 100644 mqtt_streaming_server_module/include/mqtt_streaming_server_module/constants.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fb519dbf..0b521ce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# CMakeList.txt : CMake project for OpenDAQHistorianFB, include source and define +# CMakeList.txt : CMake project for MQTTStreamingModule, include source and define # project specific logic here. # set(CMAKE_POLICY_VERSION_MINIMUM 3.5) @@ -31,14 +31,13 @@ set(CMAKE_MESSAGE_CONTEXT_SHOW ON CACHE BOOL "Show CMake message context") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -option(OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS "Enable building example applications" OFF) option(OPENDAQ_MQTT_MODULE_ENABLE_SSL "Enable building with openSSL" OFF) include(CommonUtils) setup_repo(${REPO_OPTION_PREFIX}) if(OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS) - set(DAQMODULES_REF_DEVICE_MODULE ON CACHE BOOL "" FORCE) + set(DAQMODULES_REF_DEVICE_MODULE ON CACHE BOOL "" FORCE) endif() if(OPENDAQ_MQTT_MODULE_ENABLE_SSL) @@ -54,30 +53,3 @@ endif() add_subdirectory(external) add_subdirectory(mqtt_streaming_protocol) add_subdirectory(mqtt_streaming_server_module) - -if(OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS) - message(STATUS "Example applications have been enabled") - add_subdirectory(examples) -endif() - - - -# Set CPack variables -set(CPACK_COMPONENTS_ALL RUNTIME) -set(CPACK_PROJECT_NAME ${PROJECT_NAME}) -set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) -set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) -set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_BINARY_DIR}/package") - -# Set the CPack generator based on the platform -if (WIN32) - set(CPACK_GENERATOR "ZIP") -elseif (UNIX AND NOT APPLE) - cmake_host_system_information(RESULT DISTRO_ID QUERY DISTRIB_ID) - cmake_host_system_information(RESULT DISTRO_VERSION_ID QUERY DISTRIB_VERSION_ID) - set(CPACK_SYSTEM_NAME "${DISTRO_ID}${DISTRO_VERSION_ID}") - set(CPACK_GENERATOR "TGZ") -endif() - -# Include CPack for packaging -include(CPack) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index 25c3842e..00000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -add_subdirectory(ref-dev-mqtt-pub) diff --git a/examples/ref-dev-mqtt-pub/CMakeLists.txt b/examples/ref-dev-mqtt-pub/CMakeLists.txt deleted file mode 100644 index 104f4d5a..00000000 --- a/examples/ref-dev-mqtt-pub/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -set(EXAMPLE_PROJECT_NAME "ref-dev-mqtt-pub") - -project(${EXAMPLE_PROJECT_NAME} LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -add_subdirectory(src) diff --git a/examples/ref-dev-mqtt-pub/src/CMakeLists.txt b/examples/ref-dev-mqtt-pub/src/CMakeLists.txt deleted file mode 100644 index 8ccc98a1..00000000 --- a/examples/ref-dev-mqtt-pub/src/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -add_compile_definitions(MODULE_PATH="${OPENDAQ_MODULES_DIR}") - -add_executable(${EXAMPLE_PROJECT_NAME} ref-dev-mqtt-pub.cpp) -add_dependencies(${EXAMPLE_PROJECT_NAME} daq::ref_device_module) -target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq) \ No newline at end of file diff --git a/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp b/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp deleted file mode 100644 index 3ee4ca13..00000000 --- a/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include - -#include - -using namespace daq; -namespace -{ -class InputArgs -{ -public: - void addArg(const std::string& name, const std::string& description) - { - argDescriptions[name] = description; - } - - void parse(int argc, char* argv[]) - { - parsedArgs.clear(); - for (int i = 1; i < argc; ++i) - parsedArgs.push_back(argv[i]); - } - - bool hasArg(const std::string& name) const - { - return std::find(parsedArgs.begin(), parsedArgs.end(), name) != parsedArgs.end(); - } - - bool hasUnknownArgs() const - { - for (const auto& arg : parsedArgs) { - if (argDescriptions.find(arg) == argDescriptions.end()) - return true; - } - return false; - } - - void printHelp() const - { - std::cout << "Available arguments:" << std::endl; - for (const auto& [name, desc] : argDescriptions) - std::cout << " " << name << " : " << desc << std::endl; - } - -private: - std::unordered_map argDescriptions; - std::vector parsedArgs; -}; - -} // end of namespace - -int main(int argc, char* argv[]) -{ - InputArgs args; - args.addArg("--help", "Show help message"); - args.parse(argc, argv); - - if (args.hasArg("--help") || args.hasUnknownArgs()) { - args.printHelp(); - return 0; - } - - using namespace std::chrono_literals; - StringPtr loggerPath = "ref_device_simulator.log"; - - auto users = List(); - users.pushBack(User("opendaq", "$2b$10$bqZWNEd.g1R1Q1inChdAiuDr5lbal33bBNOehlCwuWcxRH5weF3hu")); // password: opendaq - users.pushBack(User("root", "$2b$10$k/Tj3yqFV7uQz42UCJK2n.4ECd.ySQ2Sfd81Kx.xfuMOeluvA/Vpy", {"admin"})); // password: root - const AuthenticationProviderPtr authenticationProvider = StaticAuthenticationProvider(true, users); - - PropertyObjectPtr config = PropertyObject(); - config.addProperty(StringProperty("Name", "Reference device simulator")); - config.addProperty(StringProperty("LocalId", "RefDevSimulator")); - config.addProperty(StringProperty("SerialNumber", "sim01")); - config.addProperty(BoolProperty("EnableLogging", true)); - config.addProperty(StringProperty("LoggingPath", loggerPath)); - - const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH) - .addDiscoveryServer("mdns") - .setRootDevice("daqref://device1", config) - .setLogger(Logger(loggerPath)) - .setAuthenticationProvider(authenticationProvider) - .build(); - - auto refDevice = instance.addDevice("daqref://device0"); - refDevice.setPropertyValue("EnableProtectedChannel", true); - - auto serverConfig = instance.getAvailableServerTypes().get("OpenDAQMQTT").createDefaultConfig(); - serverConfig.setPropertyValue("BrokerAddress", "127.0.0.1"); - const auto mqttServer = instance.addServer("OpenDAQMQTT", serverConfig); - - std::cout << "Press \"enter\" to exit the application..." << std::endl; - std::cin.get(); - return 0; -} diff --git a/external/opendaq/CMakeLists.txt b/external/opendaq/CMakeLists.txt index a886ad1f..c4fb979c 100644 --- a/external/opendaq/CMakeLists.txt +++ b/external/opendaq/CMakeLists.txt @@ -3,10 +3,33 @@ set(OPENDAQ_ENABLE_TESTS false) FetchContent_Declare( opendaq GIT_REPOSITORY git@github.com:openDAQ/openDAQ.git - GIT_TAG b38e835b591317fdd1f9f84aad2bd79f04c06fd3 + GIT_TAG v3.29.0-rc GIT_PROGRESS ON EXCLUDE_FROM_ALL SYSTEM ) FetchContent_MakeAvailable(opendaq) + +# Ensure we have the source dir variable (set by FetchContent) +if(NOT DEFINED opendaq_SOURCE_DIR) + FetchContent_GetProperties(opendaq) + if(NOT opendaq_POPULATED) + FetchContent_Populate(opendaq) + endif() +endif() + +set(OPENDAQ_VERSION_FILE "${opendaq_SOURCE_DIR}/opendaq_version") + +if(EXISTS "${OPENDAQ_VERSION_FILE}") + file(READ "${OPENDAQ_VERSION_FILE}" _raw_version) + string(STRIP "${_raw_version}" _raw_version) + + string(REGEX REPLACE "^([0-9]+\\.[0-9]+\\.[0-9]+).*" "\\1" OPENDAQ_PACKAGE_VERSION "${_raw_version}") + + message(STATUS "openDAQ version: ${OPENDAQ_PACKAGE_VERSION}") + + set(OPENDAQ_PACKAGE_VERSION "${OPENDAQ_PACKAGE_VERSION}" CACHE STRING "openDAQ package version" FORCE) +else() + message(WARNING "opendaq_version file not found at: ${OPENDAQ_VERSION_FILE}") +endif() diff --git a/mqtt_streaming_protocol/CMakeLists.txt b/mqtt_streaming_protocol/CMakeLists.txt index e58670a2..d65c2e35 100644 --- a/mqtt_streaming_protocol/CMakeLists.txt +++ b/mqtt_streaming_protocol/CMakeLists.txt @@ -1,8 +1,10 @@ cmake_minimum_required(VERSION 3.10) -set_cmake_folder_context(TARGET_FOLDER_NAME ${SDK_TARGET_NAMESPACE}_mqtt_streaming_protocol) -project(OpenDaqMqttStreamingProtocol - VERSION 1.0.0 - LANGUAGES CXX -) +set_cmake_folder_context(TARGET_FOLDER_NAME) + +set(MQTT_STREAMING_PROTOCOL_VERSION ${OPENDAQ_PACKAGE_VERSION}) +set(MQTT_STREAMING_PROTOCOL_PRJ_NAME "OpenDaqMqttStreamingProtocol") + +message(STATUS "${MQTT_STREAMING_PROTOCOL_PRJ_NAME} version: ${MQTT_STREAMING_PROTOCOL_VERSION}") +project(${MQTT_STREAMING_PROTOCOL_PRJ_NAME} VERSION ${MQTT_STREAMING_PROTOCOL_VERSION} LANGUAGES C CXX) add_subdirectory(src) diff --git a/mqtt_streaming_protocol/include/IMqttBase.h b/mqtt_streaming_protocol/include/IMqttBase.h deleted file mode 100644 index 14254fee..00000000 --- a/mqtt_streaming_protocol/include/IMqttBase.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once -#include -#include -#include "MQTTClientType.h" - -enum class MqttClientType -{ - sync, - async -}; - -namespace mqtt -{ - - enum class MqttConnectionStatus - { - not_connected, - connected, - pending, - }; - -class IMqttBase -{ -public: - IMqttBase(bool enableSSL, - bool useCertificates, - bool verifyServerCert, - std::string trustStorePath, - std::string clientCertPath, - std::string privKeyPath, - std::string privKeyPass) - : enableSSL(enableSSL) - , useCertificates(useCertificates) - , verifyServerCert(verifyServerCert) - , trustStorePath(trustStorePath) - , clientCertPath(clientCertPath) - , privKeyPath(privKeyPath) - , privKeyPass(privKeyPass) - { - } - virtual bool connect() = 0; - virtual bool reconnect() = 0; - virtual bool disconnect() = 0; - void setConnectionStatusCB(std::function cb){ - connCb = cb; - }; - void setOnConnect(std::function cb) { - onConnectCb = cb; - } - virtual void setUsernamePasswrod(std::string username, std::string password) = 0; - virtual void setServerURL(std::string serverUrl) = 0; - virtual std::string getServerUrl() const = 0; - virtual void setClientId(std::string clientId) = 0; - virtual void setSSLConnectionProperties(bool enableSSL, - bool useCertificates, - bool verifyServerCert, - std::string trustStorePath, - std::string clientCertPath, - std::string privKeyPath, - std::string privKeyPass) - { - this->enableSSL = enableSSL; - this->useCertificates = useCertificates; - this->verifyServerCert = verifyServerCert; - this->trustStorePath = trustStorePath; - this->clientCertPath = clientCertPath; - this->privKeyPath = privKeyPath; - this->privKeyPass = privKeyPass; - } - virtual MqttConnectionStatus isConnected() = 0; - virtual MqttClientType getClientType() const = 0; - virtual ~IMqttBase() - { - } - -protected: - bool enableSSL; - bool useCertificates; - bool verifyServerCert; - std::string trustStorePath; - std::string clientCertPath; - std::string privKeyPath; - std::string privKeyPass; - std::function connCb; - std::function onConnectCb; -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/IMqttPublisher.h b/mqtt_streaming_protocol/include/IMqttPublisher.h deleted file mode 100644 index 0559c2f0..00000000 --- a/mqtt_streaming_protocol/include/IMqttPublisher.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include "IMqttBase.h" -#include - -namespace mqtt -{ -class IMqttPublisher : public virtual IMqttBase -{ -public: - virtual void publishBirthCertificates() = 0; - virtual bool publish(const std::string& topic, void* data, size_t dataLen, std::string* err = nullptr, int qos = 1, int *token = nullptr, bool retained = false) = 0; - virtual void setStateCB(std::function cb) = 0; - virtual void setPublishSuccessCB(std::function cb) - { - this->sentSuccessCb = cb; - } - - virtual void setPublishFailCB(std::function cb) - { - this->sentFailCb = cb; - } - - virtual ~IMqttPublisher() - { - } - -protected: - std::function cb; - std::function sentSuccessCb; - std::function sentFailCb; -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/IMqttSubscriber.h b/mqtt_streaming_protocol/include/IMqttSubscriber.h deleted file mode 100644 index a2cfdbea..00000000 --- a/mqtt_streaming_protocol/include/IMqttSubscriber.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "IMqttBase.h" -#include -#include "MqttMessage.h" - -namespace mqtt -{ -class IMqttSubscriber : public virtual IMqttBase -{ -public: - virtual bool subscribe(std::string topic, int qos) = 0; - virtual bool unsubscribe(std::string topic) = 0; - virtual bool unsubscribeAll() = 0; - virtual void setMessageArrivedCb(std::function cb) = 0; - virtual ~IMqttSubscriber() - { - } - -protected: - std::function cb; -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/MQTTClientType.h b/mqtt_streaming_protocol/include/MQTTClientType.h deleted file mode 100644 index 707254d3..00000000 --- a/mqtt_streaming_protocol/include/MQTTClientType.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -enum class MQTTClientType -{ - publisher, - subscriber, - pubsub, -}; diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h new file mode 100644 index 00000000..964b47f6 --- /dev/null +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include +#include + +#include "MQTTAsync.h" +#include "MqttMessage.h" + +namespace mqtt { + +enum class MqttConnectionStatus { + not_connected, + connected, + pending, +}; + +struct MqttSubscription { + std::string topic; + int qos; + + MqttSubscription(std::string t, int q) + : topic(t) + , qos(q) + {} +}; + +class MqttAsyncClient final { +public: + using MsgArrivedCb_type = void(const MqttAsyncClient &, mqtt::MqttMessage &msg); + + MqttAsyncClient(); + MqttAsyncClient(std::string serverUrl, std::string clientId, bool cleanSession); + MqttAsyncClient(const MqttAsyncClient &) = delete; + MqttAsyncClient &operator=(const MqttAsyncClient &) = delete; + MqttAsyncClient(MqttAsyncClient &&) = delete; + MqttAsyncClient &operator=(MqttAsyncClient &&) = delete; + ~MqttAsyncClient(); + + bool connect(); + bool disconnect(); + + bool publish(const std::string &topic, + void *data, + size_t dataLen, + std::string *err = nullptr, + int qos = 1, + int *token = nullptr, + bool retained = false); + + bool subscribe(std::string topic, int qos); + bool unsubscribe(std::string topic); + bool unsubscribeAll(); + + void setConnectedCb(std::function cb); + void setMessageArrivedCb(std::string topic, std::function cb); + void setMessageArrivedCb(std::function cb); + void setDisconnectCb(std::function cb); + void setSentCb(std::function cb); + void setDeliveryCompletedCb(std::function cb); + + void setServerURL(std::string serverUrl); + std::string getServerUrl() const; + void setUsernamePasswrod(std::string username, std::string password); + void setClientId(std::string clientId); + MqttConnectionStatus isConnected() const; + +private: + std::string serverUrl; + std::string clientId; + std::string username; + std::string password; + + std::atomic pendingConnect; + + MQTTAsync client; + MQTTAsync_connectOptions connOpts; + MQTTAsync_disconnectOptions disconnOpts; + MQTTAsync_createOptions createOpts; + + std::recursive_mutex cbMtx; + + std::function onConnectedCb; + std::function onSentCb; + std::function onDisconnectCb; + std::function onDeliveryCompletedCb; + std::function onMsgArrivedCmnCb; + std::unordered_map> onMsgArrivedCbs; + + std::vector subscriptions; + + std::lock_guard getCbLock(); + + static void onDeliveryCompleted(void *context, MQTTAsync_token token); + static void onConnected(void *context, char *cause); + static void onConnectionLost(void *context, char *cause); + static int onMsgArrived(void *context, + char *topicName, + int topicLen, + MQTTAsync_message *message); + static void onSendSuccess(void *context, MQTTAsync_successData *data); + static void onSendFailure(void *context, MQTTAsync_failureData *data); + static void onConnectSuccess(void *context, MQTTAsync_successData *data); + static void onConnectFailure(void *context, MQTTAsync_failureData *data); + static void onDisconnectSuccess(void *context, MQTTAsync_successData *data); + static void onDisconnectFailure(void *context, MQTTAsync_failureData *data); + static void onSubscribeSuccess(void *context, MQTTAsync_successData *response); + static void onSubscribeFailure(void *context, MQTTAsync_failureData *response); + static void onUnsubscribeSuccess(void *context, MQTTAsync_successData *response); + static void onUnsubscribeFailure(void *context, MQTTAsync_failureData *response); +}; +} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/MqttAsyncPublisher.h b/mqtt_streaming_protocol/include/MqttAsyncPublisher.h deleted file mode 100644 index a835eef3..00000000 --- a/mqtt_streaming_protocol/include/MqttAsyncPublisher.h +++ /dev/null @@ -1,168 +0,0 @@ -#pragma once -#include "IMqttPublisher.h" - -#include "MQTTAsync.h" -#include "MqttMessage.h" -#include "MQTTClientType.h" -#include -#include -#include - -namespace mqtt -{ -class MqttAsyncPublisher : public virtual IMqttPublisher -{ -public: - MqttAsyncPublisher(); - MqttAsyncPublisher(std::string serverUrl, - std::string clientId, - bool cleanSession, - bool enableSSL, - bool useCertificates, - bool verifyServerCert, - std::string trustStorePath, - std::string clientCertPath, - std::string privKeyPath, - std::string privKeyPass); - ~MqttAsyncPublisher(); - // Inherited via IMqttSyncPublisher - virtual bool connect() override; - virtual bool disconnect() override; - virtual MqttConnectionStatus isConnected() override; - virtual void setUsernamePasswrod(std::string username, std::string password) override; - virtual void publishBirthCertificates(); - virtual bool publish(const std::string& topic, void* data, size_t dataLen, std::string* err = nullptr, int qos = 1, int* token = nullptr, bool retained = false) override; - virtual void setStateCB(std::function cb) override; - virtual void setServerURL(std::string serverUrl) override; - virtual void setClientId(std::string clientId) override; - virtual bool reconnect() override; - - virtual MqttClientType getClientType() const - { - return MqttClientType::async; - } - - std::string getServerUrl() const override - { - return this->serverUrl; - } - -private: - std::string serverUrl; - std::string clientId; - std::string username; - std::string password; - MQTTAsync client; - MQTTAsync_connectOptions connOpts; - MQTTAsync_createOptions createOpts; - MQTTAsync_SSLOptions ssl_opts = MQTTAsync_SSLOptions_initializer; - std::recursive_mutex mtx; - bool penddingConnect; - - const MQTTClientType type = MQTTClientType::publisher; - - void setPendingConnect(bool penddingConnect) - { - this->penddingConnect = penddingConnect; - } - - void writeConnectionStatusMsg(std::string who, std::string msg, int level) - { - if (this->connCb) - { - this->connCb(who, msg, level, type); - } - } - - static void deliveryComplete(void* context, MQTTAsync_token token) - { - - } - - static void connectedCb(void* context, char* cause) - { - (void) cause; - // Reconnect procedure here! - if (context != nullptr) - { - auto publisher = (MqttAsyncPublisher*) context; - publisher->setPendingConnect(false); - - if (publisher->onConnectCb) - publisher->onConnectCb(); - - MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - int rc; - opts.onSuccess = MqttAsyncPublisher::onSubscriber; - opts.onFailure = MqttAsyncPublisher::onSubscriberFailure; - opts.context = publisher; - if ((rc = MQTTAsync_subscribe(publisher->client, "STATE", 1, &opts)) != MQTTASYNC_SUCCESS) - { - } - } - } - - static void connlost(void* context, char* cause) - { - (void) cause; - // Reconnect procedure here! - if (context != nullptr) - { - auto publisher = (MqttAsyncPublisher*) context; - publisher->setPendingConnect(false); - } - } - static int msgArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) - { - auto publisher = (MqttAsyncPublisher*) context; - if (publisher->cb) - { - publisher->cb(); - } - MQTTAsync_freeMessage(&message); - // if (topicLen > 0) - MQTTAsync_free(topicName); - return 1; - } - - // Inherited via IMqttSyncBase - static void delivered(void* context, MQTTAsync_successData* rsp) - { - } - - static void onSubscriber(void* context, MQTTAsync_successData* response) - { - - } - - static void onSubscriberFailure(void* context, MQTTAsync_failureData* response) - { - - } - - static void onSend(void* context, MQTTAsync_successData* data) - { - auto publisher = (MqttAsyncPublisher*) context; - if (publisher->sentSuccessCb) - publisher->sentSuccessCb(data->token); - } - - static void onSendFailure(void* context, MQTTAsync_failureData* data) - { - auto publisher = (MqttAsyncPublisher*) context; - if (publisher->sentFailCb) - publisher->sentFailCb(data->token); - } - - static void onConnectSuccess(void * context, MQTTAsync_successData data) - { - - } - - static void onConnectFailure(void* context, MQTTAsync_failureData data) - { - auto publisher = (MqttAsyncPublisher*) context; - publisher->setPendingConnect(false); - } -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h deleted file mode 100644 index 65453b78..00000000 --- a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h +++ /dev/null @@ -1,271 +0,0 @@ -#pragma once - -#include "IMqttSubscriber.h" - -#include "MQTTAsync.h" -#include "MqttMessage.h" -#include "MQTTClientType.h" -#include -#include -#include -#include -#include - -namespace mqtt -{ - -struct MqttSubscription { - std::string topic; - int qos; -}; - -class MqttAsyncSubscriber : public virtual IMqttSubscriber -{ -public: - MqttAsyncSubscriber(); - MqttAsyncSubscriber(std::string serverUrl, - std::string clientId, - bool cleanSession, - bool enableSSL, - bool useCertificates, - bool verifyServerCert, - std::string trustStorePath, - std::string clientCertPath, - std::string privKeyPath, - std::string privKeyPass) - : IMqttBase(enableSSL, useCertificates, verifyServerCert, trustStorePath, clientCertPath, privKeyPath, privKeyPass) - { - this->serverUrl = serverUrl; - this->clientId = clientId; - this->username = ""; - this->password = ""; - this->connOpts = MQTTAsync_connectOptions_initializer; - this->connOpts.cleansession = cleanSession ? 1 : 0; - this->connOpts.keepAliveInterval = 20; - this->connOpts.connectTimeout = 5; - this->connOpts.onSuccess = (MQTTAsync_onSuccess*) &MqttAsyncSubscriber::onConnectSuccess; - this->connOpts.onFailure = (MQTTAsync_onFailure*) &MqttAsyncSubscriber::onConnectFailure; - this->connOpts.automaticReconnect = true; - this->connOpts.minRetryInterval = 1; - this->connOpts.maxRetryInterval = 10; - this->createOpts = MQTTAsync_createOptions_initializer; - this->ssl_opts = MQTTAsync_SSLOptions_initializer; - this->connOpts.ssl = &this->ssl_opts; - - this->willOpts = MQTTAsync_willOptions_initializer; - this->willOpts.topicName = "STATE"; - this->willOpts.qos = 1; - this->willOpts.retained = 1; - this->willOpts.message = "offline"; - this->connOpts.will = &this->willOpts; - this->client = nullptr; - setServerURL(serverUrl); - } - - ~MqttAsyncSubscriber() - { - std::lock_guard guard(mtx); - if (this->client != nullptr) - { - disconnect(); - MQTTAsync_destroy(&this->client); - } - } - - virtual bool connect() override; - virtual bool disconnect() override; - virtual MqttConnectionStatus isConnected() override; - virtual void setServerURL(std::string serverUrl) override; - virtual void setClientId(std::string clientId) override; - virtual void setUsernamePasswrod(std::string username, std::string password); - - virtual MqttClientType getClientType() const - { - return MqttClientType::async; - } - - virtual bool unsubscribeAll() override - { - std::lock_guard guard(mtx); - for (auto& sub : subscriptions) - { - unsubscribe(sub.topic); - } - subscriptions.clear(); - - return true; - } - - virtual bool subscribe(std::string topic, int qos) override - { - std::lock_guard guard(mtx); - MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - int rc; - opts.onSuccess = MqttAsyncSubscriber::onSubscriber; - opts.onFailure = MqttAsyncSubscriber::onSubscriberFailure; - opts.context = this; - if ((rc = MQTTAsync_subscribe(client, topic.c_str(), qos, &opts)) == MQTTASYNC_SUCCESS) - { - MqttSubscription newSubscription = { topic, qos }; - subscriptions.emplace_back(newSubscription); - } - return rc == MQTTASYNC_SUCCESS; - } - - virtual bool unsubscribe(std::string topic) override - { - std::lock_guard guard(mtx); - MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - int rc; - opts.onSuccess = MqttAsyncSubscriber::onSubscriber; - opts.onFailure = MqttAsyncSubscriber::onSubscriberFailure; - opts.context = this; - rc = MQTTAsync_unsubscribe(this->client, topic.c_str(), &opts); - auto it = std::find_if(subscriptions.begin(), subscriptions.end(), [&topic](MqttSubscription sub) { return sub.topic == topic; }); - if (it != subscriptions.end()) - subscriptions.erase(it); - return rc == MQTTASYNC_SUCCESS; - } - - virtual void setMessageArrivedCb(std::function cb) override - { - std::lock_guard guard(mtx); - this->cb = cb; - } - - - virtual bool reconnect() override - { - return true; - } - - std::string getClientId() const - { - return this->clientId; - } - - std::string getServerUrl() const override - { - return this->serverUrl; - } - -private: - std::atomic initialConnectionThreadStart = false; - std::string serverUrl; - std::string clientId; - std::string username; - std::string password; - std::vector subscriptions; - std::recursive_mutex mtx; - bool penddingConnect; - const MQTTClientType type = MQTTClientType::subscriber; - - MQTTAsync client; - MQTTAsync_connectOptions connOpts; - MQTTAsync_createOptions createOpts; - MQTTAsync_SSLOptions ssl_opts = MQTTAsync_SSLOptions_initializer; - MQTTAsync_willOptions willOpts; - - void setPendingConnect(bool penddingConnect) - { - this->penddingConnect = penddingConnect; - } - - void writeConnectionStatusMsg(std::string who, std::string msg, int level) - { - if (this->connCb) - { - this->connCb(who, msg, level, type); - } - } - - // Inherited via IMqttSyncBase - - static int msgArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) - { - MqttAsyncSubscriber* subscriber = (MqttAsyncSubscriber*) context; - if (subscriber->cb) - { - mqtt::MqttMessage msg; - // Get the topic - msg.setTopic(topicName); - // Copy the payload - msg.addData((uint8_t*) message->payload, message->payloadlen); - - subscriber->cb(*subscriber, msg); - } - - MQTTAsync_freeMessage(&message); - if (topicLen > 0) - MQTTAsync_free(topicName); - return 1; - } - - static void connectedCb(void* context, char* cause) - { - if (context != nullptr) - { - try - { - auto subscriber = (MqttAsyncSubscriber*) context; - subscriber->setPendingConnect(false); - MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - int rc; - opts.onSuccess = MqttAsyncSubscriber::onSubscriber; - opts.onFailure = MqttAsyncSubscriber::onSubscriberFailure; - opts.context = subscriber; - if (subscriber->onConnectCb) - subscriber->onConnectCb(); - } - catch (std::exception ex) - { - } - } - } - static void connlost(void* context, char* cause) - { - // Reconnect procedure here! - if (context != nullptr) - { - - } - } - - // Inherited via IMqttSyncSubscriber - static void delivered(void* context, MQTTAsync_successData* rsp) - { - } - - static void onSubscriber(void* context, MQTTAsync_successData* response) - { - } - - static void onSubscriberFailure(void* context, MQTTAsync_failureData* response) - { - } - - static void onSend(void* context, MQTTAsync_successData data) - { - } - - static void onSendFailure(void* context, MQTTAsync_failureData data) - { - - } - - static void onConnectSuccess(void* context, MQTTAsync_successData data) - { - // This callback only fires onec when call to MQTTAsync_connect is called. - } - - static void onConnectFailure(void* context, MQTTAsync_failureData data) - { - auto subscriber = (MqttAsyncSubscriber*) context; - subscriber->setPendingConnect(false); - } - - static void deliveryComplete(void* context, MQTTAsync_token token) - { - } -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/mqtt_streaming_protocol/include/MqttDataWrapper.h new file mode 100644 index 00000000..8cff6a0d --- /dev/null +++ b/mqtt_streaming_protocol/include/MqttDataWrapper.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace mqtt { + +struct SampleData { + double value; + uint64_t timestamp; +}; + +struct Result { + bool ok; + std::vector msg; +}; + +struct SignalDescriptor { + std::string topic; + std::string name; + std::vector unit; +}; + +class MqttDataWrapper final { +public: + // first = device name, second = list of signal descriptors + using DeviceDescriptorType = std::pair>; + MqttDataWrapper() = delete; + + static std::pair parseSampleData(const std::string& json); + static std::pair parseSignalDescriptors(const std::string& topic, const std::string& json); + + static std::string serializeSampleData(const SampleData& data); + static std::string serializeSignalDescriptors(daq::ListObjectPtr> signals); + + static std::string buildTopicFromId(const std::string& globalId); + static std::string buildSignalsTopic(const std::string& deviceId); +}; +} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/MqttMessage.h b/mqtt_streaming_protocol/include/MqttMessage.h index 8f6726da..df6fe528 100644 --- a/mqtt_streaming_protocol/include/MqttMessage.h +++ b/mqtt_streaming_protocol/include/MqttMessage.h @@ -11,11 +11,25 @@ namespace mqtt class MqttMessage { public: - void setToken(int token) + MqttMessage() = default; + MqttMessage(std::string topic, std::vector data, int qos, bool retained) + : topic(topic) + , data(data) + , qos(qos) + , retained(retained) + {} + bool operator==(const MqttMessage &other) const { - this->token = token; + return topic == other.topic && data == other.data && qos == other.qos; } + bool operator!=(const MqttMessage &other) const + { + return !(*this == other); + } + + void setToken(int token) { this->token = token; } + int getToken() { return token; @@ -37,6 +51,11 @@ class MqttMessage return data; } + const std::vector& getData() const + { + return data; + } + std::string getTopic() const { return topic; diff --git a/mqtt_streaming_protocol/include/MqttSettings.h b/mqtt_streaming_protocol/include/MqttSettings.h new file mode 100644 index 00000000..d8cb04f0 --- /dev/null +++ b/mqtt_streaming_protocol/include/MqttSettings.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace Mqtt::Utils::Settings { +struct MqttConnectionSettings { + std::string mqttUrl; + int port; + std::string username; + std::string password; + std::string clientId; +}; +} diff --git a/mqtt_streaming_protocol/src/CMakeLists.txt b/mqtt_streaming_protocol/src/CMakeLists.txt index bb3c764c..87f3d3b2 100644 --- a/mqtt_streaming_protocol/src/CMakeLists.txt +++ b/mqtt_streaming_protocol/src/CMakeLists.txt @@ -1,31 +1,25 @@ -set(BASE_NAME mqtt_streaming_protocol) -set(LIB_NAME ${BASE_NAME}) +set(LIB_NAME mqtt_streaming_protocol) -set(SRC_Cpp MqttAsyncPublisher.cpp - MqttAsyncSubscriber.cpp +set(SRC_Cpp MqttAsyncClient.cpp + MqttDataWrapper.cpp ) -set(SRC_PublicHeaders IMqttBase.h - IMqttPublisher.h - MqttAsyncPublisher.h - IMqttSubscriber.h - MqttAsyncSubscriber.h - MQTTClientType.h +set(SRC_PublicHeaders MqttAsyncClient.h MqttMessage.h + MqttSettings.h + MqttDataWrapper.h ) set(INCLUDE_DIR ../include) prepend_include(${INCLUDE_DIR} SRC_PublicHeaders) - -set(SRC_PrivateHeaders ) +source_group("include" FILES ${SRC_PublicHeaders}) add_library(${LIB_NAME} STATIC ${SRC_Cpp} ${SRC_PublicHeaders} - ${SRC_PrivateHeaders} ) -add_library(${SDK_TARGET_NAMESPACE}::${BASE_NAME} ALIAS ${LIB_NAME}) +add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) if(BUILD_64Bit OR BUILD_ARM) set_target_properties(${LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) @@ -48,8 +42,10 @@ if(PAHO_BUILD_STATIC) set(PAHO_LIB ${PAHO_LIB}-static) endif() -target_link_libraries(${LIB_NAME} PUBLIC ${PAHO_LIB}) -target_link_libraries(${LIB_NAME} PUBLIC rapidjson) +target_link_libraries(${LIB_NAME} PUBLIC ${PAHO_LIB} + daq::opendaq + rapidjson +) if (WIN32) target_link_libraries(${LIB_NAME} PUBLIC diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp new file mode 100644 index 00000000..ab5fd8b2 --- /dev/null +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -0,0 +1,368 @@ +#include "MqttAsyncClient.h" + +namespace mqtt { + +MqttAsyncClient::MqttAsyncClient() : MqttAsyncClient("", "", false) {} + +MqttAsyncClient::MqttAsyncClient(std::string serverUrl, std::string clientId, bool cleanSession) + : clientId(clientId) + , username("") + , password("") + , pendingConnect(false) + , client(nullptr) +{ + setServerURL(serverUrl); + connOpts = MQTTAsync_connectOptions_initializer; + connOpts.cleansession = cleanSession ? 1 : 0; + connOpts.keepAliveInterval = 20; + connOpts.connectTimeout = 5; + connOpts.onSuccess = (MQTTAsync_onSuccess *) &MqttAsyncClient::onConnectSuccess; + connOpts.onFailure = (MQTTAsync_onFailure *) &MqttAsyncClient::onConnectFailure; + connOpts.automaticReconnect = true; + connOpts.minRetryInterval = 1; + connOpts.maxRetryInterval = 10; + connOpts.context = this; + + disconnOpts = MQTTAsync_disconnectOptions_initializer; + disconnOpts.onSuccess = (MQTTAsync_onSuccess *) &MqttAsyncClient::onDisconnectSuccess; + disconnOpts.onFailure = (MQTTAsync_onFailure *) &MqttAsyncClient::onDisconnectFailure; + disconnOpts.context = this; + + createOpts = MQTTAsync_createOptions_initializer; +} + +MqttAsyncClient::~MqttAsyncClient() { + if (client != nullptr) { + MQTTAsync_destroy(&client); + } +} + +bool MqttAsyncClient::connect() { + if (client != nullptr) { + MQTTAsync_destroy(&client); + } + + if (serverUrl.empty() || clientId.empty()) { + return false; + } + + int rc = MQTTAsync_createWithOptions(&client, + serverUrl.c_str(), + clientId.c_str(), + MQTTCLIENT_PERSISTENCE_NONE, + NULL, + &createOpts); + + if (rc != MQTTASYNC_SUCCESS) { + return false; + } + + rc = MQTTAsync_setCallbacks(client, + this, + &MqttAsyncClient::onConnectionLost, + &MqttAsyncClient::onMsgArrived, + &MqttAsyncClient::onDeliveryCompleted); + + if (rc != MQTTASYNC_SUCCESS) { + return false; + } + + rc = MQTTAsync_setConnected(client, this, &MqttAsyncClient::onConnected); + if (rc != MQTTASYNC_SUCCESS) { + return false; + } + + rc = MQTTAsync_connect(client, &connOpts); + if (rc != MQTTASYNC_SUCCESS) { + return false; + } + pendingConnect = true; + return true; +} + +bool MqttAsyncClient::disconnect() { + // It is only the result of the request to disconnect (queuing) + return MQTTAsync_disconnect(client, &disconnOpts) == MQTTASYNC_SUCCESS; +} + +MqttConnectionStatus MqttAsyncClient::isConnected() const { + if (pendingConnect) + return MqttConnectionStatus::pending; + + return MQTTAsync_isConnected(client) ? MqttConnectionStatus::connected + : MqttConnectionStatus::not_connected; +} + +std::lock_guard MqttAsyncClient::getCbLock() { + return std::lock_guard(cbMtx); +} + +void MqttAsyncClient::setUsernamePasswrod(std::string username, std::string password) +{ + this->username = username; + this->password = password; + + connOpts.username = !username.empty() ? username.c_str() : NULL; + connOpts.password = !password.empty() ? password.c_str() : NULL; +} + +bool MqttAsyncClient::publish(const std::string &topic, + void *data, + size_t dataLen, + std::string *err, + int qos, + int *token, + bool retained) +{ + std::string tmpErr; + if (client == nullptr) { + tmpErr = "MQTTAsync is nullptr"; + } + + if (topic.empty()) { + tmpErr = "topic is empty"; + } + + if (qos > 2 || qos < 0) { + tmpErr = "QoS is wrong"; + } + + if (!tmpErr.empty()) { + if (err != nullptr) { + *err = std::move(tmpErr); + } + return false; + } + + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + opts.onSuccess = (MQTTAsync_onSuccess *) &MqttAsyncClient::onSendSuccess; + opts.onFailure = (MQTTAsync_onFailure *) &MqttAsyncClient::onSendFailure; + opts.context = this; + pubmsg.payload = data; + pubmsg.payloadlen = (int) dataLen; + pubmsg.qos = qos; + pubmsg.retained = retained ? 1 : 0; + if ((rc = MQTTAsync_sendMessage(client, topic.c_str(), &pubmsg, &opts)) != MQTTASYNC_SUCCESS) { + if (err != nullptr) { + *err = MQTTAsync_strerror(rc); + } + } + if (token != nullptr) { + *token = opts.token; + } + return rc == MQTTASYNC_SUCCESS; +} + +bool MqttAsyncClient::subscribe(std::string topic, int qos) { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + opts.onSuccess = MqttAsyncClient::onSubscribeSuccess; + opts.onFailure = MqttAsyncClient::onSubscribeFailure; + opts.context = this; + if ((rc = MQTTAsync_subscribe(client, topic.c_str(), qos, &opts)) == MQTTASYNC_SUCCESS) { + subscriptions.emplace_back(topic, qos); + } + return rc == MQTTASYNC_SUCCESS; +} + +bool MqttAsyncClient::unsubscribe(std::string topic) { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + opts.onSuccess = (MQTTAsync_onSuccess *) &MqttAsyncClient::onUnsubscribeSuccess; + opts.onFailure = (MQTTAsync_onFailure *) &MqttAsyncClient::onUnsubscribeFailure; + opts.context = this; + int rc = MQTTAsync_unsubscribe(client, topic.c_str(), &opts); + auto it = std::find_if(subscriptions.begin(), + subscriptions.end(), + [&topic](const MqttSubscription &sub) { return sub.topic == topic; }); + if (it != subscriptions.end()) + subscriptions.erase(it); + return rc == MQTTASYNC_SUCCESS; +} + +bool MqttAsyncClient::unsubscribeAll() { + for (auto &sub : subscriptions) { + unsubscribe(sub.topic); + } + subscriptions.clear(); + + return true; +} + +void MqttAsyncClient::setServerURL(std::string serverUrl) { + if (serverUrl != "") { + serverUrl = "tcp://" + serverUrl; + } + this->serverUrl = serverUrl; +} + +void MqttAsyncClient::setClientId(std::string clientId) { + this->clientId = clientId; +} + +std::string MqttAsyncClient::getServerUrl() const { return serverUrl; } + +void MqttAsyncClient::onDeliveryCompleted(void *context, MQTTAsync_token token) +{ + if (context != nullptr) { + auto clienttInst = (MqttAsyncClient *) context; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onDeliveryCompletedCb) + clienttInst->onDeliveryCompletedCb(token); + } +} + +void MqttAsyncClient::onConnected(void *context, char *cause) { + if (context != nullptr) { + auto clienttInst = (MqttAsyncClient *) context; + clienttInst->pendingConnect = false; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onConnectedCb) + clienttInst->onConnectedCb(); + } +} + +void MqttAsyncClient::onConnectionLost(void *context, char *cause) { + (void) cause; + // Reconnect procedure here! + if (context != nullptr) { + auto clienttInst = (MqttAsyncClient *) context; + clienttInst->pendingConnect = false; + } +} + +int MqttAsyncClient::onMsgArrived(void *context, + char *topicName, + int topicLen, + MQTTAsync_message *message) +{ + if (context != nullptr && message != nullptr) { + MqttAsyncClient *client = (MqttAsyncClient *) context; + { + auto lock = client->getCbLock(); + auto it = (topicName != nullptr) ? client->onMsgArrivedCbs.find(topicName) + : client->onMsgArrivedCbs.end(); + if (client->onMsgArrivedCmnCb || it != client->onMsgArrivedCbs.end()) { + mqtt::MqttMessage msg; + msg.setTopic(topicName); + msg.addData((uint8_t *) message->payload, message->payloadlen); + msg.setQos(message->qos); + msg.setRetained(message->retained != 0); + if (client->onMsgArrivedCmnCb) + client->onMsgArrivedCmnCb(*client, msg); + if (it != client->onMsgArrivedCbs.end() && it->second) + it->second(*client, msg); + } + } + } + if (message != nullptr) + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return 1; +} + +void MqttAsyncClient::onSendSuccess(void *context, MQTTAsync_successData *data) +{ + if (context != nullptr && data != nullptr) { + auto clienttInst = (MqttAsyncClient *) context; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onSentCb) + clienttInst->onSentCb(data->token, true); + } +} + +void MqttAsyncClient::onSendFailure(void *context, MQTTAsync_failureData *data) +{ + if (context != nullptr && data != nullptr) { + auto clienttInst = (MqttAsyncClient *) context; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onSentCb) + clienttInst->onSentCb(data->token, false); + } +} + +void MqttAsyncClient::onConnectSuccess(void *context, MQTTAsync_successData *data) +{ + // TODO : check when this is called + if (context != nullptr) { + auto clienttInst = (MqttAsyncClient *) context; + clienttInst->pendingConnect = false; + } +} + +void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData *data) +{ + // TODO : check when this is called + if (context != nullptr) { + auto clienttInst = (MqttAsyncClient *) context; + clienttInst->pendingConnect = false; + } +} + +void MqttAsyncClient::onDisconnectSuccess(void *context, MQTTAsync_successData *data) +{ + // TODO : check when this is called + if (context != nullptr) { + auto clienttInst = (MqttAsyncClient *) context; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onDisconnectCb) + clienttInst->onDisconnectCb(true); + } +} + +void MqttAsyncClient::onDisconnectFailure(void *context, MQTTAsync_failureData *data) +{ + // TODO : check when this is called + if (context != nullptr) { + auto clienttInst = (MqttAsyncClient *) context; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onDisconnectCb) + clienttInst->onDisconnectCb(false); + } +} + +void MqttAsyncClient::onSubscribeSuccess(void *context, MQTTAsync_successData *response) {} + +void MqttAsyncClient::onSubscribeFailure(void *context, MQTTAsync_failureData *response) {} + +void MqttAsyncClient::onUnsubscribeSuccess(void *context, MQTTAsync_successData *response) {} + +void MqttAsyncClient::onUnsubscribeFailure(void *context, MQTTAsync_failureData *response) {} + +void MqttAsyncClient::setConnectedCb(std::function cb) +{ + auto lock = getCbLock(); + onConnectedCb = cb; +} + +void MqttAsyncClient::setMessageArrivedCb(std::string topic, std::function cb) +{ + auto lock = getCbLock(); + onMsgArrivedCbs.insert({topic, cb}); +} + +void MqttAsyncClient::setMessageArrivedCb(std::function cb) +{ + auto lock = getCbLock(); + onMsgArrivedCmnCb = cb; +} + +void MqttAsyncClient::setDisconnectCb(std::function cb) +{ + auto lock = getCbLock(); + onDisconnectCb = cb; +} + +void MqttAsyncClient::setSentCb(std::function cb) +{ + auto lock = getCbLock(); + onSentCb = cb; +} + +void MqttAsyncClient::setDeliveryCompletedCb(std::function cb) +{ + auto lock = getCbLock(); + onDeliveryCompletedCb = cb; +} +} // namespace mqtt diff --git a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp deleted file mode 100644 index 95ee1b3a..00000000 --- a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#include "MqttAsyncPublisher.h" - -#include - -namespace mqtt { - -MqttAsyncPublisher::MqttAsyncPublisher() : - MqttAsyncPublisher("", "", false, false, false, false, "", "", "", "") -{ - -} - -MqttAsyncPublisher::MqttAsyncPublisher(std::string serverUrl, - std::string clientId, - bool cleanSession, - bool enableSSL, - bool useCertificates, - bool verifyServerCert, - std::string trustStorePath, - std::string clientCertPath, - std::string privKeyPath, - std::string privKeyPass) - : IMqttBase(enableSSL, useCertificates, verifyServerCert, trustStorePath, clientCertPath, privKeyPath, privKeyPass) - , penddingConnect(false) -{ - this->serverUrl = serverUrl; - this->clientId = clientId; - this->username = ""; - this->password = ""; - this->connOpts = MQTTAsync_connectOptions_initializer; - this->connOpts.cleansession = cleanSession ? 1 : 0; - this->connOpts.keepAliveInterval = 20; - this->connOpts.connectTimeout = 5; - this->connOpts.onSuccess = (MQTTAsync_onSuccess*) &MqttAsyncPublisher::onConnectSuccess; - this->connOpts.onFailure = (MQTTAsync_onFailure*) &MqttAsyncPublisher::onConnectFailure; - this->connOpts.automaticReconnect = true; - this->connOpts.minRetryInterval = 1; - this->connOpts.maxRetryInterval = 10; - this->createOpts = MQTTAsync_createOptions_initializer; - -#ifdef OPENDAQ_MQTT_MODULE_ENABLE_SSL - this->ssl_opts = MQTTAsync_SSLOptions_initializer; - this->connOpts.ssl = &this->ssl_opts; -#endif - - this->client = nullptr; - setServerURL(serverUrl); -} - -MqttAsyncPublisher::~MqttAsyncPublisher() -{ - std::lock_guard guard(mtx); - if (this->client != nullptr) - { - disconnect(); - MQTTAsync_destroy(&this->client); - } -} - -bool MqttAsyncPublisher::connect() -{ - std::lock_guard guard(mtx); - if (this->client != nullptr) - { - // Signal stop reconnecting - disconnect(); - MQTTAsync_destroy(&this->client); - } - - penddingConnect = true; - setServerURL(this->serverUrl); - int rc = MQTTAsync_createWithOptions(&this->client, this->serverUrl.c_str(), this->clientId.c_str(), MQTTCLIENT_PERSISTENCE_NONE, NULL, &this->createOpts); - - if (rc != MQTTASYNC_SUCCESS) - { - return false; - } - - rc = MQTTAsync_setCallbacks(this->client, this, &MqttAsyncPublisher::connlost, &MqttAsyncPublisher::msgArrived, &MqttAsyncPublisher::deliveryComplete); - if (rc != MQTTASYNC_SUCCESS) - { - return false; - } - - rc = MQTTAsync_setConnected(this->client, this, &MqttAsyncPublisher::connectedCb); - if (rc != MQTTASYNC_SUCCESS) - { - return false; - } - - this->connOpts.context = this; - if ((rc = MQTTAsync_connect(this->client, &this->connOpts)) != MQTTASYNC_SUCCESS) - { - return false; - } - - - return true; -} - -bool MqttAsyncPublisher::disconnect() -{ - std::lock_guard guard(mtx); - return MQTTAsync_disconnect(this->client, NULL) == MQTTASYNC_SUCCESS; -} - -MqttConnectionStatus MqttAsyncPublisher::isConnected() -{ - std::lock_guard guard(mtx); - if (this->penddingConnect) - return MqttConnectionStatus::pending; - return MQTTAsync_isConnected(this->client) ? MqttConnectionStatus::connected : MqttConnectionStatus::not_connected; -} - -void MqttAsyncPublisher::setUsernamePasswrod(std::string username, std::string password) -{ - std::lock_guard guard(mtx); - this->username = username; - this->password = password; - - this->connOpts.username = !username.empty() ? this->username.c_str() : NULL; - this->connOpts.password = !password.empty() ? this->password.c_str() : NULL; -} - -void MqttAsyncPublisher::publishBirthCertificates() -{ -} - -bool MqttAsyncPublisher::publish(const std::string& topic, void* data, size_t dataLen, std::string* err, int qos, int* token, bool retained) -{ - std::lock_guard guard(mtx); - MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - MQTTAsync_message pubmsg = MQTTAsync_message_initializer; - int rc; - opts.onSuccess = (MQTTAsync_onSuccess*) &MqttAsyncPublisher::onSend; - opts.onFailure = (MQTTAsync_onFailure*) &MqttAsyncPublisher::onSendFailure; - opts.context = this; - pubmsg.payload = data; - pubmsg.payloadlen = (int) dataLen; - pubmsg.qos = qos; - pubmsg.retained = retained ? 1 : 0; - if ((rc = MQTTAsync_sendMessage(client, topic.c_str(), &pubmsg, &opts)) != MQTTASYNC_SUCCESS) - { - if (err != nullptr) - { - *err = MQTTAsync_strerror(rc); - } - } - if (token != nullptr) - { - *token = opts.token; - } - return rc == MQTTASYNC_SUCCESS; -} - -void MqttAsyncPublisher::setStateCB(std::function cb) -{ - this->cb = cb; -} - -void MqttAsyncPublisher::setServerURL(std::string serverUrl) -{ - std::lock_guard guard(mtx); - this->serverUrl = serverUrl; - if (serverUrl[0] == ':' || serverUrl == "") - { - this->serverUrl = ""; - return; - } - std::string ssl("ssl://"); - std::string tcp("tcp://"); - // Remove any protocol if available - if ((this->serverUrl.size() >= ssl.size() && std::equal(ssl.begin(), ssl.end(), this->serverUrl.begin()))) - { - this->serverUrl.erase(0, ssl.length()); - } - if ((this->serverUrl.size() >= tcp.size() && std::equal(tcp.begin(), tcp.end(), this->serverUrl.begin()))) - { - this->serverUrl.erase(0, tcp.length()); - } - - if (enableSSL) - { - this->serverUrl = "ssl://" + this->serverUrl; - this->ssl_opts.enableServerCertAuth = this->useCertificates && this->verifyServerCert && trustStorePath != ""; - if (trustStorePath != "") - { - this->ssl_opts.trustStore = trustStorePath.c_str(); - } - else - { - this->ssl_opts.trustStore = nullptr; - } - if (this->useCertificates) - { - this->ssl_opts.keyStore = clientCertPath.c_str(); - this->ssl_opts.privateKey = privKeyPath.c_str(); - } - else - { - this->ssl_opts.keyStore = nullptr; - this->ssl_opts.privateKey = nullptr; - } - } - else - { - this->serverUrl = "tcp://" + this->serverUrl; - } -} - -void MqttAsyncPublisher::setClientId(std::string clientId) -{ - std::lock_guard guard(mtx); - this->clientId = clientId; -} -bool MqttAsyncPublisher::reconnect() -{ - return false; -} -} diff --git a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp deleted file mode 100644 index 491c3f24..00000000 --- a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "MqttAsyncSubscriber.h" - -namespace mqtt { - MqttAsyncSubscriber::MqttAsyncSubscriber() : - MqttAsyncSubscriber("", "", false, false, false, false, "", "", "", "") - { - } - - bool MqttAsyncSubscriber::connect() -{ - std::lock_guard guard(mtx); - if (this->client != nullptr) - { - // Signal stop reconnecting - disconnect(); - MQTTAsync_destroy(&this->client); - } - - penddingConnect = true; - setServerURL(this->serverUrl); - int rc = MQTTAsync_createWithOptions( - &this->client, this->serverUrl.c_str(), this->clientId.c_str(), MQTTCLIENT_PERSISTENCE_NONE, NULL, &this->createOpts); - - if (rc != MQTTASYNC_SUCCESS) - { - return false; - } - - rc = MQTTAsync_setCallbacks( - this->client, this, &MqttAsyncSubscriber::connlost, &MqttAsyncSubscriber::msgArrived, &MqttAsyncSubscriber::deliveryComplete); - if (rc != MQTTASYNC_SUCCESS) - { - return false; - } - - rc = MQTTAsync_setConnected(this->client, this, &MqttAsyncSubscriber::connectedCb); - if (rc != MQTTASYNC_SUCCESS) - { - return false; - } - - this->connOpts.context = this; - if ((rc = MQTTAsync_connect(this->client, &this->connOpts)) != MQTTASYNC_SUCCESS) - { - return false; - } - - return true; -} - -bool MqttAsyncSubscriber::disconnect() -{ - std::lock_guard guard(mtx); - return MQTTAsync_disconnect(this->client, NULL) == MQTTASYNC_SUCCESS; -} - -MqttConnectionStatus MqttAsyncSubscriber::isConnected() -{ - std::lock_guard guard(mtx); - if (this->penddingConnect) - return MqttConnectionStatus::pending; - return MQTTAsync_isConnected(this->client) ? MqttConnectionStatus::connected : MqttConnectionStatus::not_connected; -} - -void MqttAsyncSubscriber::setServerURL(std::string serverUrl) -{ - std::lock_guard guard(mtx); - this->serverUrl = serverUrl; - if (serverUrl[0] == ':' || serverUrl == "") - { - this->serverUrl = ""; - return; - } - std::string ssl("ssl://"); - std::string tcp("tcp://"); - // Remove any protocol if available - if ((this->serverUrl.size() >= ssl.size() && std::equal(ssl.begin(), ssl.end(), this->serverUrl.begin()))) - { - this->serverUrl.erase(0, ssl.length()); - } - if ((this->serverUrl.size() >= tcp.size() && std::equal(tcp.begin(), tcp.end(), this->serverUrl.begin()))) - { - this->serverUrl.erase(0, tcp.length()); - } - - if (enableSSL) - { - this->serverUrl = "ssl://" + this->serverUrl; - this->ssl_opts.enableServerCertAuth = this->useCertificates && this->verifyServerCert && trustStorePath != ""; - if (trustStorePath != "") - { - this->ssl_opts.trustStore = trustStorePath.c_str(); - } - else - { - this->ssl_opts.trustStore = nullptr; - } - if (this->useCertificates) - { - this->ssl_opts.keyStore = clientCertPath.c_str(); - this->ssl_opts.privateKey = privKeyPath.c_str(); - } - else - { - this->ssl_opts.keyStore = nullptr; - this->ssl_opts.privateKey = nullptr; - } - } - else - { - this->serverUrl = "tcp://" + this->serverUrl; - } -} - -void MqttAsyncSubscriber::setClientId(std::string clientId) -{ - std::lock_guard guard(mtx); - this->clientId = clientId; -} - -void MqttAsyncSubscriber::setUsernamePasswrod(std::string username, std::string password) -{ - std::lock_guard guard(mtx); - this->username = username; - this->password = password; - - this->connOpts.username = !username.empty() ? this->username.c_str() : NULL; - this->connOpts.password = !password.empty() ? this->password.c_str() : NULL; -} -} diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp new file mode 100644 index 00000000..fca11201 --- /dev/null +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -0,0 +1,199 @@ +#include "MqttDataWrapper.h" + +#include +#include +#include +#include "rapidjson/writer.h" + +namespace mqtt { + +static const char* TOPIC_ALL_SIGNALS_PREFIX = "openDAQ"; +static const char* DEVICE_SIGNAL_LIST = "$signals"; + +std::pair MqttDataWrapper::parseSampleData(const std::string &json) +{ + std::pair res{{false, {}}, {0.0, 0}}; + Result& status = res.first; + SampleData& data = res.second; + try { + rapidjson::Document jsonDocument; + jsonDocument.Parse(json); + if (jsonDocument.HasParseError()) { + status.msg.emplace_back("Error parsing mqtt payload as JSON"); + return res; + } + + if (jsonDocument.IsObject()) { + + int successCnt = 0; + for (auto it = jsonDocument.MemberBegin(); it != jsonDocument.MemberEnd(); ++it) { + const std::string name = it->name.GetString(); + if (name == "value") { + if (jsonDocument[name].IsDouble() || jsonDocument[name].IsInt() || jsonDocument[name].IsFloat()){ + data.value = jsonDocument[name].GetDouble(); + successCnt++; + } else { + status.msg.emplace_back("Value is not supported."); + } + } else if (name == "timestamp") { + if (jsonDocument[name].IsInt() || jsonDocument[name].IsUint64() || jsonDocument[name].IsInt64()){ + data.timestamp = jsonDocument[name].GetUint64(); + successCnt++; + } else { + status.msg.emplace_back("Value is not supported."); + } + } else { + status.msg.emplace_back(fmt::format("Field \"{}\" is not supported.", name)); + } + } + if (successCnt == 2) { + status.ok = true; + } else { + status.msg.emplace_back("Not all required fields are present."); + + } + } + } + catch (...) { + status.msg.emplace_back("Error deserializing mqtt payload"); + } + return res; +} + +std::string MqttDataWrapper::serializeSampleData(const SampleData &data) +{ + std::string result; + + rapidjson::Document doc; + doc.SetObject(); + doc.AddMember("value", rapidjson::Value(data.value), doc.GetAllocator()); + doc.AddMember("timestamp", rapidjson::Value(data.timestamp), doc.GetAllocator()); + + // Serialize to string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + + result = buffer.GetString(); + + return result; +} + +std::string MqttDataWrapper::serializeSignalDescriptors( + daq::ListObjectPtr > signals) +{ + std::string result; + rapidjson::Document doc; + doc.SetArray(); + + auto& allocator = doc.GetAllocator(); + + for (const auto& signal : signals) { + rapidjson::Value obj(rapidjson::kObjectType); + + // topic + { + std::string topic = buildTopicFromId(signal.getGlobalId().toStdString()); + obj.AddMember("topic", rapidjson::Value(topic.c_str(), allocator), allocator); + } + + // name + { + std::string name = ""; + if (signal.getName().assigned()) + name = signal.getName().toStdString(); + obj.AddMember("name", rapidjson::Value(name.c_str(), allocator), allocator); + } + + // unit + { + auto unit = signal.getDescriptor().getUnit(); + rapidjson::Value unitArray(rapidjson::kArrayType); + + if (unit.assigned()) { + auto addUnitInfo = [&unitArray, &allocator](daq::StringPtr unitInfo) { + if (unitInfo.assigned()) + unitArray.PushBack(rapidjson::Value(unitInfo.toStdString().c_str(), allocator), allocator); + }; + addUnitInfo(unit.getSymbol()); + addUnitInfo(unit.getName()); + addUnitInfo(unit.getQuantity()); + } + obj.AddMember("unit", unitArray, allocator); + } + + doc.PushBack(obj, allocator); + } + + // Serialize to string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + + result = buffer.GetString(); + return result; +} + +std::pair +MqttDataWrapper::parseSignalDescriptors(const std::string& topic, const std::string &json) +{ + std::pair res{{false, {}}, {"", {}}}; + Result& status = res.first; + auto& [deviceName, signalDesc] = res.second; + { + std::vector list; + boost::split(list, topic, boost::is_any_of("/")); + + if (list.size() != 3 + || list[0] != TOPIC_ALL_SIGNALS_PREFIX + || list[2] != DEVICE_SIGNAL_LIST) { + return res; // not a signal list message + } + + deviceName = list[1]; + } + + rapidjson::Document doc; + + if (doc.Parse(json.c_str()).HasParseError() || !doc.IsArray()) { + status.msg.emplace_back(fmt::format("{} - Signal list format is not correct: {}", topic, json)); + return res; + } + + const auto array = doc.GetArray(); + + signalDesc.reserve(array.Size()); + for (const auto& v : array) { + if (v.IsObject()) { + SignalDescriptor sd; + if (v.HasMember("topic") && v["topic"].IsString()) { + sd.topic = v["topic"].GetString(); + } + + if (v.HasMember("name") && v["name"].IsString()) { + sd.name = v["name"].GetString(); + } + + if (v.HasMember("unit") && v["unit"].IsArray()) { + for (auto& u : v["unit"].GetArray()) { + if (u.IsString()) + sd.unit.emplace_back(u.GetString()); + } + } + signalDesc.emplace_back(std::move(sd)); + } + } + status.ok = true; + return res; +} + +std::string MqttDataWrapper::buildTopicFromId(const std::string& globalId) +{ + return (TOPIC_ALL_SIGNALS_PREFIX + globalId); +} + +std::string MqttDataWrapper::buildSignalsTopic(const std::string& deviceId) +{ + return (TOPIC_ALL_SIGNALS_PREFIX + deviceId + "/" + DEVICE_SIGNAL_LIST); +} +} diff --git a/mqtt_streaming_server_module/CMakeLists.txt b/mqtt_streaming_server_module/CMakeLists.txt index ebae7fd6..ecae0cea 100644 --- a/mqtt_streaming_server_module/CMakeLists.txt +++ b/mqtt_streaming_server_module/CMakeLists.txt @@ -1,5 +1,10 @@ cmake_minimum_required(VERSION 3.10) -get_current_folder_name(TARGET_FOLDER_NAME) -project(MQTTModule VERSION 3.4.0 LANGUAGES CXX) +set_cmake_folder_context(TARGET_FOLDER_NAME) + +set(MQTT_SERVER_MODULE_VERSION ${OPENDAQ_PACKAGE_VERSION}) +set(MQTT_SERVER_MODULE_PRJ_NAME "OpenDaqMqttServerModule") + +message(STATUS "${MQTT_SERVER_MODULE_PRJ_NAME} version: ${MQTT_SERVER_MODULE_VERSION}") +project(${MQTT_SERVER_MODULE_PRJ_NAME} VERSION ${MQTT_SERVER_MODULE_VERSION} LANGUAGES C CXX) add_subdirectory(src) diff --git a/mqtt_streaming_server_module/include/mqtt_streaming_server_module/constants.h b/mqtt_streaming_server_module/include/mqtt_streaming_server_module/constants.h new file mode 100644 index 00000000..9cc5512b --- /dev/null +++ b/mqtt_streaming_server_module/include/mqtt_streaming_server_module/constants.h @@ -0,0 +1,31 @@ +#pragma once + +#include "common.h" + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_SERVER_MODULE + +static const char* MODULE_NAME = "OpenDAQMQTTServerModule"; +static const char* MODULE_ID = "OpenDAQMQTTClientModule"; +static constexpr const char* SERVER_ID_AND_CAPABILITY = "OpenDAQMQTT"; + +static constexpr const char* DEFAULT_BROKER_ADDRESS = "127.0.0.1"; +static constexpr uint16_t DEFAULT_PORT = 1883; +static constexpr const char* DEFAULT_USERNAME = ""; +static constexpr const char* DEFAULT_PASSWORD = ""; +static constexpr size_t DEFAULT_MAX_PACKET_READ_COUNT = 1000; +static constexpr uint16_t DEFAULT_POLLING_PERIOD = 20; // milliseconds + +static const char* MQTT_PREFIX = "daq.mqtt"; +static const char* CONNECTION_TYPE = "TCP/IP"; + +static constexpr const char* PROPERTY_NAME_MQTT_BROKER_ADDRESS = "MqttBrokerAddress"; +static constexpr const char* PROPERTY_NAME_MQTT_BROKER_PORT = "MqttBrokerPort"; +static constexpr const char* PROPERTY_NAME_MQTT_USERNAME = "MqttUsername"; +static constexpr const char* PROPERTY_NAME_MQTT_PASSWORD = "MqttPassword"; +static constexpr const char* PROPERTY_NAME_MAX_PACKET_READ_COUNT = "MaxPacketReadCount"; +static constexpr const char* PROPERTY_NAME_POLLING_PERIOD = "StreamingDataPollingPeriod"; + +static const char* TOPIC_ALL_SIGNALS_PREFIX = "openDAQ"; +static const char* DEVICE_SIGNAL_LIST = "$signals"; + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_SERVER_MODULE diff --git a/mqtt_streaming_server_module/include/mqtt_streaming_server_module/mqtt_streaming_server_impl.h b/mqtt_streaming_server_module/include/mqtt_streaming_server_module/mqtt_streaming_server_impl.h index adcc3647..21004054 100644 --- a/mqtt_streaming_server_module/include/mqtt_streaming_server_module/mqtt_streaming_server_impl.h +++ b/mqtt_streaming_server_module/include/mqtt_streaming_server_module/mqtt_streaming_server_impl.h @@ -22,24 +22,14 @@ #include #include #include -//#include #include #include -#include +#include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_SERVER_MODULE -namespace Mqtt::Utils::Settings { - struct MqttConnectionSettings { - std::string mqttUrl; - int port; - std::string username; - std::string password; - std::string clientId; - }; -} - struct ChannelData { std::vector data; std::vector timestamps; @@ -57,29 +47,18 @@ class MqttStreamingServerImpl : public daq::Server static PropertyObjectPtr populateDefaultConfig(const PropertyObjectPtr& config, const ContextPtr& context); protected: - PropertyObjectPtr getDiscoveryConfig() override; void onStopServer() override; - StreamingPtr onGetStreaming(); void connectSignalReaders(); bool isSignalCompatible(const SignalPtr& signal); void addReader(SignalPtr signalToRead); - void removeReader(SignalPtr signalToRead); void stopServerInternal(); - void addSignalsOfComponent(ComponentPtr& component); - void componentAdded(ComponentPtr& sender, CoreEventArgsPtr& eventArgs); - void componentRemoved(ComponentPtr& sender, CoreEventArgsPtr& eventArgs); - void componentUpdated(ComponentPtr& updatedComponent); - void coreEventCallback(ComponentPtr& sender, CoreEventArgsPtr& eventArgs); - void setupMqttPublisher(); void sendData(const std::string& topic, const ChannelData& data, SizeT readAmount); - std::vector prepareJsonMessages(const std::string& topic, const ChannelData& data, SizeT dataAmount); + std::vector prepareJsonMessages(const ChannelData& data, SizeT dataAmount); std::string prepareJsonTopics(); - std::string buildTopicFromId(const std::string& globalId); - std::string buildSignalsTopic(); void sendTopicList(); void readMqttSettings(); @@ -97,13 +76,13 @@ class MqttStreamingServerImpl : public daq::Server LoggerPtr logger; LoggerComponentPtr loggerComponent; - bool serverStopped; + std::atomic serverStopped; size_t maxPacketReadCount; std::chrono::milliseconds processingThreadSleepTime; - mqtt::MqttAsyncPublisher publisher; + mqtt::MqttAsyncClient publisher; Mqtt::Utils::Settings::MqttConnectionSettings connectionSettings; std::mutex readersSync; - bool processingThreadRunning; + std::atomic processingThreadRunning; std::thread processingThread; std::atomic topicsAreSent = false; diff --git a/mqtt_streaming_server_module/src/CMakeLists.txt b/mqtt_streaming_server_module/src/CMakeLists.txt index ad1d9cd4..b78e3e59 100644 --- a/mqtt_streaming_server_module/src/CMakeLists.txt +++ b/mqtt_streaming_server_module/src/CMakeLists.txt @@ -10,6 +10,7 @@ set(SRC_Include common.h module_dll.h mqtt_streaming_server_module_impl.h mqtt_streaming_server_impl.h + constants.h ) prepend_include(mqtt_streaming_server_module SRC_Include) @@ -19,11 +20,16 @@ set(SRC_Srcs module_dll.cpp mqtt_streaming_server_impl.cpp ) +source_group("common" FILES ${MODULE_HEADERS_DIR}/common.h + ${MODULE_HEADERS_DIR}/constants.h +) + source_group("module" FILES ${MODULE_HEADERS_DIR}/mqtt_streaming_server_module_impl.h ${MODULE_HEADERS_DIR}/mqtt_streaming_server_impl.h ${MODULE_HEADERS_DIR}/module_dll.h module_dll.cpp mqtt_streaming_server_module_impl.cpp + mqtt_streaming_server_impl.cpp ) diff --git a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp index c8f11aaf..e4be2872 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -16,28 +16,15 @@ #include -#include "rapidjson/writer.h" -#include "rapidjson/stringbuffer.h" +#include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_SERVER_MODULE using namespace daq; -static constexpr size_t DEFAULT_MAX_PACKET_READ_COUNT = 1000; -static constexpr uint16_t DEFAULT_POLLING_PERIOD = 20; -static constexpr uint16_t DEFAULT_PORT = 1883; -static constexpr const char* DEFAULT_ADDRESS = "127.0.0.1"; -static constexpr const char* DEFAULT_USERNAME = ""; -static constexpr const char* DEFAULT_PASSWORD = ""; -static constexpr const char* SERVER_ID_AND_CAPABILITY = "OpenDAQMQTT"; - -static constexpr const char* PROPERTY_NAME_MQTT_BROKER_URL = "BrokerAddress"; -static constexpr const char* PROPERTY_NAME_MQTT_BROKER_PORT = "MqttBrokerPort"; -static constexpr const char* PROPERTY_NAME_MQTT_USERNAME = "MqttUsername"; -static constexpr const char* PROPERTY_NAME_MQTT_PASSWORD = "MqttPassword"; - -MqttStreamingServerImpl::MqttStreamingServerImpl(const DevicePtr& rootDevice, - const PropertyObjectPtr& config, - const ContextPtr& context) +MqttStreamingServerImpl::MqttStreamingServerImpl(const DevicePtr &rootDevice, + const PropertyObjectPtr &config, + const ContextPtr &context) : Server(SERVER_ID_AND_CAPABILITY, config, rootDevice, context) , signals(List()) , rootDeviceGlobalId(rootDevice.getGlobalId().toStdString()) @@ -45,29 +32,17 @@ MqttStreamingServerImpl::MqttStreamingServerImpl(const DevicePtr& rootDevice, , loggerComponent(logger.getOrAddComponent(id)) , serverStopped(false) , publisher() - , connectionSettings({ "", DEFAULT_PORT, "", "", rootDevice.getGlobalId().toStdString()}) + , connectionSettings({DEFAULT_BROKER_ADDRESS, + DEFAULT_PORT, + DEFAULT_USERNAME, + DEFAULT_PASSWORD, + rootDevice.getGlobalId().toStdString()}) { - auto info = rootDevice.getInfo(); - if (info.hasServerCapability(SERVER_ID_AND_CAPABILITY)) - DAQ_THROW_EXCEPTION(InvalidStateException, fmt::format("Device \"{}\" already has an {} server capability.", info.getName(), SERVER_ID_AND_CAPABILITY)); - readMqttSettings(); - StringPtr path = config.getPropertyValue("Path"); - - ServerCapabilityConfigPtr serverCapabilityStreaming = - ServerCapability(SERVER_ID_AND_CAPABILITY, SERVER_ID_AND_CAPABILITY, ProtocolType::Streaming) - .setPrefix("daq.mqtts") - .setConnectionType("TCP/IP") - .setPort(connectionSettings.port); - - serverCapabilityStreaming.addProperty(StringProperty("Path", path == "/" ? "" : path)); - info.asPtr(true).addServerCapability(serverCapabilityStreaming); - - this->context.getOnCoreEvent() += event(&MqttStreamingServerImpl::coreEventCallback); - - maxPacketReadCount = config.getPropertyValue("MaxPacketReadCount"); - processingThreadSleepTime = std::chrono::milliseconds(config.getPropertyValue("StreamingDataPollingPeriod")); + maxPacketReadCount = config.getPropertyValue(PROPERTY_NAME_MAX_PACKET_READ_COUNT); + processingThreadSleepTime = std::chrono::milliseconds( + config.getPropertyValue(PROPERTY_NAME_POLLING_PERIOD)); buffer.data.resize(maxPacketReadCount); buffer.timestamps.resize(maxPacketReadCount); @@ -82,99 +57,19 @@ MqttStreamingServerImpl::~MqttStreamingServerImpl() stopServerInternal(); } -void MqttStreamingServerImpl::addSignalsOfComponent(ComponentPtr& component) -{ - if (component.supportsInterface()) - { - LOG_I("Added Signal: {};", component.getGlobalId()); - //serverHandler->addSignal(component.asPtr(true)); - } - else if (component.supportsInterface()) - { - auto nestedComponents = component.asPtr().getItems(search::Recursive(search::Any())); - for (const auto& nestedComponent : nestedComponents) - { - if (nestedComponent.supportsInterface()) - { - LOG_I("Added Signal: {};", nestedComponent.getGlobalId()); - //serverHandler->addSignal(nestedComponent.asPtr(true)); - } - } - } -} - -void MqttStreamingServerImpl::componentAdded(ComponentPtr& /*sender*/, CoreEventArgsPtr& eventArgs) -{ - ComponentPtr addedComponent = eventArgs.getParameters().get("Component"); - - auto addedComponentGlobalId = addedComponent.getGlobalId().toStdString(); - if (addedComponentGlobalId.find(rootDeviceGlobalId) != 0) - return; - - LOG_I("Added Component: {};", addedComponentGlobalId); - addSignalsOfComponent(addedComponent); -} - -void MqttStreamingServerImpl::componentRemoved(ComponentPtr& sender, CoreEventArgsPtr& eventArgs) -{ - StringPtr removedComponentLocalId = eventArgs.getParameters().get("Id"); - - auto removedComponentGlobalId = - sender.getGlobalId().toStdString() + "/" + removedComponentLocalId.toStdString(); - if (removedComponentGlobalId.find(rootDeviceGlobalId) != 0) - return; - - LOG_I("Component: {}; is removed", removedComponentGlobalId); - //serverHandler->removeComponentSignals(removedComponentGlobalId); -} - -void MqttStreamingServerImpl::componentUpdated(ComponentPtr& updatedComponent) -{ - auto updatedComponentGlobalId = updatedComponent.getGlobalId().toStdString(); - if (updatedComponentGlobalId.find(rootDeviceGlobalId) != 0) - return; - - LOG_I("Component: {}; is updated", updatedComponentGlobalId); - - // remove all registered signal of updated component since those might be modified or removed - //serverHandler->removeComponentSignals(updatedComponentGlobalId); - - // add updated versions of signals - addSignalsOfComponent(updatedComponent); -} - -void MqttStreamingServerImpl::coreEventCallback(ComponentPtr& sender, CoreEventArgsPtr& eventArgs) -{ - switch (static_cast(eventArgs.getEventId())) - { - case CoreEventId::ComponentAdded: - componentAdded(sender, eventArgs); - break; - case CoreEventId::ComponentRemoved: - componentRemoved(sender, eventArgs); - break; - case CoreEventId::ComponentUpdateEnd: - componentUpdated(sender); - break; - default: - break; - } -} - void MqttStreamingServerImpl::setupMqttPublisher() { publisher.disconnect(); publisher.setServerURL(connectionSettings.mqttUrl); publisher.setClientId(connectionSettings.clientId); publisher.setUsernamePasswrod(connectionSettings.username, connectionSettings.password); - publisher.setOnConnect([this]() { LOG_I("MQTT: Connection established"); }); + publisher.setConnectedCb([this]() { LOG_I("MQTT: Connection established"); }); LOG_I("MQTT: Trying to connect to MQTT broker ({})", connectionSettings.mqttUrl); if (!publisher.connect()) { LOG_E("MQTT: Connection failed"); } - } void MqttStreamingServerImpl::sendData(const std::string& topic, const ChannelData& data, SizeT readAmount) @@ -182,7 +77,7 @@ void MqttStreamingServerImpl::sendData(const std::string& topic, const ChannelDa if (readAmount == 0) return; - const auto jsonMessages = prepareJsonMessages(topic, data, readAmount); + const auto jsonMessages = prepareJsonMessages(data, readAmount); if (publisher.isConnected() == mqtt::MqttConnectionStatus::connected) { for (const auto& jsonMessage : jsonMessages) { std::string err; @@ -194,22 +89,12 @@ void MqttStreamingServerImpl::sendData(const std::string& topic, const ChannelDa } } -std::vector MqttStreamingServerImpl::prepareJsonMessages(const std::string& topic, const ChannelData& data, SizeT dataAmount) +std::vector MqttStreamingServerImpl::prepareJsonMessages(const ChannelData& data, SizeT dataAmount) { std::vector result; for (size_t i = 0; i < dataAmount; ++i) { - rapidjson::Document doc; - doc.SetObject(); - doc.AddMember("value", rapidjson::Value(data.data[i]), doc.GetAllocator()); - doc.AddMember("timestamp", rapidjson::Value(data.timestamps[i]), doc.GetAllocator()); - - // Serialize to string - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - doc.Accept(writer); - - result.emplace_back(buffer.GetString()); + result.emplace_back(mqtt::MqttDataWrapper::serializeSampleData({data.data[i], data.timestamps[i]})); } return result; @@ -217,39 +102,12 @@ std::vector MqttStreamingServerImpl::prepareJsonMessages(const std: std::string MqttStreamingServerImpl::prepareJsonTopics() { - std::string result; - rapidjson::Document doc; - doc.SetArray(); - for (const auto& signal : signals) { - rapidjson::Value topicValue; - topicValue.SetString(buildTopicFromId(signal.getGlobalId().toStdString()).c_str(), doc.GetAllocator()); - doc.PushBack(topicValue, doc.GetAllocator()); - } - - // Serialize to string - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - doc.Accept(writer); - - result = buffer.GetString(); - - - return result; -} - -std::string MqttStreamingServerImpl::buildTopicFromId(const std::string& globalId) -{ - return ("openDAQ" + globalId); -} - -std::string MqttStreamingServerImpl::buildSignalsTopic() -{ - return ("openDAQ" + rootDeviceGlobalId + "/$signals"); + return mqtt::MqttDataWrapper::serializeSignalDescriptors(signals); } void MqttStreamingServerImpl::sendTopicList() { - std::string topic = buildSignalsTopic(); + std::string topic = mqtt::MqttDataWrapper::buildSignalsTopic(rootDeviceGlobalId); auto topicsMessage = prepareJsonTopics(); if (publisher.isConnected() == mqtt::MqttConnectionStatus::connected) { bool status = publisher.publish(topic, (void*) topicsMessage.c_str(), topicsMessage.length(), nullptr, 1, nullptr, true); @@ -263,7 +121,7 @@ void MqttStreamingServerImpl::sendTopicList() void MqttStreamingServerImpl::readMqttSettings() { - connectionSettings.mqttUrl = (std::string)config.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_URL); + connectionSettings.mqttUrl = (std::string)config.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS); connectionSettings.port = config.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT); connectionSettings.username = (std::string)config.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME); connectionSettings.password = (std::string)config.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD); @@ -272,8 +130,8 @@ void MqttStreamingServerImpl::readMqttSettings() void MqttStreamingServerImpl::processingThreadFunc() { - daqNameThread("MqttC2DSread"); - LOG_I("Streaming-to-device read thread started") + daqNameThread("MqttStrmSrvRead"); + LOG_I("Streaming read thread started") while (processingThreadRunning) { { @@ -291,8 +149,10 @@ void MqttStreamingServerImpl::processingThreadFunc() continue; daq::SizeT readAmount = maxPacketReadCount; reader.readWithDomain(buffer.data.data(), buffer.timestamps.data(), &readAmount); - sendData(buildTopicFromId(signals[i].getGlobalId().toStdString()), buffer, readAmount); - + sendData(mqtt::MqttDataWrapper::buildTopicFromId( + signals[i].getGlobalId().toStdString()), + buffer, + readAmount); if (reader.getAvailableCount() > 0) hasPacketsToRead = true; @@ -303,13 +163,13 @@ void MqttStreamingServerImpl::processingThreadFunc() std::this_thread::sleep_for(processingThreadSleepTime); } - LOG_I("Streaming-to-device read thread stopped"); + LOG_I("Streaming read thread stopped"); } void MqttStreamingServerImpl::startProcessingThread() { - assert(!processingThreadRunning); - processingThreadRunning = true; + if (processingThreadRunning.exchange(true)) + return; processingThread = std::thread(&MqttStreamingServerImpl::processingThreadFunc, this); } @@ -325,12 +185,10 @@ void MqttStreamingServerImpl::stopProcessingThread() void MqttStreamingServerImpl::stopServerInternal() { - if (serverStopped) - return; - serverStopped = true; + if (serverStopped.exchange(true)) + return; - this->context.getOnCoreEvent() -= event(&MqttStreamingServerImpl::coreEventCallback); if (const DevicePtr rootDevice = this->rootDeviceRef.assigned() ? this->rootDeviceRef.getRef() : nullptr; rootDevice.assigned() && !rootDevice.isRemoved()) { @@ -420,7 +278,7 @@ PropertyObjectPtr MqttStreamingServerImpl::createDefaultConfig(const ContextPtr& //auto defaultConfig = MqttStreamingServerHandler::createDefaultConfig(); auto defaultConfig = PropertyObject(); - const auto pollingPeriodProp = IntPropertyBuilder("StreamingDataPollingPeriod", DEFAULT_POLLING_PERIOD) + const auto pollingPeriodProp = IntPropertyBuilder(PROPERTY_NAME_POLLING_PERIOD, DEFAULT_POLLING_PERIOD) .setMinValue(1) .setMaxValue(65535) .setDescription("Polling period in milliseconds " @@ -429,7 +287,7 @@ PropertyObjectPtr MqttStreamingServerImpl::createDefaultConfig(const ContextPtr& .build(); defaultConfig.addProperty(pollingPeriodProp); - const auto maxPacketReadCountProp = IntPropertyBuilder("MaxPacketReadCount", DEFAULT_MAX_PACKET_READ_COUNT) + const auto maxPacketReadCountProp = IntPropertyBuilder(PROPERTY_NAME_MAX_PACKET_READ_COUNT, DEFAULT_MAX_PACKET_READ_COUNT) .setMinValue(1) .setDescription("Specifies the size of a pre-allocated packet buffer into " "which packets are dequeued. The size determines the amount of " @@ -439,7 +297,7 @@ PropertyObjectPtr MqttStreamingServerImpl::createDefaultConfig(const ContextPtr& .build(); defaultConfig.addProperty(maxPacketReadCountProp); - const auto url = StringPropertyBuilder(PROPERTY_NAME_MQTT_BROKER_URL, DEFAULT_ADDRESS) + const auto url = StringPropertyBuilder(PROPERTY_NAME_MQTT_BROKER_ADDRESS, DEFAULT_BROKER_ADDRESS) .setDescription("") .build(); defaultConfig.addProperty(url); @@ -461,11 +319,6 @@ PropertyObjectPtr MqttStreamingServerImpl::createDefaultConfig(const ContextPtr& .build(); defaultConfig.addProperty(password); - const auto path = StringPropertyBuilder("Path", "") - .setDescription("") - .build(); - defaultConfig.addProperty(path); - populateDefaultConfigFromProvider(context, defaultConfig); return defaultConfig; } @@ -483,17 +336,6 @@ PropertyObjectPtr MqttStreamingServerImpl::populateDefaultConfig(const PropertyO return defConfig; } -PropertyObjectPtr MqttStreamingServerImpl::getDiscoveryConfig() -{ - auto discoveryConfig = PropertyObject(); - discoveryConfig.addProperty(StringProperty("ServiceName", "_opendaq-streaming-mqtt._tcp.local.")); - discoveryConfig.addProperty(StringProperty("ServiceCap", "OPENDAQ_MQTTS")); - discoveryConfig.addProperty(StringProperty("Path", config.getPropertyValue("Path"))); - discoveryConfig.addProperty(IntProperty("Port", 0)); - discoveryConfig.addProperty(StringProperty("ProtocolVersion", std::to_string(/*GetLatestConfigProtocolVersion()*/0))); - return discoveryConfig; -} - ServerTypePtr MqttStreamingServerImpl::createType(const ContextPtr& context) { return ServerType( @@ -508,29 +350,18 @@ void MqttStreamingServerImpl::onStopServer() stopServerInternal(); } -StreamingPtr MqttStreamingServerImpl::onGetStreaming() -{ - return nullptr; -} - void MqttStreamingServerImpl::addReader(SignalPtr signalToRead) { std::scoped_lock lock(readersSync); signals.pushBack(signalToRead); streamReaders.emplace_back(StreamReaderBuilder() .setSignal(signalToRead) - .setInputPortNotificationMethod(PacketReadyNotification::None) .setValueReadType(SampleType::Float64) .setDomainReadType(SampleType::Int64) .setSkipEvents(true) .build()); } -void MqttStreamingServerImpl::removeReader(SignalPtr signalToRead) -{ - -} - OPENDAQ_DEFINE_CLASS_FACTORY_WITH_INTERFACE( INTERNAL_FACTORY, MqttStreamingServer, daq::IServer, daq::DevicePtr, rootDevice, diff --git a/mqtt_streaming_server_module/src/mqtt_streaming_server_module_impl.cpp b/mqtt_streaming_server_module/src/mqtt_streaming_server_module_impl.cpp index 3506e793..025cc9e4 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_module_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_module_impl.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -8,12 +9,12 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_SERVER_MODULE MqttStreamingServerModule::MqttStreamingServerModule(ContextPtr context) - : Module("OpenDAQMqttStreamingServerModule", + : Module(MODULE_NAME, daq::VersionInfo(MQTT_STREAM_SRV_MODULE_MAJOR_VERSION, MQTT_STREAM_SRV_MODULE_MINOR_VERSION, MQTT_STREAM_SRV_MODULE_PATCH_VERSION), std::move(context), - "OpenDAQMqttStreamingServerModule") + MODULE_ID) { } From c51831fe7ce2cf30f949d998f5a8d7819dd870d1 Mon Sep 17 00:00:00 2001 From: "nikolai.shipilov" Date: Tue, 14 Oct 2025 19:16:35 +0200 Subject: [PATCH 06/55] Fix boost dependencies --- .../src/MqttAsyncClient.cpp | 1 + .../src/MqttDataWrapper.cpp | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index ab5fd8b2..50811a12 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -1,4 +1,5 @@ #include "MqttAsyncClient.h" +#include namespace mqtt { diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index fca11201..27956343 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -1,15 +1,31 @@ #include "MqttDataWrapper.h" -#include #include #include #include "rapidjson/writer.h" +#include +#include +#include + + namespace mqtt { static const char* TOPIC_ALL_SIGNALS_PREFIX = "openDAQ"; static const char* DEVICE_SIGNAL_LIST = "$signals"; +static std::vector split(const std::string& s, char delimiter) +{ + std::vector tokens; + std::string token; + std::istringstream tokenStream(s); + while (std::getline(tokenStream, token, delimiter)) + { + tokens.push_back(token); + } + return tokens; +} + std::pair MqttDataWrapper::parseSampleData(const std::string &json) { std::pair res{{false, {}}, {0.0, 0}}; @@ -142,7 +158,7 @@ MqttDataWrapper::parseSignalDescriptors(const std::string& topic, const std::str auto& [deviceName, signalDesc] = res.second; { std::vector list; - boost::split(list, topic, boost::is_any_of("/")); + list = split(topic, '/'); if (list.size() != 3 || list[0] != TOPIC_ALL_SIGNALS_PREFIX From 150a8336f9423141377b9a9d95b6d8993eec7841 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 15 Oct 2025 10:56:06 +0200 Subject: [PATCH 07/55] mqtt: new MQTT FB for raw data processing (MQTT msg -> signal binary data); new example application --- examples/CMakeLists.txt | 3 +- examples/InputArgs.h | 10 +- examples/ref-dev-mqtt-raw-sub/CMakeLists.txt | 12 ++ .../ref-dev-mqtt-raw-sub/src/CMakeLists.txt | 13 ++ .../src/ref-dev-mqtt-raw-sub.cpp | 89 +++++++++++ .../mqtt_streaming_client_module/constants.h | 2 + .../mqtt_raw_receiver_fb_impl.h | 62 ++++++++ .../src/CMakeLists.txt | 5 +- .../src/mqtt_raw_receiver_fb_impl.cpp | 147 ++++++++++++++++++ .../src/mqtt_streaming_device_impl.cpp | 21 ++- 10 files changed, 360 insertions(+), 4 deletions(-) create mode 100644 examples/ref-dev-mqtt-raw-sub/CMakeLists.txt create mode 100644 examples/ref-dev-mqtt-raw-sub/src/CMakeLists.txt create mode 100644 examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp create mode 100644 mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h create mode 100644 mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d30841eb..0291983f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.16) add_subdirectory(ref-dev-mqtt-pub) -add_subdirectory(ref-dev-mqtt-sub) \ No newline at end of file +add_subdirectory(ref-dev-mqtt-sub) +add_subdirectory(ref-dev-mqtt-raw-sub) diff --git a/examples/InputArgs.h b/examples/InputArgs.h index 8435d576..c21d2da0 100644 --- a/examples/InputArgs.h +++ b/examples/InputArgs.h @@ -78,8 +78,15 @@ class InputArgs return false; } + void setUsageHelp(const std::string& str) + { + usageString = str; + } + void printHelp() const { + if (!usageString.empty()) + std::cout << "Usage: " << usageString << std::endl; std::cout << "Available arguments:" << std::endl; for (const auto& [name, descStruct] : argDescriptions) { @@ -100,4 +107,5 @@ class InputArgs std::vector parsedArgs; std::unordered_map argValues; std::vector positionalArgs; -}; \ No newline at end of file + std::string usageString; +}; diff --git a/examples/ref-dev-mqtt-raw-sub/CMakeLists.txt b/examples/ref-dev-mqtt-raw-sub/CMakeLists.txt new file mode 100644 index 00000000..34a70d6b --- /dev/null +++ b/examples/ref-dev-mqtt-raw-sub/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.16) + +set(EXAMPLE_PROJECT_NAME "ref-dev-mqtt-raw-sub") + +project(${EXAMPLE_PROJECT_NAME} LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +add_subdirectory(src) diff --git a/examples/ref-dev-mqtt-raw-sub/src/CMakeLists.txt b/examples/ref-dev-mqtt-raw-sub/src/CMakeLists.txt new file mode 100644 index 00000000..c213494f --- /dev/null +++ b/examples/ref-dev-mqtt-raw-sub/src/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.16) + +add_compile_definitions(MODULE_PATH="${OPENDAQ_MODULES_DIR}") +add_compile_definitions(APP_NAME="${EXAMPLE_PROJECT_NAME}") + +add_executable(${EXAMPLE_PROJECT_NAME} ref-dev-mqtt-raw-sub.cpp) + +target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq + rapidjson + mqtt_stream_cli_module +) +target_include_directories(${EXAMPLE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../) + diff --git a/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp b/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp new file mode 100644 index 00000000..653921f3 --- /dev/null +++ b/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp @@ -0,0 +1,89 @@ +#include +#include "../../InputArgs.h" +#include +#include +#include + +#include + +using namespace daq; +using namespace daq::modules::mqtt_streaming_client_module; + +int main(int argc, char* argv[]) +{ + InputArgs args; + args.addArg("--help", "Show help message"); + args.addArg("--address", "MQTT broker address", true); + args.setUsageHelp(APP_NAME " [options] ... "); + args.parse(argc, argv); + + if (args.hasArg("--help") || args.hasUnknownArgs()) { + args.printHelp(); + return 0; + } + + std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); + auto topics = args.getPositionalArgs(); + + if (topics.empty()) { + std::cout << "MQTT topics are required." << std::endl; + return -1; + } + const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); + auto brokerDevice = instance.addDevice("daq.mqtt://" + brokerAddress); + auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); + + if (availableDeviceNodes.getCount() == 0) { + std::cout << "No function block available from the device." << std::endl; + return -1; + } + + + for (const auto& [key, value] : availableDeviceNodes) { + std::cout << "Available function block: " << key << std::endl; + } + const std::string fbName = RAW_FB_NAME; + std::cout << "Try to add the " << fbName << std::endl; + + auto config = availableDeviceNodes.get(fbName).createDefaultConfig(); + auto topicList = List(); + for (auto& topic : topics) { + addToList(topicList, std::move(topic)); + } + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + daq::FunctionBlockPtr rawFb = brokerDevice.addFunctionBlock(fbName, config); + + + auto signals = rawFb.getSignals(); + std::map readers; + for (const auto& s : signals) { + readers.emplace(std::pair(s.getName().toStdString(), daq::PacketReader(s))); + } + + std::thread readerThread([readers]() { + while (true) { + for (const auto& pair : readers) { + const auto& reader = pair.second; + while (!reader.getEmpty()) { + auto packet = reader.read(); + const auto eventPacket = packet.asPtrOrNull(); + if (eventPacket.assigned()) { + continue; + } + const auto dataPacket = packet.asPtrOrNull(); + if (dataPacket.assigned()) { + std::string tmp(static_cast(dataPacket.getData()), dataPacket.getDataSize()); + std::cout << pair.first << " - " << tmp << std::endl; + } + + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + }); + readerThread.detach(); + + std::cout << "Press \"enter\" to exit the application..." << std::endl; + std::cin.get(); + return 0; +} diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h index a6efa8af..f2715cbd 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h @@ -28,6 +28,8 @@ static constexpr const char* PROPERTY_NAME_MQTT_PASSWORD = "MqttPassword"; static constexpr const char* PROPERTY_NAME_INIT_DELAY = "InitDelay"; static constexpr const char* PROPERTY_NAME_SIGNAL_LIST = "SignalList"; +static constexpr const char* RAW_FB_NAME = "@rawMqttFb"; + static const char* TOPIC_ALL_SIGNALS = "openDAQ/+/$signals"; static const char* MQTT_LOCAL_DEVICE_ID_PREFIX = "MqttDevice"; diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h new file mode 100644 index 00000000..d3e56684 --- /dev/null +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h @@ -0,0 +1,62 @@ +/* + * Copyright 2022-2025 openDAQ d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include +#include + +#include "MqttAsyncClient.h" + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +class MqttRawReceiverFbImpl final : public FunctionBlock +{ +public: + explicit MqttRawReceiverFbImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const FunctionBlockTypePtr& type, + const StringPtr& localId, + std::shared_ptr subscriber, + const PropertyObjectPtr& config = nullptr); + ~MqttRawReceiverFbImpl() override; + +private: + std::unordered_map outputSignals; + + std::shared_ptr subscriber; + ListObjectPtr subscribedSignals; + + std::mutex sync; + + void createSignals(); + + void createAndSendDataPacket(mqtt::MqttMessage& msg); + + void initProperties(const PropertyObjectPtr& config); + void readProperties(); + + void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); + + std::string buildSignalNameFromTopic(std::string topic) const; + + void subscribeToTopics(); + void unsubscribeFromTopics(); +}; + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/CMakeLists.txt b/mqtt_streaming_client_module/src/CMakeLists.txt index 6897fce8..6968c0cb 100644 --- a/mqtt_streaming_client_module/src/CMakeLists.txt +++ b/mqtt_streaming_client_module/src/CMakeLists.txt @@ -6,6 +6,7 @@ set(SRC_Include common.h mqtt_streaming_client_module_impl.h mqtt_streaming_device_impl.h mqtt_receiver_fb_impl.h + mqtt_raw_receiver_fb_impl.h constants.h ) @@ -13,6 +14,7 @@ set(SRC_Srcs module_dll.cpp mqtt_streaming_client_module_impl.cpp mqtt_streaming_device_impl.cpp mqtt_receiver_fb_impl.cpp + mqtt_raw_receiver_fb_impl.cpp ) prepend_include(${TARGET_FOLDER_NAME} SRC_Include) @@ -32,8 +34,9 @@ source_group("device" FILES ${MODULE_HEADERS_DIR}/mqtt_streaming_device_impl.h ) source_group("functionalBlock" FILES ${MODULE_HEADERS_DIR}/mqtt_receiver_fb_impl.h - dispatch.h mqtt_receiver_fb_impl.cpp + ${MODULE_HEADERS_DIR}/mqtt_raw_receiver_fb_impl.h + mqtt_raw_receiver_fb_impl.cpp ) add_library(${LIB_NAME} SHARED ${SRC_Include} diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp new file mode 100644 index 00000000..8e4007cd --- /dev/null +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -0,0 +1,147 @@ +#include "mqtt_streaming_client_module/constants.h" +#include "opendaq/data_packet_ptr.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +MqttRawReceiverFbImpl::MqttRawReceiverFbImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const FunctionBlockTypePtr& type, + const StringPtr& localId, + std::shared_ptr subscriber, + const PropertyObjectPtr& config) + : FunctionBlock(type, ctx, parent, localId), subscriber(subscriber) +{ + initComponentStatus(); + initProperties(config.assigned() ? config : type.createDefaultConfig()); + createSignals(); + subscribeToTopics(); + + setComponentStatus(ComponentStatus::Ok); +} + +MqttRawReceiverFbImpl::~MqttRawReceiverFbImpl() +{ + unsubscribeFromTopics(); +} + +void MqttRawReceiverFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, + mqtt::MqttMessage& msg) +{ + createAndSendDataPacket(msg); +} + +void MqttRawReceiverFbImpl::initProperties(const PropertyObjectPtr& config) +{ + for (const auto& prop : config.getAllProperties()) + { + const auto propName = prop.getName(); + if (!objPtr.hasProperty(propName)) + { + if (const auto internalProp = prop.asPtrOrNull(true); + internalProp.assigned()) + { + objPtr.addProperty(internalProp.clone()); + } + } + objPtr.setPropertyValue(propName, prop.getValue()); + } + readProperties(); +} + +void MqttRawReceiverFbImpl::readProperties() +{ + auto lock = std::lock_guard(sync); + subscribedSignals = List(); + if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) + { + auto prop = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtrOrNull(); + if (prop.assigned()) + { + for (const auto& topic : prop) + { + auto signalId = topic.asPtr(); + if (signalId.assigned()) + { + LOG_I("Signal in list: {}", signalId.toStdString()); + subscribedSignals.pushBack(signalId); + } + } + } + } +} + +void MqttRawReceiverFbImpl::createAndSendDataPacket(mqtt::MqttMessage& msg) +{ + std::string topic(msg.getTopic()); + + auto lock = std::lock_guard(sync); + auto signalIter = outputSignals.find(topic); + if (signalIter == outputSignals.end()) + { + return; + } + + const auto& signal = signalIter->second; + const auto outputPacket = + BinaryDataPacket(nullptr, signal.getDescriptor(), msg.getData().size()); + memcpy(outputPacket.getData(), msg.getData().data(), msg.getData().size()); + signal.sendPacket(outputPacket); +} + +void MqttRawReceiverFbImpl::createSignals() +{ + auto lock = std::lock_guard(sync); + for (const auto& topic : subscribedSignals) + { + LOG_I("Subscribing to topic: {}", topic); + + const auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::Binary).build(); + outputSignals.emplace( + std::make_pair(topic, createAndAddSignal(buildSignalNameFromTopic(topic), signalDsc))); + } +} + +std::string MqttRawReceiverFbImpl::buildSignalNameFromTopic(std::string topic) const +{ + boost::replace_all(topic, "/", "_"); + topic += "_Mqtt"; + return topic; +} + +void MqttRawReceiverFbImpl::subscribeToTopics() +{ + for (const auto& topic : subscribedSignals) + { + subscriber->setMessageArrivedCb(topic, + std::bind(&MqttRawReceiverFbImpl::onSignalsMessage, + this, + std::placeholders::_1, + std::placeholders::_2)); + auto ok = subscriber->subscribe(topic, 1); + if (!ok) + LOG_W("Failed to subscribe to the topic: {}", topic); + } +} + +void MqttRawReceiverFbImpl::unsubscribeFromTopics() +{ + for (const auto& topic : subscribedSignals) + { + subscriber->setMessageArrivedCb(topic, nullptr); + subscriber->unsubscribe(topic); + } +} +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index f3d8eadc..33b1883b 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -1,4 +1,5 @@ #include "mqtt_streaming_client_module/mqtt_receiver_fb_impl.h" +#include "mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h" #include #include "mqtt_streaming_client_module/constants.h" @@ -154,6 +155,16 @@ DictPtr MqttStreamingDeviceImpl::onGetAvailableFunc fbTypes.set(fbType.getId(), fbType); } + + { + auto defaultConfig = PropertyObject(); + defaultConfig.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, List())); + const auto fbType = FunctionBlockType(RAW_FB_NAME, + RAW_FB_NAME, + "", + defaultConfig); + fbTypes.set(fbType.getId(), fbType); + } return fbTypes; } @@ -165,7 +176,15 @@ FunctionBlockPtr MqttStreamingDeviceImpl::onAddFunctionBlock(const StringPtr& ty if (fbTypes.hasKey(typeId)) { auto fbTypePtr = fbTypes.getOrDefault(typeId); - nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, typeId, subscriber, config); + if (fbTypePtr.getName() == RAW_FB_NAME) + { + nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, typeId, subscriber, config); + } + else + { + nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, typeId, subscriber, config); + } + addNestedFunctionBlock(nestedFunctionBlock); setComponentStatus(ComponentStatus::Ok); } else { From 563a857e6fdd20c244769422b78ff8e0c6d7a033 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 16 Oct 2025 13:06:24 +0200 Subject: [PATCH 08/55] mqtt: new approach for MQTT FB JSON parsing (JSON config) --- .../mqtt_streaming_client_module/constants.h | 1 + .../mqtt_receiver_fb_impl.h | 19 +- .../mqtt_streaming_device_impl.h | 2 +- .../src/mqtt_receiver_fb_impl.cpp | 87 ++-- .../src/mqtt_streaming_device_impl.cpp | 53 +-- .../include/MqttDataWrapper.h | 91 +++- .../src/MqttDataWrapper.cpp | 397 +++++++++++++----- 7 files changed, 426 insertions(+), 224 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h index f2715cbd..6f20f428 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h @@ -29,6 +29,7 @@ static constexpr const char* PROPERTY_NAME_INIT_DELAY = "InitDelay"; static constexpr const char* PROPERTY_NAME_SIGNAL_LIST = "SignalList"; static constexpr const char* RAW_FB_NAME = "@rawMqttFb"; +static constexpr const char* JSON_FB_NAME = "@jsonMqttFb"; static const char* TOPIC_ALL_SIGNALS = "openDAQ/+/$signals"; diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h index 6eec5c1a..e43704a9 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -20,8 +20,10 @@ #include #include #include +#include #include "MqttAsyncClient.h" +#include "MqttDataWrapper.h" BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE @@ -37,27 +39,28 @@ class MqttReceiverFbImpl final : public FunctionBlock ~MqttReceiverFbImpl() override; private: - std::unordered_map outputSignals; - std::unordered_map outputDomainSignals; + mqtt::MqttDataWrapper jsonDataWorker; + std::unordered_map outputSignals; std::shared_ptr subscriber; - DictObjectPtr subscribedSignals; + //DictObjectPtr subscribedSignals; + std::unordered_map subscribedSignals; - std::mutex sync; + mutable std::mutex sync; void createSignals(); void parseMessage(mqtt::MqttMessage& msg); - void createDataPacket(const std::string& topic, double value, UInt timestamp); + void createDataPacket(const std::string& topic, const std::string& json); void initProperties(const PropertyObjectPtr& config); void readProperties(); void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); - std::string buildSignalNameFromTopic(std::string topic) const; - std::string buildDomainSignalNameFromTopic(std::string topic) const; - + std::string buildSignalNameFromTopic(std::string topic, const std::string& signalName) const; + std::string buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName) const; + std::set getSubscribedTopics() const; }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h index f294d782..3e76b8e3 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h @@ -63,7 +63,7 @@ class MqttStreamingDeviceImpl : public Device std::promise connectedPromise; std::future connectedFuture; std::atomic connectedDone{false}; - std::unordered_map> deviceMap; + std::unordered_map deviceMap; // device name -> signal list JSON }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index 644ef574..827905aa 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -1,6 +1,4 @@ #include -#include "opendaq/data_packet_ptr.h" -#include "opendaq/packet_factory.h" #include #include #include @@ -23,6 +21,7 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, std::shared_ptr subscriber, const PropertyObjectPtr& config) : FunctionBlock(type, ctx, parent, localId) + , jsonDataWorker(loggerComponent) , subscriber(subscriber) { initComponentStatus(); @@ -33,7 +32,7 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, initProperties(type.createDefaultConfig()); createSignals(); - for (const auto& topic : subscribedSignals.getKeys()) + for (const auto& topic : getSubscribedTopics()) { subscriber->setMessageArrivedCb(topic, std::bind(&MqttReceiverFbImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); auto ok = subscriber->subscribe(topic, 1); @@ -45,12 +44,13 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, MqttReceiverFbImpl::~MqttReceiverFbImpl() { - for (const auto& topic : subscribedSignals.getKeys()) + for (const auto& topic : getSubscribedTopics()) { subscriber->setMessageArrivedCb(topic, nullptr); subscriber->unsubscribe(topic); } } + void MqttReceiverFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg) { parseMessage(msg); @@ -76,66 +76,42 @@ void MqttReceiverFbImpl::initProperties(const PropertyObjectPtr& config) void MqttReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); - subscribedSignals = Dict(); + subscribedSignals.clear(); if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) { - auto prop = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtrOrNull(); - if (prop.assigned()) { - for (const auto& [topic, descriptor] : prop) { - auto signalId = topic.asPtr(); - if (signalId.assigned()) { - LOG_I("Signal in list: {}", signalId.toStdString()); - subscribedSignals.set(signalId, descriptor); - } + auto signalConfig = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtrOrNull(); + if (signalConfig.assigned()) { + jsonDataWorker.setConfig(signalConfig.toStdString()); + subscribedSignals = jsonDataWorker.extractDescription(); + LOG_I("Signal in list:"); + for (const auto& [signalId, descriptor] : subscribedSignals) { + LOG_I("{} | {}", signalId.topic, signalId.signalName); } } } } -void MqttReceiverFbImpl::createDataPacket(const std::string& topic, double value, UInt timestamp) +void MqttReceiverFbImpl::createDataPacket(const std::string& topic, const std::string& json) { auto lock = std::lock_guard(sync); - auto signalIter = outputSignals.find(topic); - auto dSignalIter = outputDomainSignals.find(topic); - if (signalIter == outputSignals.end() || dSignalIter == outputDomainSignals.end()) { - return; - } - auto signal = signalIter->second; - auto dSignal = dSignalIter->second; - - DataPacketPtr outputDomainPacket = DataPacket(signal.getDomainSignal().getDescriptor(), 1); - std::memcpy(outputDomainPacket.getRawData(), ×tamp, sizeof(timestamp)); - DataPacketPtr outputPacket = DataPacketWithDomain(outputDomainPacket, signal.getDescriptor(), 1); - - auto outputData = reinterpret_cast(outputPacket.getRawData()); - *outputData = value; - signal.sendPacket(outputPacket); - dSignal.sendPacket(outputDomainPacket); + jsonDataWorker.createAndSendDataPacket(topic, json); } void MqttReceiverFbImpl::parseMessage(mqtt::MqttMessage& msg) { std::string topic(msg.getTopic()); std::string jsonObjStr(msg.getData().begin(), msg.getData().end()); - auto [status, data] = mqtt::MqttDataWrapper::parseSampleData(jsonObjStr); - if (status.ok) { - createDataPacket(topic, data.value, data.timestamp); - } else { - for (const auto& s : status.msg) { - LOG_W("Data parsing: {}", s); - } - } + createDataPacket(topic, jsonObjStr); } void MqttReceiverFbImpl::createSignals() { auto lock = std::lock_guard(sync); - for (const auto& [topic, descriptor] : subscribedSignals) + for (const auto& [signalId, descriptor] : subscribedSignals) { - LOG_I("Subscribing to topic: {}", topic); - std::string signalName = topic; - boost::replace_all(signalName, "/", "_"); + LOG_I("Creating signal \"{}\" for topic \"{}\"", signalId.signalName, signalId.topic); + const std::string& topic = signalId.topic; - auto signalDsc = JsonDeserializer().deserialize(descriptor).asPtrOrNull(); + auto signalDsc = descriptor; auto getEpoch = []() ->std::string { const std::time_t epochTime = std::chrono::system_clock::to_time_t(std::chrono::time_point{}); @@ -152,24 +128,35 @@ void MqttReceiverFbImpl::createSignals() .setOrigin(getEpoch()) .setName("Time").build(); - auto refS = outputSignals.emplace(std::make_pair(topic, createAndAddSignal(buildSignalNameFromTopic(topic), signalDsc))).first; - auto refSD = outputDomainSignals.emplace(std::make_pair(topic, createAndAddSignal(buildDomainSignalNameFromTopic(topic), domainSignalDsc, false))).first; - refS->second->setDomainSignal(refSD->second); + auto refS = outputSignals.emplace(std::make_pair(signalId, createAndAddSignal(buildSignalNameFromTopic(topic, signalId.signalName), signalDsc))).first; + refS->second->setDomainSignal(createAndAddSignal(buildDomainSignalNameFromTopic(topic, signalId.signalName), domainSignalDsc, false)); } + jsonDataWorker.setOutputSignals(&outputSignals); } -std::string MqttReceiverFbImpl::buildSignalNameFromTopic(std::string topic) const +std::string MqttReceiverFbImpl::buildSignalNameFromTopic(std::string topic, const std::string& signalName) const { boost::replace_all(topic, "/", "_"); - topic += "_Mqtt"; + topic += "_Mqtt_" + signalName; return topic; } -std::string MqttReceiverFbImpl::buildDomainSignalNameFromTopic(std::string topic) const +std::string MqttReceiverFbImpl::buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName) const { boost::replace_all(topic, "/", "_"); - topic += std::string("_Mqtt") + "_domain"; + topic += std::string("_Mqtt") + "_domain" + signalName; return topic; } +std::set MqttReceiverFbImpl::getSubscribedTopics() const +{ + auto lock = std::lock_guard(sync); + std::set topics; + for (const auto& [signalId, _] : subscribedSignals) + { + topics.emplace(signalId.topic); + } + return topics; +} + END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index 33b1883b..84670a3e 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -108,54 +108,41 @@ void MqttStreamingDeviceImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subs { const std::string signalList(msg.getData().begin(), msg.getData().end()); const std::string topic = msg.getTopic(); - auto [status, data] = mqtt::MqttDataWrapper::parseSignalDescriptors(topic, signalList); - if (status.ok) { - deviceMap.insert({std::move(data.first), std::move(data.second)}); - } else { - for (const auto& s : status.msg) - LOG_W("Data error: {}", s); + std::string deviceName = mqtt::MqttDataWrapper::extractDeviceName(topic); + if (!deviceName.empty()) { + deviceMap.insert({std::move(deviceName), std::move(signalList)}); } } DictPtr MqttStreamingDeviceImpl::onGetAvailableFunctionBlockTypes() { fbTypes = Dict(); - for (const auto& device : deviceMap) + // Add function block types from deviceMap (devices that sent signal lists) + for (const auto& [deviceName, config] : deviceMap) { auto defaultConfig = PropertyObject(); - auto signalDict = Dict(); - for (const auto& signal : device.second) { - auto builder = DataDescriptorBuilder().setSampleType(SampleType::Float64); - if (!signal.name.empty()) - builder.setName(signal.name); - if (!signal.unit.empty()) { - std::string symbol{""}; - std::string name{""}; - std::string quantity{""}; - if (signal.unit.size() > 0) - symbol = signal.unit[0]; - if (signal.unit.size() > 1) - name = signal.unit[1]; - if (signal.unit.size() > 2) - quantity = signal.unit[2]; - builder.setUnit(Unit(symbol, -1, name, quantity)); - } - - const auto serializer = JsonSerializer(); - builder.build().serialize(serializer); - signalDict.set(signal.topic, serializer.getOutput()); - } - defaultConfig.addProperty(DictProperty(PROPERTY_NAME_SIGNAL_LIST, signalDict)); + defaultConfig.addProperty(StringProperty(PROPERTY_NAME_SIGNAL_LIST, config)); - const auto fbType = FunctionBlockType(device.first, - device.first, + const auto fbType = FunctionBlockType(deviceName, + deviceName, "", defaultConfig); - fbTypes.set(fbType.getId(), fbType); } + // Add a function block type for manual JSON configuration + { + auto defaultConfig = PropertyObject(); + defaultConfig.addProperty(StringProperty(PROPERTY_NAME_SIGNAL_LIST, String(""))); + const auto fbType = FunctionBlockType(JSON_FB_NAME, + JSON_FB_NAME, + "", + defaultConfig); + + fbTypes.set(fbType.getId(), fbType); + } + // Add a function block type for raw MQTT messages { auto defaultConfig = PropertyObject(); defaultConfig.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, List())); diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/mqtt_streaming_protocol/include/MqttDataWrapper.h index 8cff6a0d..16924930 100644 --- a/mqtt_streaming_protocol/include/MqttDataWrapper.h +++ b/mqtt_streaming_protocol/include/MqttDataWrapper.h @@ -1,44 +1,99 @@ #pragma once #include +#include #include #include #include +#include #include +#include #include -namespace mqtt { +namespace mqtt +{ -struct SampleData { +struct SampleData +{ double value; uint64_t timestamp; }; -struct Result { - bool ok; - std::vector msg; +struct SignalId +{ + std::string topic; + std::string signalName; + + bool operator==(const SignalId& other) const noexcept + { + return topic == other.topic && signalName == other.signalName; + } }; -struct SignalDescriptor { - std::string topic; - std::string name; - std::vector unit; +struct DataPackets +{ + daq::DataPacketPtr dataPacket; + daq::DataPacketPtr domainDataPacket; }; -class MqttDataWrapper final { +struct MqttMsgDescriptor +{ + std::string signalName; + std::string valueFieldName; // Value + std::string tsFieldName; // Timestamp +}; + +class MqttDataWrapper final +{ public: - // first = device name, second = list of signal descriptors - using DeviceDescriptorType = std::pair>; - MqttDataWrapper() = delete; + MqttDataWrapper(daq::LoggerComponentPtr loggerComponent); - static std::pair parseSampleData(const std::string& json); - static std::pair parseSignalDescriptors(const std::string& topic, const std::string& json); + static std::string extractDeviceName(const std::string& topic); static std::string serializeSampleData(const SampleData& data); - static std::string serializeSignalDescriptors(daq::ListObjectPtr> signals); + static std::string serializeSignalDescriptors( + daq::ListObjectPtr> signals); + + static std::string buildTopicFromId(const std::string& globalId); + static std::string buildSignalsTopic(const std::string& deviceId); - static std::string buildTopicFromId(const std::string& globalId); - static std::string buildSignalsTopic(const std::string& deviceId); + void setConfig(const std::string& config); + std::unordered_map extractDescription(); + void setOutputSignals(std::unordered_map* const outputSignals); + void createAndSendDataPacket(const std::string& topic, const std::string& json); + +private: + std::string config; + + daq::LoggerComponentPtr loggerComponent; + // {topic, signalName} : daq::signal + std::unordered_map* outputSignals = nullptr; + // topic : MqttMsgDescriptor; used for description how to extract data from sample json\ + // each topic message can contain multiple signals + std::unordered_map> topicDescriptors; + + std::vector> extractDataSamples( + const std::string& topic, const MqttMsgDescriptor& msgDescriptor, const std::string& json); + void sendDataSamples(const SignalId& signalId, const DataPackets& dataPackets); + DataPackets buildDataPackets(const SignalId& signalId, double value, uint64_t timestamp); + daq::UnitPtr extractSignalUnit(const rapidjson::Value& signalObj); + std::string extractValueFieldName(const rapidjson::Value& signalObj); + std::string extractTimestampFieldName(const rapidjson::Value& signalObj); + std::string extractFieldName(const rapidjson::Value& signalObj, const std::string& field); }; } // namespace mqtt + +namespace std +{ + +template <> struct hash +{ + std::size_t operator()(const mqtt::SignalId& id) const noexcept + { + std::size_t h1 = std::hash{}(id.topic); + std::size_t h2 = std::hash{}(id.signalName); + return h1 ^ (h2 << 1); + } +}; +} // namespace std diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index fca11201..5e0b2bbd 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -1,66 +1,27 @@ #include "MqttDataWrapper.h" +#include "rapidjson/writer.h" #include +#include +#include +#include #include #include -#include "rapidjson/writer.h" -namespace mqtt { +#include + +namespace mqtt +{ static const char* TOPIC_ALL_SIGNALS_PREFIX = "openDAQ"; static const char* DEVICE_SIGNAL_LIST = "$signals"; -std::pair MqttDataWrapper::parseSampleData(const std::string &json) +MqttDataWrapper::MqttDataWrapper(daq::LoggerComponentPtr loggerComponent) + : loggerComponent(loggerComponent) { - std::pair res{{false, {}}, {0.0, 0}}; - Result& status = res.first; - SampleData& data = res.second; - try { - rapidjson::Document jsonDocument; - jsonDocument.Parse(json); - if (jsonDocument.HasParseError()) { - status.msg.emplace_back("Error parsing mqtt payload as JSON"); - return res; - } - - if (jsonDocument.IsObject()) { - - int successCnt = 0; - for (auto it = jsonDocument.MemberBegin(); it != jsonDocument.MemberEnd(); ++it) { - const std::string name = it->name.GetString(); - if (name == "value") { - if (jsonDocument[name].IsDouble() || jsonDocument[name].IsInt() || jsonDocument[name].IsFloat()){ - data.value = jsonDocument[name].GetDouble(); - successCnt++; - } else { - status.msg.emplace_back("Value is not supported."); - } - } else if (name == "timestamp") { - if (jsonDocument[name].IsInt() || jsonDocument[name].IsUint64() || jsonDocument[name].IsInt64()){ - data.timestamp = jsonDocument[name].GetUint64(); - successCnt++; - } else { - status.msg.emplace_back("Value is not supported."); - } - } else { - status.msg.emplace_back(fmt::format("Field \"{}\" is not supported.", name)); - } - } - if (successCnt == 2) { - status.ok = true; - } else { - status.msg.emplace_back("Not all required fields are present."); - - } - } - } - catch (...) { - status.msg.emplace_back("Error deserializing mqtt payload"); - } - return res; } -std::string MqttDataWrapper::serializeSampleData(const SampleData &data) +std::string MqttDataWrapper::serializeSampleData(const SampleData& data) { std::string result; @@ -80,49 +41,50 @@ std::string MqttDataWrapper::serializeSampleData(const SampleData &data) } std::string MqttDataWrapper::serializeSignalDescriptors( - daq::ListObjectPtr > signals) + daq::ListObjectPtr> signals) { std::string result; rapidjson::Document doc; - doc.SetArray(); - - auto& allocator = doc.GetAllocator(); + doc.SetObject(); - for (const auto& signal : signals) { - rapidjson::Value obj(rapidjson::kObjectType); + auto& alc = doc.GetAllocator(); - // topic - { - std::string topic = buildTopicFromId(signal.getGlobalId().toStdString()); - obj.AddMember("topic", rapidjson::Value(topic.c_str(), allocator), allocator); - } - - // name - { - std::string name = ""; - if (signal.getName().assigned()) - name = signal.getName().toStdString(); - obj.AddMember("name", rapidjson::Value(name.c_str(), allocator), allocator); - } + for (const auto& signal : signals) + { + rapidjson::Value signalValueObj(rapidjson::kObjectType); + signalValueObj.AddMember("Value", rapidjson::Value("value", alc), alc); + signalValueObj.AddMember("Timestamp", rapidjson::Value("timestamp", alc), alc); // unit + rapidjson::Value unitArray(rapidjson::kArrayType); { auto unit = signal.getDescriptor().getUnit(); - rapidjson::Value unitArray(rapidjson::kArrayType); - - if (unit.assigned()) { - auto addUnitInfo = [&unitArray, &allocator](daq::StringPtr unitInfo) { + if (unit.assigned()) + { + auto addUnitInfo = [&unitArray, &alc](daq::StringPtr unitInfo) + { if (unitInfo.assigned()) - unitArray.PushBack(rapidjson::Value(unitInfo.toStdString().c_str(), allocator), allocator); + unitArray.PushBack(rapidjson::Value(unitInfo.toStdString().c_str(), alc), + alc); }; addUnitInfo(unit.getSymbol()); addUnitInfo(unit.getName()); addUnitInfo(unit.getQuantity()); } - obj.AddMember("unit", unitArray, allocator); } - - doc.PushBack(obj, allocator); + signalValueObj.AddMember("Unit", unitArray, alc); + + rapidjson::Value signalObj(rapidjson::kObjectType); + signalObj.AddMember(rapidjson::Value(signal.getName().toStdString().c_str(), alc), + signalValueObj, + alc); + rapidjson::Value topicArray(rapidjson::kArrayType); + topicArray.PushBack(signalObj, alc); + + doc.AddMember(rapidjson::Value(buildTopicFromId(signal.getGlobalId().toStdString()).c_str(), + alc), + topicArray, + alc); } // Serialize to string @@ -134,66 +96,273 @@ std::string MqttDataWrapper::serializeSignalDescriptors( return result; } -std::pair -MqttDataWrapper::parseSignalDescriptors(const std::string& topic, const std::string &json) +std::string MqttDataWrapper::extractDeviceName(const std::string& topic) { - std::pair res{{false, {}}, {"", {}}}; - Result& status = res.first; - auto& [deviceName, signalDesc] = res.second; - { - std::vector list; - boost::split(list, topic, boost::is_any_of("/")); - if (list.size() != 3 - || list[0] != TOPIC_ALL_SIGNALS_PREFIX - || list[2] != DEVICE_SIGNAL_LIST) { - return res; // not a signal list message - } + std::vector list; + boost::split(list, topic, boost::is_any_of("/")); - deviceName = list[1]; + if (list.size() != 3 || list[0] != TOPIC_ALL_SIGNALS_PREFIX || list[2] != DEVICE_SIGNAL_LIST) + { + return ""; // not a signal list message } + return list[1]; +} + +std::string MqttDataWrapper::buildTopicFromId(const std::string& globalId) +{ + return (TOPIC_ALL_SIGNALS_PREFIX + globalId); +} + +std::string MqttDataWrapper::buildSignalsTopic(const std::string& deviceId) +{ + return (TOPIC_ALL_SIGNALS_PREFIX + deviceId + "/" + DEVICE_SIGNAL_LIST); +} + +void MqttDataWrapper::setConfig(const std::string& config) +{ + this->config = config; +} + +std::unordered_map MqttDataWrapper::extractDescription() +{ + std::unordered_map result; rapidjson::Document doc; + topicDescriptors.clear(); - if (doc.Parse(json.c_str()).HasParseError() || !doc.IsArray()) { - status.msg.emplace_back(fmt::format("{} - Signal list format is not correct: {}", topic, json)); - return res; + if (doc.Parse(config.c_str()).HasParseError()) + { + LOG_E("The JSON config has wrong format"); + return result; } - const auto array = doc.GetArray(); + for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) + { + const rapidjson::Value& array = it->value; + const std::string topic = it->name.GetString(); + if (!array.IsArray()) + { + LOG_W("Wrong description for \"{}\" topic. Skip!", topic); + continue; + } - signalDesc.reserve(array.Size()); - for (const auto& v : array) { - if (v.IsObject()) { - SignalDescriptor sd; - if (v.HasMember("topic") && v["topic"].IsString()) { - sd.topic = v["topic"].GetString(); - } + std::vector msgDescriptors; + // Each topic array contains one or more signal objects + for (const auto& elem : array.GetArray()) + { + if (!elem.IsObject()) + continue; + + + + // Iterate over signals inside this element + for (auto sigIt = elem.MemberBegin(); sigIt != elem.MemberEnd(); ++sigIt) + { + mqtt::SignalId SignalId{.topic = topic, .signalName = sigIt->name.GetString()}; - if (v.HasMember("name") && v["name"].IsString()) { - sd.name = v["name"].GetString(); + const rapidjson::Value& signalObj = sigIt->value; + + auto unit = extractSignalUnit(signalObj); + std::string valueFieldName = extractValueFieldName(signalObj); + std::string tsFieldName = extractTimestampFieldName(signalObj); + + msgDescriptors.emplace_back(MqttMsgDescriptor{SignalId.signalName, + std::move(valueFieldName), + std::move(tsFieldName)}); + + auto dataDescBdr = + daq::DataDescriptorBuilder().setSampleType(daq::SampleType::Float64); + if (unit.assigned()) + dataDescBdr.setUnit(unit); + + result.emplace(std::pair(std::move(SignalId), dataDescBdr.build())); } + } + topicDescriptors.emplace(std::pair(topic, std::move(msgDescriptors))); + } + return result; +} - if (v.HasMember("unit") && v["unit"].IsArray()) { - for (auto& u : v["unit"].GetArray()) { - if (u.IsString()) - sd.unit.emplace_back(u.GetString()); +void MqttDataWrapper::setOutputSignals( + std::unordered_map* const outputSignals) +{ + this->outputSignals = outputSignals; +} + +void MqttDataWrapper::createAndSendDataPacket(const std::string& topic, const std::string& json) +{ + auto msgDescriptors = topicDescriptors.find(topic); + for (const auto& dsc : msgDescriptors->second) + { + auto packets = extractDataSamples(topic, dsc, json); + for (const auto& data : packets) + { + sendDataSamples(data.first, data.second); + } + } +} + +std::vector> MqttDataWrapper::extractDataSamples( + const std::string& topic, const MqttMsgDescriptor& msgDescriptor, const std::string& json) +{ + std::vector> res; + double value = 0.0; + uint64_t ts = 0; + try + { + rapidjson::Document jsonDocument; + jsonDocument.Parse(json); + if (jsonDocument.HasParseError()) + { + LOG_E("Error parsing mqtt payload as JSON"); + return res; + } + + if (jsonDocument.IsObject()) + { + + int successCnt = 0; + for (auto it = jsonDocument.MemberBegin(); it != jsonDocument.MemberEnd(); ++it) + { + const std::string name = it->name.GetString(); + if (name == msgDescriptor.valueFieldName) + { + if (jsonDocument[name].IsDouble() || jsonDocument[name].IsInt() || + jsonDocument[name].IsFloat()) + { + value = jsonDocument[name].GetDouble(); + successCnt++; + } + else + { + LOG_W("Value is not supported."); + } } + else if (name == msgDescriptor.tsFieldName) + { + if (jsonDocument[name].IsInt() || jsonDocument[name].IsUint64() || + jsonDocument[name].IsInt64()) + { + ts = jsonDocument[name].GetUint64(); + successCnt++; + } + else + { + LOG_W("Value is not supported."); + } + } + else + { + LOG_T("Field \"{}\" is not supported.", name); + } + } + if (successCnt != 2) + { + LOG_W("Not all required fields are present."); + } + else + { + // TODO : value [1, 2, 3, ...] support + SignalId signalId{topic, msgDescriptor.signalName}; + auto dataPackets = buildDataPackets(signalId, value, ts); + res.emplace_back(std::move(signalId), std::move(dataPackets)); } - signalDesc.emplace_back(std::move(sd)); } } - status.ok = true; + catch (...) + { + LOG_E("Error deserializing mqtt payload"); + } return res; } -std::string MqttDataWrapper::buildTopicFromId(const std::string& globalId) +void MqttDataWrapper::sendDataSamples(const SignalId& signalId, const DataPackets& dataPackets) { - return (TOPIC_ALL_SIGNALS_PREFIX + globalId); + const auto signalIter = outputSignals->find(signalId); + if (signalIter == outputSignals->end()) + { + return; + } + + auto signal = signalIter->second; + auto domainSignal = signal.getDomainSignal(); + + signal.sendPacket(dataPackets.dataPacket); + if (domainSignal.assigned() && dataPackets.domainDataPacket.assigned()) + signal.getDomainSignal().asPtr().sendPacket( + dataPackets.domainDataPacket); } -std::string MqttDataWrapper::buildSignalsTopic(const std::string& deviceId) +DataPackets +MqttDataWrapper::buildDataPackets(const SignalId& signalId, double value, uint64_t timestamp) { - return (TOPIC_ALL_SIGNALS_PREFIX + deviceId + "/" + DEVICE_SIGNAL_LIST); + DataPackets dataPackets; + const auto signalIter = outputSignals->find(signalId); + if (signalIter == outputSignals->end()) + { + return dataPackets; + } + + auto signal = signalIter->second; + auto domainSignal = signal.getDomainSignal(); + + if (domainSignal.assigned()) + { + dataPackets.domainDataPacket = daq::DataPacket(signal.getDomainSignal().getDescriptor(), 1); + std::memcpy(dataPackets.domainDataPacket.getRawData(), ×tamp, sizeof(timestamp)); + dataPackets.dataPacket = + DataPacketWithDomain(dataPackets.domainDataPacket, signal.getDescriptor(), 1); + } + else + { + dataPackets.dataPacket = DataPacket(signal.getDescriptor(), 1); + } + + auto outputData = reinterpret_cast(dataPackets.dataPacket.getRawData()); + *outputData = value; + + return dataPackets; } + +daq::UnitPtr MqttDataWrapper::extractSignalUnit(const rapidjson::Value& signalObj) +{ + daq::UnitPtr unit; + if (signalObj.HasMember("Unit") && signalObj["Unit"].IsArray()) + { + auto unitBuilder = daq::UnitBuilder(); + auto unitArr = signalObj["Unit"].GetArray(); + if (unitArr.Size() >= 1 && unitArr[0].IsString()) + { + unitBuilder.setSymbol(unitArr[0].GetString()); + } + if (unitArr.Size() >= 2 && unitArr[1].IsString()) + { + unitBuilder.setName(unitArr[1].GetString()); + } + if (unitArr.Size() >= 3 && unitArr[2].IsString()) + { + unitBuilder.setQuantity(unitArr[2].GetString()); + } + unit = unitBuilder.build(); + } + return unit; +} + +std::string MqttDataWrapper::extractValueFieldName(const rapidjson::Value& signalObj) +{ + return extractFieldName(signalObj, "Value"); +} + +std::string MqttDataWrapper::extractTimestampFieldName(const rapidjson::Value& signalObj) +{ + return extractFieldName(signalObj, "Timestamp"); +} +std::string MqttDataWrapper::extractFieldName(const rapidjson::Value& signalObj, + const std::string& field) +{ + return (signalObj.HasMember(field) && signalObj[field].IsString()) + ? signalObj[field].GetString() + : ""; } +} // namespace mqtt From 1c15d515dcc18e4d8502901e5f6c089dae79c825 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 16 Oct 2025 17:20:27 +0200 Subject: [PATCH 09/55] mqtt: new example application for testing of customizable JSON --- examples/CMakeLists.txt | 1 + examples/custom-mqtt-sub/CMakeLists.txt | 12 +++ examples/custom-mqtt-sub/src/CMakeLists.txt | 10 ++ .../custom-mqtt-sub/src/custom-mqtt-sub.cpp | 99 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 examples/custom-mqtt-sub/CMakeLists.txt create mode 100644 examples/custom-mqtt-sub/src/CMakeLists.txt create mode 100644 examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0291983f..dbb73406 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -3,3 +3,4 @@ cmake_minimum_required(VERSION 3.16) add_subdirectory(ref-dev-mqtt-pub) add_subdirectory(ref-dev-mqtt-sub) add_subdirectory(ref-dev-mqtt-raw-sub) +add_subdirectory(custom-mqtt-sub) \ No newline at end of file diff --git a/examples/custom-mqtt-sub/CMakeLists.txt b/examples/custom-mqtt-sub/CMakeLists.txt new file mode 100644 index 00000000..89686ad4 --- /dev/null +++ b/examples/custom-mqtt-sub/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.16) + +set(EXAMPLE_PROJECT_NAME "custom-mqtt-sub") + +project(${EXAMPLE_PROJECT_NAME} LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +add_subdirectory(src) diff --git a/examples/custom-mqtt-sub/src/CMakeLists.txt b/examples/custom-mqtt-sub/src/CMakeLists.txt new file mode 100644 index 00000000..222a08fc --- /dev/null +++ b/examples/custom-mqtt-sub/src/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.16) + +add_compile_definitions(MODULE_PATH="${OPENDAQ_MODULES_DIR}") +add_compile_definitions(APP_NAME="${EXAMPLE_PROJECT_NAME}") + +add_executable(${EXAMPLE_PROJECT_NAME} custom-mqtt-sub.cpp) +target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq + mqtt_stream_cli_module +) +target_include_directories(${EXAMPLE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../) diff --git a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp new file mode 100644 index 00000000..0ce034ea --- /dev/null +++ b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp @@ -0,0 +1,99 @@ +#include +#include "../../InputArgs.h" +#include + +#include +#include +#include + +using namespace daq; +using namespace daq::modules::mqtt_streaming_client_module; + +std::string readFileToString(const std::string& filePath) +{ + std::ifstream file(filePath); + if (!file) + throw std::runtime_error("Failed to open file: " + filePath); + + std::ostringstream buffer; + buffer << file.rdbuf(); // Read the entire file buffer + return buffer.str(); +} + +int main(int argc, char* argv[]) +{ + InputArgs args; + args.addArg("--help", "Show help message"); + args.addArg("--address", "MQTT broker address", true); // If you want to support --address for sub + args.setUsageHelp(APP_NAME " [options] "); + args.parse(argc, argv); + + if (args.hasArg("--help") || args.hasUnknownArgs()) { + args.printHelp(); + return 0; + } + + std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); + auto configFilePath = args.getPositionalArgs(); + if (configFilePath.size() != 1) { + std::cout << "Configuration file path is required." << std::endl; + return -1; + } + + const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); + auto brokerDevice = instance.addDevice("daq.mqtt://" + brokerAddress); + auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); + + if (availableDeviceNodes.getCount() == 0) { + std::cout << "No function block available from the device." << std::endl; + return -1; + } + + for (const auto& [key, value] : availableDeviceNodes) { + std::cout << "Available function block: " << key << std::endl; + } + const std::string fbName = JSON_FB_NAME; + std::cout << "Try to add the " << fbName << std::endl; + + auto config = availableDeviceNodes.get(fbName).createDefaultConfig(); + std::string jsonConfig = readFileToString(configFilePath[0]); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); + daq::FunctionBlockPtr jsonFb = brokerDevice.addFunctionBlock(fbName, config); + + + auto signals = jsonFb.getSignals(); + + using ReaderContainerEntryType = std::pair, StreamReaderPtr>; + using ReaderContainerType = std::vector; + ReaderContainerType readers; + for (const auto& s : signals) + { + readers.emplace_back(ReaderContainerEntryType( + std::pair(s, daq::StreamReader(s, ReadTimeoutType::Any)))); + } + + std::thread readerThread([readers]() { + constexpr int size = 1000; + double samples[size]; + uint64_t timestamps[size]; + while (true) { + for (const auto& [signal, reader] : readers) { + while (!reader.getEmpty()) { + daq::SizeT count = size; + reader.readWithDomain(samples, timestamps, &count); + const std::string sampleUnit = (signal.getDescriptor().assigned() && signal.getDescriptor().getUnit().assigned()) ? + " " + signal.getDescriptor().getUnit().getSymbol().toStdString() : ""; + for (daq::SizeT i = 0; i < count; ++i) + std::cout << signal.getName() << " - Sample: " << samples[i] << sampleUnit << " Timestamp: " << timestamps[i] << std::endl; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + }); + readerThread.detach(); + + std::cout << "Press \"enter\" to exit the application..." << std::endl; + std::cin.get(); + return 0; +} From c65379752f50f1cc6c55cdbeafdd76dbb9541151 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 17 Oct 2025 12:33:09 +0200 Subject: [PATCH 10/55] mqtt: autoparsing of timestamp --- .../src/CMakeLists.txt | 5 +- .../include/timestampConverter.h | 83 +++++++++++++++++++ mqtt_streaming_protocol/src/CMakeLists.txt | 7 +- .../src/MqttDataWrapper.cpp | 9 +- .../src/CMakeLists.txt | 2 + 5 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 mqtt_streaming_protocol/include/timestampConverter.h diff --git a/mqtt_streaming_client_module/src/CMakeLists.txt b/mqtt_streaming_client_module/src/CMakeLists.txt index 6968c0cb..167a75b1 100644 --- a/mqtt_streaming_client_module/src/CMakeLists.txt +++ b/mqtt_streaming_client_module/src/CMakeLists.txt @@ -39,6 +39,8 @@ source_group("functionalBlock" FILES ${MODULE_HEADERS_DIR}/mqtt_receiver_fb_impl mqtt_raw_receiver_fb_impl.cpp ) +find_package(Boost REQUIRED COMPONENTS algorithm) + add_library(${LIB_NAME} SHARED ${SRC_Include} ${SRC_Srcs} ) @@ -50,13 +52,12 @@ endif() target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq PRIVATE mqtt_streaming_protocol - $ + $ ) target_include_directories(${LIB_NAME} PUBLIC $ $ $ - ${Boost_INCLUDE_DIRS} ) opendaq_set_module_properties(${LIB_NAME} ${PROJECT_VERSION_MAJOR}) diff --git a/mqtt_streaming_protocol/include/timestampConverter.h b/mqtt_streaming_protocol/include/timestampConverter.h new file mode 100644 index 00000000..b8e8cce0 --- /dev/null +++ b/mqtt_streaming_protocol/include/timestampConverter.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mqtt::utils +{ +uint64_t numericToMicroseconds(uint64_t val) +{ + // Determine number of digits + int digits = (val == 0) ? 1 : static_cast(std::log10(val)) + 1; + + switch (digits) + { + case 10: + return val * 1'000'000ULL; // seconds → µs + case 13: + return val * 1'000ULL; // milliseconds → µs + case 16: + return val; // microseconds → µs + case 19: + return val / 1'000ULL; // nanoseconds → µs + default: + return 0; // unsupported + } +} + +uint64_t toUnixTicks(const std::string& input) +{ + std::string str = input; + // Trim leading/trailing spaces + str.erase(str.begin(), + std::find_if(str.begin(), + str.end(), + [](unsigned char c) { return !std::isspace(c); })); + str.erase(std::find_if(str.rbegin(), + str.rend(), + [](unsigned char c) { return !std::isspace(c); }) + .base(), + str.end()); + + if (str.empty()) + return 0; // Exception replacement + + // Check if numeric + if (std::all_of(str.begin(), str.end(), ::isdigit)) + { + uint64_t val = 0; + try + { + val = std::stoull(str); + } + catch (...) + { + return 0; + } + return numericToMicroseconds(val); + } + + // Normalize ISO 8601: replace 'T' with space, remove 'Z' + std::replace(str.begin(), str.end(), 'T', ' '); + if (!str.empty() && (str.back() == 'Z' || str.back() == 'z')) + str.pop_back(); + + try + { + // Parse string to ptime + boost::posix_time::ptime pt = + boost::posix_time::time_from_string(str); // format "YYYY-MM-DD HH:MM:SS[.fff]" + boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); + boost::posix_time::time_duration diff = pt - epoch; + return static_cast(diff.total_microseconds()); + } + catch (...) + { + return 0; // Exception replacement + } +} +} // namespace mqtt::utils diff --git a/mqtt_streaming_protocol/src/CMakeLists.txt b/mqtt_streaming_protocol/src/CMakeLists.txt index 87f3d3b2..2e9ccb03 100644 --- a/mqtt_streaming_protocol/src/CMakeLists.txt +++ b/mqtt_streaming_protocol/src/CMakeLists.txt @@ -8,6 +8,7 @@ set(SRC_PublicHeaders MqttAsyncClient.h MqttMessage.h MqttSettings.h MqttDataWrapper.h + timestampConverter.h ) set(INCLUDE_DIR ../include) @@ -15,6 +16,8 @@ prepend_include(${INCLUDE_DIR} SRC_PublicHeaders) source_group("include" FILES ${SRC_PublicHeaders}) +find_package(Boost REQUIRED COMPONENTS algorithm date_time) + add_library(${LIB_NAME} STATIC ${SRC_Cpp} ${SRC_PublicHeaders} ) @@ -29,7 +32,7 @@ endif() target_include_directories(${LIB_NAME} PUBLIC $ $ - $ + $ ) if(PAHO_WITH_SSL) @@ -45,6 +48,8 @@ endif() target_link_libraries(${LIB_NAME} PUBLIC ${PAHO_LIB} daq::opendaq rapidjson + PRIVATE $ + $ ) if (WIN32) diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 5e0b2bbd..c80b9a0b 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -10,6 +10,8 @@ #include +#include + namespace mqtt { @@ -244,7 +246,12 @@ std::vector> MqttDataWrapper::extractDataSample if (jsonDocument[name].IsInt() || jsonDocument[name].IsUint64() || jsonDocument[name].IsInt64()) { - ts = jsonDocument[name].GetUint64(); + ts = utils::numericToMicroseconds(jsonDocument[name].GetUint64()); + successCnt++; + } + else if (jsonDocument[name].IsString()) + { + ts = utils::toUnixTicks(jsonDocument[name].GetString()); successCnt++; } else diff --git a/mqtt_streaming_server_module/src/CMakeLists.txt b/mqtt_streaming_server_module/src/CMakeLists.txt index b78e3e59..461706db 100644 --- a/mqtt_streaming_server_module/src/CMakeLists.txt +++ b/mqtt_streaming_server_module/src/CMakeLists.txt @@ -32,6 +32,7 @@ source_group("module" FILES ${MODULE_HEADERS_DIR}/mqtt_streaming_server_module_i mqtt_streaming_server_impl.cpp ) +find_package(Boost REQUIRED COMPONENTS asio) add_library(${LIB_NAME} SHARED ${SRC_Include} ${SRC_Srcs} @@ -41,6 +42,7 @@ add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq PRIVATE mqtt_streaming_protocol + $ ) target_include_directories(${LIB_NAME} PUBLIC $ From 0f088f214f311a458644440fffe7839e0946f6c2 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 17 Oct 2025 16:59:48 +0200 Subject: [PATCH 11/55] mqtt: support for signal without a domain --- .../custom-mqtt-sub/src/custom-mqtt-sub.cpp | 39 ++++-- .../mqtt_receiver_fb_impl.h | 1 - .../src/mqtt_receiver_fb_impl.cpp | 34 +++--- .../include/MqttDataWrapper.h | 6 + .../src/MqttDataWrapper.cpp | 113 +++++++++++++----- 5 files changed, 138 insertions(+), 55 deletions(-) diff --git a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp index 0ce034ea..9df2c53c 100644 --- a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp +++ b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp @@ -75,17 +75,40 @@ int main(int argc, char* argv[]) std::thread readerThread([readers]() { constexpr int size = 1000; - double samples[size]; - uint64_t timestamps[size]; + + std::vector samplesVec(size); + std::vector timestampsVec(size); + auto samples = samplesVec.data(); + auto timestamps = timestampsVec.data(); + + auto readWithDomain = [samples, timestamps](const daq::GenericSignalPtr<> signal, const daq::StreamReaderPtr reader) + { + daq::SizeT count = size; + reader.readWithDomain(samples, timestamps, &count); + const std::string sampleUnit = (signal.getDescriptor().assigned() && signal.getDescriptor().getUnit().assigned()) ? + " " + signal.getDescriptor().getUnit().getSymbol().toStdString() : ""; + for (daq::SizeT i = 0; i < count; ++i) + std::cout << signal.getName() << " - Sample: " << samples[i] << sampleUnit << " Timestamp: " << timestamps[i] << std::endl; + }; + + auto read = [samples](const daq::GenericSignalPtr<> signal, const daq::StreamReaderPtr reader) + { + daq::SizeT count = size; + reader.read(samples, &count); + const std::string sampleUnit = (signal.getDescriptor().assigned() && signal.getDescriptor().getUnit().assigned()) ? + " " + signal.getDescriptor().getUnit().getSymbol().toStdString() : ""; + for (daq::SizeT i = 0; i < count; ++i) + std::cout << signal.getName() << " - Sample: " << samples[i] << sampleUnit << std::endl; + }; + while (true) { for (const auto& [signal, reader] : readers) { while (!reader.getEmpty()) { - daq::SizeT count = size; - reader.readWithDomain(samples, timestamps, &count); - const std::string sampleUnit = (signal.getDescriptor().assigned() && signal.getDescriptor().getUnit().assigned()) ? - " " + signal.getDescriptor().getUnit().getSymbol().toStdString() : ""; - for (daq::SizeT i = 0; i < count; ++i) - std::cout << signal.getName() << " - Sample: " << samples[i] << sampleUnit << " Timestamp: " << timestamps[i] << std::endl; + if (signal.getDomainSignal().assigned()) + readWithDomain(signal, reader); + else + read(signal, reader); + } } std::this_thread::sleep_for(std::chrono::milliseconds(20)); diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h index e43704a9..5d9ada7d 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -43,7 +43,6 @@ class MqttReceiverFbImpl final : public FunctionBlock std::unordered_map outputSignals; std::shared_ptr subscriber; - //DictObjectPtr subscribedSignals; std::unordered_map subscribedSignals; mutable std::mutex sync; diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index 827905aa..2e9d27b1 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -113,23 +113,25 @@ void MqttReceiverFbImpl::createSignals() auto signalDsc = descriptor; - auto getEpoch = []() ->std::string { - const std::time_t epochTime = std::chrono::system_clock::to_time_t(std::chrono::time_point{}); - char buf[48]; - strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&epochTime)); - return { buf }; - }; - - const auto domainSignalDsc = - DataDescriptorBuilder() - .setSampleType(SampleType::UInt64) - .setUnit(Unit("s", -1, "seconds", "time")) - .setTickResolution(Ratio(1, 1'000'000)) - .setOrigin(getEpoch()) - .setName("Time").build(); - auto refS = outputSignals.emplace(std::make_pair(signalId, createAndAddSignal(buildSignalNameFromTopic(topic, signalId.signalName), signalDsc))).first; - refS->second->setDomainSignal(createAndAddSignal(buildDomainSignalNameFromTopic(topic, signalId.signalName), domainSignalDsc, false)); + if (jsonDataWorker.hasDomainSignal(signalId)) + { + auto getEpoch = []() ->std::string { + const std::time_t epochTime = std::chrono::system_clock::to_time_t(std::chrono::time_point{}); + char buf[48]; + strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&epochTime)); + return { buf }; + }; + + const auto domainSignalDsc = + DataDescriptorBuilder() + .setSampleType(SampleType::UInt64) + .setUnit(Unit("s", -1, "seconds", "time")) + .setTickResolution(Ratio(1, 1'000'000)) + .setOrigin(getEpoch()) + .setName("Time").build(); + refS->second->setDomainSignal(createAndAddSignal(buildDomainSignalNameFromTopic(topic, signalId.signalName), domainSignalDsc, false)); + } } jsonDataWorker.setOutputSignals(&outputSignals); } diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/mqtt_streaming_protocol/include/MqttDataWrapper.h index 16924930..9598e177 100644 --- a/mqtt_streaming_protocol/include/MqttDataWrapper.h +++ b/mqtt_streaming_protocol/include/MqttDataWrapper.h @@ -62,6 +62,7 @@ class MqttDataWrapper final std::unordered_map extractDescription(); void setOutputSignals(std::unordered_map* const outputSignals); void createAndSendDataPacket(const std::string& topic, const std::string& json); + bool hasDomainSignal(const SignalId& signalId) const; private: std::string config; @@ -77,6 +78,11 @@ class MqttDataWrapper final const std::string& topic, const MqttMsgDescriptor& msgDescriptor, const std::string& json); void sendDataSamples(const SignalId& signalId, const DataPackets& dataPackets); DataPackets buildDataPackets(const SignalId& signalId, double value, uint64_t timestamp); + DataPackets buildDataPackets(const SignalId& signalId, double value); + daq::DataPacketPtr buildDomainDataPacket(daq::GenericSignalConfigPtr<> signalConfig, uint64_t timestamp); + daq::DataPacketPtr buildDataPacket(daq::GenericSignalConfigPtr<> signalConfig, + double value, + const daq::DataPacketPtr domainPacket); daq::UnitPtr extractSignalUnit(const rapidjson::Value& signalObj); std::string extractValueFieldName(const rapidjson::Value& signalObj); std::string extractTimestampFieldName(const rapidjson::Value& signalObj); diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index c80b9a0b..0092fd26 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -198,19 +198,38 @@ void MqttDataWrapper::createAndSendDataPacket(const std::string& topic, const st for (const auto& dsc : msgDescriptors->second) { auto packets = extractDataSamples(topic, dsc, json); - for (const auto& data : packets) + for (const auto& [signalId, data] : packets) { - sendDataSamples(data.first, data.second); + sendDataSamples(signalId, data); } } } +bool MqttDataWrapper::hasDomainSignal(const SignalId& signalId) const +{ + auto it = topicDescriptors.find(signalId.topic); + if (it != topicDescriptors.end()) + { + + for (const auto& desc : it->second) + { + if (desc.signalName == signalId.signalName) + { + return !desc.tsFieldName.empty(); + } + } + } + return false; +} + std::vector> MqttDataWrapper::extractDataSamples( const std::string& topic, const MqttMsgDescriptor& msgDescriptor, const std::string& json) { std::vector> res; double value = 0.0; uint64_t ts = 0; + bool hasTS = false; + bool hasValue = false; try { rapidjson::Document jsonDocument; @@ -223,36 +242,34 @@ std::vector> MqttDataWrapper::extractDataSample if (jsonDocument.IsObject()) { - - int successCnt = 0; for (auto it = jsonDocument.MemberBegin(); it != jsonDocument.MemberEnd(); ++it) { const std::string name = it->name.GetString(); - if (name == msgDescriptor.valueFieldName) + if (!msgDescriptor.valueFieldName.empty() && name == msgDescriptor.valueFieldName) { if (jsonDocument[name].IsDouble() || jsonDocument[name].IsInt() || jsonDocument[name].IsFloat()) { value = jsonDocument[name].GetDouble(); - successCnt++; + hasValue = true; } else { LOG_W("Value is not supported."); } } - else if (name == msgDescriptor.tsFieldName) + else if (!msgDescriptor.tsFieldName.empty() && name == msgDescriptor.tsFieldName) { if (jsonDocument[name].IsInt() || jsonDocument[name].IsUint64() || jsonDocument[name].IsInt64()) { ts = utils::numericToMicroseconds(jsonDocument[name].GetUint64()); - successCnt++; + hasTS = true; } else if (jsonDocument[name].IsString()) { ts = utils::toUnixTicks(jsonDocument[name].GetString()); - successCnt++; + hasTS = true; } else { @@ -264,23 +281,28 @@ std::vector> MqttDataWrapper::extractDataSample LOG_T("Field \"{}\" is not supported.", name); } } - if (successCnt != 2) - { - LOG_W("Not all required fields are present."); - } - else - { - // TODO : value [1, 2, 3, ...] support - SignalId signalId{topic, msgDescriptor.signalName}; - auto dataPackets = buildDataPackets(signalId, value, ts); - res.emplace_back(std::move(signalId), std::move(dataPackets)); - } } } catch (...) { LOG_E("Error deserializing mqtt payload"); } + + if (!hasValue) + { + LOG_W("Not all required fields are present."); + } + else + { + // TODO : value [1, 2, 3, ...] support + SignalId signalId{topic, msgDescriptor.signalName}; + DataPackets dataPackets; + if (hasTS) + dataPackets = buildDataPackets(signalId, value, ts); + else + dataPackets = buildDataPackets(signalId, value); + res.emplace_back(std::move(signalId), std::move(dataPackets)); + } return res; } @@ -312,24 +334,55 @@ MqttDataWrapper::buildDataPackets(const SignalId& signalId, double value, uint64 } auto signal = signalIter->second; - auto domainSignal = signal.getDomainSignal(); - if (domainSignal.assigned()) + dataPackets.domainDataPacket = buildDomainDataPacket(signal, timestamp); + dataPackets.dataPacket = buildDataPacket(signal, value, dataPackets.domainDataPacket); + + return dataPackets; +} + +DataPackets +MqttDataWrapper::buildDataPackets(const SignalId& signalId, double value) +{ + DataPackets dataPackets; + const auto signalIter = outputSignals->find(signalId); + if (signalIter == outputSignals->end()) + { + return dataPackets; + } + + auto signal = signalIter->second; + dataPackets.dataPacket = buildDataPacket(signal, value, daq::DataPacketPtr()); + + return dataPackets; +} + +daq::DataPacketPtr MqttDataWrapper::buildDataPacket(daq::GenericSignalConfigPtr<> signalConfig, double value, const daq::DataPacketPtr domainPacket) +{ + daq::DataPacketPtr dataPacket; + if (signalConfig.getDomainSignal().assigned() && domainPacket.assigned()) { - dataPackets.domainDataPacket = daq::DataPacket(signal.getDomainSignal().getDescriptor(), 1); - std::memcpy(dataPackets.domainDataPacket.getRawData(), ×tamp, sizeof(timestamp)); - dataPackets.dataPacket = - DataPacketWithDomain(dataPackets.domainDataPacket, signal.getDescriptor(), 1); + dataPacket = DataPacketWithDomain(domainPacket, signalConfig.getDescriptor(), 1); } else { - dataPackets.dataPacket = DataPacket(signal.getDescriptor(), 1); + dataPacket = DataPacket(signalConfig.getDescriptor(), 1); } - - auto outputData = reinterpret_cast(dataPackets.dataPacket.getRawData()); + auto outputData = reinterpret_cast(dataPacket.getRawData()); *outputData = value; + return dataPacket; +} - return dataPackets; +daq::DataPacketPtr MqttDataWrapper::buildDomainDataPacket(daq::GenericSignalConfigPtr<> signalConfig, uint64_t timestamp) +{ + daq::DataPacketPtr dataPacket; + if (signalConfig.getDomainSignal().assigned()) + { + dataPacket = daq::DataPacket(signalConfig.getDomainSignal().getDescriptor(), 1); + std::memcpy(dataPacket.getRawData(), ×tamp, sizeof(timestamp)); + } + + return dataPacket; } daq::UnitPtr MqttDataWrapper::extractSignalUnit(const rapidjson::Value& signalObj) From ae413be20943dd02ca8c6dce3083e40d14f7344a Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 21 Oct 2025 16:09:52 +0200 Subject: [PATCH 12/55] mqtt: json config example for client side --- examples/custom-mqtt-sub/pub-config.json | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 examples/custom-mqtt-sub/pub-config.json diff --git a/examples/custom-mqtt-sub/pub-config.json b/examples/custom-mqtt-sub/pub-config.json new file mode 100644 index 00000000..e315507a --- /dev/null +++ b/examples/custom-mqtt-sub/pub-config.json @@ -0,0 +1,54 @@ +{ + "openDAQ/RefDev0/IO/AI/RefCh0/Sig/AI0": [ + { + "AI0": { + "Value": "value", + "Timestamp": "timestamp", + "Unit": [ + "V", + "volts", + "voltage" + ] + } + } + ], + "openDAQ/RefDev0/IO/AI/RefCh1/Sig/AI1": [ + { + "AI1": { + "Value": "value", + "Timestamp": "timestamp", + "Unit": [ + "V", + "volts", + "voltage" + ] + } + } + ], + "openDAQ/RefDev0/IO/AI/RefCh2/Sig/AI2": [ + { + "AI2": { + "Value": "value", + "Timestamp": "timestamp", + "Unit": [ + "V", + "volts", + "voltage" + ] + } + } + ], + "openDAQ/RefDev0/IO/AI/RefCh3/Sig/AI3": [ + { + "AI3": { + "Value": "value", + "Timestamp": "timestamp", + "Unit": [ + "V", + "volts", + "voltage" + ] + } + } + ] +} From 67e490bcc0e032608636fc7bfc25ee07cf6cf4c2 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 22 Oct 2025 12:38:46 +0200 Subject: [PATCH 13/55] mqtt: bugfix - moving creating of available FB types to device constructor; Couldn't create device if getAvailableFunctionBlockTypes() has not been called; Now getAvailableFunctionBlockTypes() only returns Dict; --- .../mqtt_streaming_device_impl.h | 3 + .../src/mqtt_streaming_device_impl.cpp | 61 +++++++++++-------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h index 6df9b469..5b6aaa34 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h @@ -47,12 +47,15 @@ class MqttStreamingDeviceImpl : public Device DictPtr onGetAvailableFunctionBlockTypes() override; FunctionBlockPtr onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) override; + void initBaseFunctionalBlocks(); void initMqttSubscriber(); + void buildFunctionBlockTypes(); bool waitForConnection(const int timeoutMs); void receiveSignalTopics(const int timeoutMs); void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); DictObjectPtr fbTypes; + DictObjectPtr baseFbTypes; StringPtr connectionString; EnumerationPtr connectionStatus; diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index b8f2ae95..fbd0a1d2 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -39,7 +39,7 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const Co int initTimeout = config.getPropertyValue(PROPERTY_NAME_INIT_DELAY); initComponentStatus(); - + initBaseFunctionalBlocks(); initMqttSubscriber(); if (!waitForConnection(initTimeout)) { @@ -49,6 +49,8 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const Co LOG_I("MQTT: Connection established"); receiveSignalTopics(initTimeout); + // Build function block types based on received signal lists only once + buildFunctionBlockTypes(); } void MqttStreamingDeviceImpl::removed() @@ -64,6 +66,34 @@ DeviceInfoPtr MqttStreamingDeviceImpl::onGetInfo() return DeviceInfo(connectionString, MQTT_DEVICE_NAME); } + +void MqttStreamingDeviceImpl::initBaseFunctionalBlocks() +{ + baseFbTypes = Dict(); + // Add a function block type for manual JSON configuration + { + auto defaultConfig = PropertyObject(); + defaultConfig.addProperty(StringProperty(PROPERTY_NAME_SIGNAL_LIST, String(""))); + + const auto fbType = FunctionBlockType(JSON_FB_NAME, + JSON_FB_NAME, + "", + defaultConfig); + + baseFbTypes.set(fbType.getId(), fbType); + } + // Add a function block type for raw MQTT messages + { + auto defaultConfig = PropertyObject(); + defaultConfig.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, List())); + const auto fbType = FunctionBlockType(RAW_FB_NAME, + RAW_FB_NAME, + "", + defaultConfig); + baseFbTypes.set(fbType.getId(), fbType); + } +} + void MqttStreamingDeviceImpl::initMqttSubscriber() { subscriber->setServerURL(connectionSettings.mqttUrl); @@ -117,10 +147,11 @@ void MqttStreamingDeviceImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subs deviceMap.insert({std::move(deviceName), std::move(signalList)}); } } - -DictPtr MqttStreamingDeviceImpl::onGetAvailableFunctionBlockTypes() +void MqttStreamingDeviceImpl::buildFunctionBlockTypes() { fbTypes = Dict(); + // Add base function block types + fbTypes = baseFbTypes; // Add function block types from deviceMap (devices that sent signal lists) for (const auto& [deviceName, config] : deviceMap) { @@ -134,28 +165,10 @@ DictPtr MqttStreamingDeviceImpl::onGetAvailableFunc fbTypes.set(fbType.getId(), fbType); } - // Add a function block type for manual JSON configuration - { - auto defaultConfig = PropertyObject(); - defaultConfig.addProperty(StringProperty(PROPERTY_NAME_SIGNAL_LIST, String(""))); - - const auto fbType = FunctionBlockType(JSON_FB_NAME, - JSON_FB_NAME, - "", - defaultConfig); +} - fbTypes.set(fbType.getId(), fbType); - } - // Add a function block type for raw MQTT messages - { - auto defaultConfig = PropertyObject(); - defaultConfig.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, List())); - const auto fbType = FunctionBlockType(RAW_FB_NAME, - RAW_FB_NAME, - "", - defaultConfig); - fbTypes.set(fbType.getId(), fbType); - } +DictPtr MqttStreamingDeviceImpl::onGetAvailableFunctionBlockTypes() +{ return fbTypes; } From 93328b229e939c4cfaa002fe8664ae27d125bc94 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 22 Oct 2025 16:17:38 +0200 Subject: [PATCH 14/55] mqtt: exeption if user tries to add more then one MQTT device --- .../src/mqtt_streaming_client_module_impl.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp index a877ed4c..67a4c00c 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp @@ -50,6 +50,8 @@ DictPtr MqttStreamingClientModule::onGetAvailableDeviceTyp DevicePtr MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionString, const ComponentPtr& parent, const PropertyObjectPtr& config) { + if (device.assigned()) + DAQ_THROW_EXCEPTION(AlreadyExistsException, "Only one MQTT streaming device can be created per module instance."); if (!connectionString.assigned()) DAQ_THROW_EXCEPTION(ArgumentNullException); From a563bf370f0037a8d3cfeecb7d5fe3981b970c29 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 22 Oct 2025 16:18:51 +0200 Subject: [PATCH 15/55] mqtt: correct name for MQTT device type --- .../src/mqtt_streaming_client_module_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp index 67a4c00c..e76136a5 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp @@ -233,7 +233,7 @@ DeviceTypePtr MqttStreamingClientModule::createDeviceType() { return DeviceTypeBuilder() .setId(DaqMqttDeviceTypeId) - .setName("MQTT enabled device") + .setName(MQTT_DEVICE_NAME) .setDescription("Network device connected over MQTT protocol") .setConnectionStringPrefix(DaqMqttDevicePrefix) .setDefaultConfig(createDefaultConfig()) From 9097ab70576a0b32a6135d8b96af438876943765 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 23 Oct 2025 11:20:49 +0200 Subject: [PATCH 16/55] mqtt: topic validation for raw MQTT FB --- .../src/mqtt_raw_receiver_fb_impl.cpp | 13 +++++++++- .../include/MqttDataWrapper.h | 1 + .../src/MqttDataWrapper.cpp | 25 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index 8e4007cd..4d17a9cd 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -1,4 +1,5 @@ #include "mqtt_streaming_client_module/constants.h" +#include "MqttDataWrapper.h" #include "opendaq/data_packet_ptr.h" #include #include @@ -65,15 +66,17 @@ void MqttRawReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); subscribedSignals = List(); + bool isPresent = false; if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) { auto prop = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtrOrNull(); if (prop.assigned()) { + isPresent = true; for (const auto& topic : prop) { auto signalId = topic.asPtr(); - if (signalId.assigned()) + if (mqtt::MqttDataWrapper::validateTopic(signalId, loggerComponent)) { LOG_I("Signal in list: {}", signalId.toStdString()); subscribedSignals.pushBack(signalId); @@ -81,6 +84,14 @@ void MqttRawReceiverFbImpl::readProperties() } } } + if (!isPresent) + { + LOG_W("{} property is missing!", PROPERTY_NAME_SIGNAL_LIST); + } + if (subscribedSignals.empty()) + { + LOG_W("No signals to subscribe to!"); + } } void MqttRawReceiverFbImpl::createAndSendDataPacket(mqtt::MqttMessage& msg) diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/mqtt_streaming_protocol/include/MqttDataWrapper.h index 9598e177..b5afbb1a 100644 --- a/mqtt_streaming_protocol/include/MqttDataWrapper.h +++ b/mqtt_streaming_protocol/include/MqttDataWrapper.h @@ -57,6 +57,7 @@ class MqttDataWrapper final static std::string buildTopicFromId(const std::string& globalId); static std::string buildSignalsTopic(const std::string& deviceId); + static bool validateTopic(const daq::StringPtr topic, const daq::LoggerComponentPtr loggerComponent = nullptr); void setConfig(const std::string& config); std::unordered_map extractDescription(); diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 0092fd26..93ca3f45 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -122,6 +122,31 @@ std::string MqttDataWrapper::buildSignalsTopic(const std::string& deviceId) return (TOPIC_ALL_SIGNALS_PREFIX + deviceId + "/" + DEVICE_SIGNAL_LIST); } +bool MqttDataWrapper::validateTopic(const daq::StringPtr topic, const daq::LoggerComponentPtr loggerComponent) +{ + if (!topic.assigned() || topic.getLength() == 0) + { + if (loggerComponent.assigned()) + LOG_W("Empty topic is not allowed!"); + return false; + } + + std::vector list; + boost::split(list, topic.toStdString(), boost::is_any_of("/")); + + for (const auto& part : list) + { + if (part == "#" || part == "+") + { + if (loggerComponent.assigned()) + LOG_W("Wildcard characters '+' and '#' are not allowed in topic: {}", topic.toStdString()); + return false; + } + } + + return true; +} + void MqttDataWrapper::setConfig(const std::string& config) { this->config = config; From eaa82582ea9e5000413c57d2abaef01ff5da34a9 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 23 Oct 2025 11:30:18 +0200 Subject: [PATCH 17/55] mqtt: renaming and logs --- .../mqtt_raw_receiver_fb_impl.h | 2 +- .../src/mqtt_raw_receiver_fb_impl.cpp | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h index d3e56684..2d47e938 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h @@ -40,7 +40,7 @@ class MqttRawReceiverFbImpl final : public FunctionBlock std::unordered_map outputSignals; std::shared_ptr subscriber; - ListObjectPtr subscribedSignals; + ListObjectPtr topicsForSubscribing; std::mutex sync; diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index 4d17a9cd..6e19e479 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -65,7 +65,7 @@ void MqttRawReceiverFbImpl::initProperties(const PropertyObjectPtr& config) void MqttRawReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); - subscribedSignals = List(); + topicsForSubscribing = List(); bool isPresent = false; if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) { @@ -75,11 +75,11 @@ void MqttRawReceiverFbImpl::readProperties() isPresent = true; for (const auto& topic : prop) { - auto signalId = topic.asPtr(); - if (mqtt::MqttDataWrapper::validateTopic(signalId, loggerComponent)) + auto topicStr = topic.asPtr(); + if (mqtt::MqttDataWrapper::validateTopic(topicStr, loggerComponent)) { - LOG_I("Signal in list: {}", signalId.toStdString()); - subscribedSignals.pushBack(signalId); + LOG_I("Topic in list: {}", topicStr.toStdString()); + topicsForSubscribing.pushBack(topicStr); } } } @@ -88,9 +88,9 @@ void MqttRawReceiverFbImpl::readProperties() { LOG_W("{} property is missing!", PROPERTY_NAME_SIGNAL_LIST); } - if (subscribedSignals.empty()) + if (topicsForSubscribing.empty()) { - LOG_W("No signals to subscribe to!"); + LOG_W("No topics to subscribe to!"); } } @@ -115,7 +115,7 @@ void MqttRawReceiverFbImpl::createAndSendDataPacket(mqtt::MqttMessage& msg) void MqttRawReceiverFbImpl::createSignals() { auto lock = std::lock_guard(sync); - for (const auto& topic : subscribedSignals) + for (const auto& topic : topicsForSubscribing) { LOG_I("Subscribing to topic: {}", topic); @@ -134,7 +134,7 @@ std::string MqttRawReceiverFbImpl::buildSignalNameFromTopic(std::string topic) c void MqttRawReceiverFbImpl::subscribeToTopics() { - for (const auto& topic : subscribedSignals) + for (const auto& topic : topicsForSubscribing) { subscriber->setMessageArrivedCb(topic, std::bind(&MqttRawReceiverFbImpl::onSignalsMessage, @@ -149,10 +149,12 @@ void MqttRawReceiverFbImpl::subscribeToTopics() void MqttRawReceiverFbImpl::unsubscribeFromTopics() { - for (const auto& topic : subscribedSignals) + for (const auto& topic : topicsForSubscribing) { subscriber->setMessageArrivedCb(topic, nullptr); - subscriber->unsubscribe(topic); + auto ok = subscriber->unsubscribe(topic); + if (!ok) + LOG_W("Failed to unsubscribe from the topic: {}", topic); } } END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE From 004d6ee57efd1b7d6a77d5a0a24cec6af064c9be Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 24 Oct 2025 11:32:49 +0200 Subject: [PATCH 18/55] mqtt: tests for Raw MQTT FB --- external/opendaq/CMakeLists.txt | 2 +- .../mqtt_raw_receiver_fb_impl.h | 6 +- .../src/CMakeLists.txt | 4 +- .../src/mqtt_raw_receiver_fb_impl.cpp | 21 +- .../tests/CMakeLists.txt | 2 + .../test_mqtt_streaming_client_module.cpp | 358 +++++++++++++++++- mqtt_streaming_protocol/tests/CMakeLists.txt | 13 + .../tests/MqttAsyncClientWrapper.cpp | 125 ++++++ .../tests/MqttAsyncClientWrapper.h | 27 ++ mqtt_streaming_protocol/tests/Timer.h | 32 ++ .../tests/test_mqtt_streaming_protocol.cpp | 148 +------- 11 files changed, 564 insertions(+), 174 deletions(-) create mode 100644 mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp create mode 100644 mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.h create mode 100644 mqtt_streaming_protocol/tests/Timer.h diff --git a/external/opendaq/CMakeLists.txt b/external/opendaq/CMakeLists.txt index 12768c24..3ae014f1 100644 --- a/external/opendaq/CMakeLists.txt +++ b/external/opendaq/CMakeLists.txt @@ -3,7 +3,7 @@ set(OPENDAQ_ENABLE_TESTS false) FetchContent_Declare( opendaq GIT_REPOSITORY https://github.com/openDAQ/openDAQ.git - GIT_TAG v3.29.0-rc + GIT_TAG origin/release-candidate/3.30 GIT_PROGRESS ON EXCLUDE_FROM_ALL SYSTEM diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h index 2d47e938..c8a594d6 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h @@ -16,17 +16,15 @@ #pragma once #include -#include -#include #include -#include -#include "MqttAsyncClient.h" +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE class MqttRawReceiverFbImpl final : public FunctionBlock { + friend class MqttStreamingClientModuleTest; public: explicit MqttRawReceiverFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, diff --git a/mqtt_streaming_client_module/src/CMakeLists.txt b/mqtt_streaming_client_module/src/CMakeLists.txt index 167a75b1..6f8958aa 100644 --- a/mqtt_streaming_client_module/src/CMakeLists.txt +++ b/mqtt_streaming_client_module/src/CMakeLists.txt @@ -51,8 +51,8 @@ if (MSVC) endif() target_link_libraries(${LIB_NAME} PUBLIC daq::opendaq - PRIVATE mqtt_streaming_protocol - $ + mqtt_streaming_protocol + PRIVATE $ ) target_include_directories(${LIB_NAME} PUBLIC $ diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index 6e19e479..fc89ec05 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -1,19 +1,8 @@ #include "mqtt_streaming_client_module/constants.h" #include "MqttDataWrapper.h" -#include "opendaq/data_packet_ptr.h" #include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE @@ -134,6 +123,11 @@ std::string MqttRawReceiverFbImpl::buildSignalNameFromTopic(std::string topic) c void MqttRawReceiverFbImpl::subscribeToTopics() { + if (!subscriber) + { + LOG_E("The subscriber is null"); + return; + } for (const auto& topic : topicsForSubscribing) { subscriber->setMessageArrivedCb(topic, @@ -149,6 +143,11 @@ void MqttRawReceiverFbImpl::subscribeToTopics() void MqttRawReceiverFbImpl::unsubscribeFromTopics() { + if (!subscriber) + { + LOG_E("The subscriber is null"); + return; + } for (const auto& topic : topicsForSubscribing) { subscriber->setMessageArrivedCb(topic, nullptr); diff --git a/mqtt_streaming_client_module/tests/CMakeLists.txt b/mqtt_streaming_client_module/tests/CMakeLists.txt index 20e1f6f4..90583e3c 100644 --- a/mqtt_streaming_client_module/tests/CMakeLists.txt +++ b/mqtt_streaming_client_module/tests/CMakeLists.txt @@ -10,6 +10,8 @@ add_executable(${TEST_APP} ${TEST_SOURCES} target_link_libraries(${TEST_APP} PRIVATE daq::test_utils ${MODULE_NAME} + mqtt_stream_cli_module + mqtt_streaming_protocol_test_helper ) add_test(NAME ${TEST_APP} diff --git a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp index 5a67f568..6d782a41 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp @@ -1,20 +1,58 @@ -#include +#include "mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h" +#include +#include +#include #include #include -#include -#include +#include +#include +#include +#include +#include -#include #include +#include -#include #include #include +#include #include +#include "MqttAsyncClientWrapper.h" -using MqttStreamingClientModuleTest = testing::Test; using namespace daq; using namespace daq::modules::mqtt_streaming_client_module; +namespace daq::modules::mqtt_streaming_client_module +{ +class MqttStreamingClientModuleTest : public testing::Test +{ +public: + std::unique_ptr obj; + + void onSignalsMessage(mqtt::MqttMessage& msg) + { + mqtt::MqttAsyncClient unused; + obj->onSignalsMessage(unused, msg); + } + + void CreateRawFB(std::vector topics) + { + auto config = PropertyObject(); + config.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, List())); + const auto fbType = FunctionBlockType(RAW_FB_NAME, RAW_FB_NAME, "", config); + auto topicList = List(); + for (auto& topic : topics) + { + addToList(topicList, std::move(topic)); + } + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + obj = std::make_unique(NullContext(), nullptr, fbType, "localId", nullptr, config); + } + + std::string buildTopicName() { + return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()); + } +}; +} // namespace daq::modules::mqtt_streaming_client_module static ModulePtr CreateModule() { @@ -71,13 +109,6 @@ TEST_F(MqttStreamingClientModuleTest, CreateDeviceConnectionStringNull) ASSERT_THROW(device = module.createDevice(nullptr, nullptr), ArgumentNullException); } -// TEST_F(MqttStreamingClientModuleTest, CreateDeviceConnectionFailed) -// { -// auto module = CreateModule(); - -// ASSERT_THROW(module.createDevice("daq.mqtt://127.0.0.1", nullptr), CreateFailedException); -// } - TEST_F(MqttStreamingClientModuleTest, GetAvailableComponentTypes) { const auto module = CreateModule(); @@ -138,6 +169,303 @@ TEST_F(MqttStreamingClientModuleTest, DefaultDeviceConfig) ASSERT_EQ(deviceTypes.getCount(), 1u); ASSERT_TRUE(deviceTypes.hasKey(DaqMqttDeviceTypeId)); - auto pseudoDeviceConfig = deviceTypes.get(DaqMqttDeviceTypeId).createDefaultConfig(); - ASSERT_TRUE(pseudoDeviceConfig.assigned()); + auto defaultConfig = deviceTypes.get(DaqMqttDeviceTypeId).createDefaultConfig(); + ASSERT_TRUE(defaultConfig.assigned()); + + ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 5u); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_PORT)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_USERNAME)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_PASSWORD)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_INIT_DELAY)); + + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_PORT).getValueType(), CoreType::ctInt); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_USERNAME).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_PASSWORD).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_INIT_DELAY).getValueType(), CoreType::ctInt); + + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS), DEFAULT_BROKER_ADDRESS); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT), DEFAULT_PORT); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME), DEFAULT_USERNAME); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD), DEFAULT_PASSWORD); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_INIT_DELAY), DEFAULT_INIT_DELAY); +} + +TEST_F(MqttStreamingClientModuleTest, CreatingDevice) +{ + const auto instance = Instance(); + daq::GenericDevicePtr device; + ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1")); + ASSERT_EQ(device.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); + ASSERT_EQ(device.getInfo().getName(), MQTT_DEVICE_NAME); + auto devices = instance.getDevices(); + bool contain = false; + daq::GenericDevicePtr deviceFromList; + for (const auto& d : devices) + { + contain = (d.getName() == MQTT_DEVICE_NAME); + if (contain) + { + deviceFromList = d; + break; + } + } + ASSERT_TRUE(contain); + ASSERT_TRUE(deviceFromList.assigned()); + ASSERT_EQ(deviceFromList.getInfo().getName(), device.getInfo().getName()); + ASSERT_TRUE(deviceFromList == device); +} + +TEST_F(MqttStreamingClientModuleTest, CreatingSeveralDevices) +{ + const auto instance = Instance(); + daq::GenericDevicePtr device; + ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1")); + ASSERT_EQ(device.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); + ASSERT_EQ(device.getInfo().getName(), MQTT_DEVICE_NAME); + daq::GenericDevicePtr anotherDevice; + ASSERT_THROW(anotherDevice = instance.addDevice("daq.mqtt://127.0.0.1"), AlreadyExistsException); +} + +TEST_F(MqttStreamingClientModuleTest, RemovingDevice) +{ + const auto instance = Instance(); + daq::GenericDevicePtr device; + ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1")); + ASSERT_NO_THROW(instance.removeDevice(device)); +} + +TEST_F(MqttStreamingClientModuleTest, CheckDeviceFunctionalBlocks) +{ + const auto instance = Instance(); + auto device = instance.addDevice("daq.mqtt://127.0.0.1"); + daq::DictPtr fbTypes; + ASSERT_NO_THROW(fbTypes = device.getAvailableFunctionBlockTypes()); + ASSERT_GE(fbTypes.getCount(), 2); + ASSERT_TRUE(fbTypes.hasKey(RAW_FB_NAME)); + ASSERT_TRUE(fbTypes.hasKey(JSON_FB_NAME)); +} + +TEST_F(MqttStreamingClientModuleTest, DefaultRawFbConfig) +{ + const auto instance = Instance(); + auto device = instance.addDevice("daq.mqtt://127.0.0.1"); + daq::DictPtr fbTypes; + daq::FunctionBlockTypePtr fbt; + daq::PropertyObjectPtr defaultConfig; + ASSERT_NO_THROW(fbTypes = device.getAvailableFunctionBlockTypes()); + ASSERT_NO_THROW(fbt = fbTypes.get(RAW_FB_NAME)); + ASSERT_NO_THROW(defaultConfig = fbt.createDefaultConfig()); + + ASSERT_TRUE(defaultConfig.assigned()); + + ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 1u); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SIGNAL_LIST)); + + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SIGNAL_LIST).getValueType(), CoreType::ctList); + ASSERT_TRUE(defaultConfig.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtr().empty()); +} + +TEST_F(MqttStreamingClientModuleTest, CreateRawFunctionalBlocks) +{ + const auto instance = Instance(); + auto device = instance.addDevice("daq.mqtt://127.0.0.1"); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME)); + ASSERT_EQ(rawFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); + ASSERT_EQ(rawFb.getName(), RAW_FB_NAME); + auto fbs = device.getFunctionBlocks(); + bool contain = false; + daq::GenericFunctionBlockPtr fbFromList; + for (const auto& fb : fbs) + { + contain = (fb.getName() == RAW_FB_NAME); + if (contain) + { + fbFromList = fb; + break; + } + } + ASSERT_TRUE(contain); + ASSERT_TRUE(fbFromList.assigned()); + ASSERT_EQ(fbFromList.getName(), rawFb.getName()); + ASSERT_TRUE(fbFromList == rawFb); +} + +TEST_F(MqttStreamingClientModuleTest, CheckRawFbEmptySignalList) +{ + const auto instance = Instance(); + auto device = instance.addDevice("daq.mqtt://127.0.0.1"); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME)); + auto signals = rawFb.getSignals(); + ASSERT_EQ(signals.getCount(), 0u); +} + +TEST_F(MqttStreamingClientModuleTest, CheckRawFbSignalList) +{ + constexpr uint NUM_TOPICS = 5u; + const auto instance = Instance(); + auto device = instance.addDevice("daq.mqtt://127.0.0.1"); + + const auto topic = buildTopicName(); + auto topicList = List(); + for (int i = 0; i < NUM_TOPICS; ++i) + { + addToList(topicList, fmt::format("{}_{}", topic, i)); + } + auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); + auto signals = rawFb.getSignals(); + ASSERT_EQ(signals.getCount(), NUM_TOPICS); +} + +TEST_F(MqttStreamingClientModuleTest, CheckRawFbSignalListWithWildcard) +{ + const auto instance = Instance(); + auto device = instance.addDevice("daq.mqtt://127.0.0.1"); + + auto topicList = List(); + addToList(topicList, ""); + addToList(topicList, "goodTopic/test/topic"); + addToList(topicList, "badTopic/+/test/topic"); + addToList(topicList, "badTopic/+/+/topic"); + addToList(topicList, "badTopic/#"); + addToList(topicList, "goodTopic/test/newTopic"); + + auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); + auto signals = rawFb.getSignals(); + ASSERT_EQ(signals.getCount(), 2u); +} + +TEST_F(MqttStreamingClientModuleTest, CheckRawFbConfig) +{ + constexpr uint NUM_TOPICS = 5u; + const auto instance = Instance(); + auto device = instance.addDevice("daq.mqtt://127.0.0.1"); + + const auto topic = buildTopicName(); + auto topicList = List(); + for (int i = 0; i < NUM_TOPICS; ++i) + { + addToList(topicList, fmt::format("{}_{}", topic, i)); + } + auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); + + const auto allProperties = rawFb.getAllProperties(); + ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount()); + + for (const auto& pror : config.getAllProperties()) + { + const auto propName = pror.getName(); + ASSERT_TRUE(rawFb.hasProperty(propName)); + ASSERT_EQ(rawFb.getPropertyValue(propName), config.getPropertyValue(propName)); + } +} + +TEST_F(MqttStreamingClientModuleTest, CheckRawFbDataTransfer) +{ + const auto topic = buildTopicName(); + const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, + std::vector{0x11, 0x12, 0x13, 0x14}, + std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, + std::vector{0x31}, + std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; + std::vector> dataToReceive; + + CreateRawFB({topic}); + + auto signalList = List(); + obj->getSignals(&signalList); + auto reader = daq::PacketReader(signalList[0]); + + for (const auto& data : dataToSend) + { + mqtt::MqttMessage msg = {topic, data, 1, 0}; + onSignalsMessage(msg); + } + + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.push_back(std::move(readData)); + } + } + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_EQ(dataToSend, dataToReceive); +} + +TEST_F(MqttStreamingClientModuleTest, CheckRawFbFullDataTransfer) +{ + const std::string topic = buildTopicName(); + + const auto instance = Instance(); + auto device = instance.addDevice("daq.mqtt://127.0.0.1"); + + auto topicList = List(); + addToList(topicList, topic); + auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + auto singal = device.addFunctionBlock(RAW_FB_NAME, config).getSignals()[0]; + auto reader = daq::PacketReader(singal); + + MqttAsyncClientWrapper publisher(std::make_shared(), "testPublisherId"); + ASSERT_TRUE(publisher.connect("127.0.0.1")); + + const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, + std::vector{0x11, 0x12, 0x13, 0x14}, + std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, + std::vector{0x31}, + std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; + std::vector> dataToReceive; + + for (const auto& data : dataToSend) + { + mqtt::MqttMessage msg = {topic, data, 1, 0}; + ASSERT_TRUE(publisher.publishMsg(msg)); + } + + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.push_back(std::move(readData)); + } + } + + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_EQ(dataToSend, dataToReceive); } diff --git a/mqtt_streaming_protocol/tests/CMakeLists.txt b/mqtt_streaming_protocol/tests/CMakeLists.txt index 4c0dd569..a63b8278 100644 --- a/mqtt_streaming_protocol/tests/CMakeLists.txt +++ b/mqtt_streaming_protocol/tests/CMakeLists.txt @@ -1,5 +1,6 @@ set(MODULE_NAME mqtt_streaming_protocol) set(TEST_APP test_${MODULE_NAME}) +set(TEST_LIB ${MODULE_NAME}_test_helper) set(TEST_SOURCES test_mqtt_streaming_protocol.cpp test_app.cpp @@ -8,8 +9,20 @@ set(TEST_SOURCES test_mqtt_streaming_protocol.cpp add_executable(${TEST_APP} ${TEST_SOURCES} ) +add_library(${TEST_LIB} STATIC MqttAsyncClientWrapper.h + MqttAsyncClientWrapper.cpp + Timer.h +) + +target_include_directories(${TEST_LIB} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(${TEST_LIB} PRIVATE ${MODULE_NAME} +) + target_link_libraries(${TEST_APP} PRIVATE daq::test_utils ${MODULE_NAME} + ${TEST_LIB} ) add_test(NAME ${TEST_APP} diff --git a/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp b/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp new file mode 100644 index 00000000..b3a97674 --- /dev/null +++ b/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp @@ -0,0 +1,125 @@ +#include "MqttAsyncClientWrapper.h" +#include "MqttAsyncClient.h" +#include "Timer.h" +#include + +using namespace std::chrono; + +MqttAsyncClientWrapper::MqttAsyncClientWrapper(std::string clientId) + : instance(std::make_shared()), + clientId(clientId) {}; + +MqttAsyncClientWrapper::MqttAsyncClientWrapper(std::shared_ptr instance, std::string clientId) + : instance(instance) +{ + if (!clientId.empty()) + { + this->clientId = clientId; + } +} + +bool MqttAsyncClientWrapper::createConnection(const std::string& url, const std::string& id) +{ + instance->setServerURL(url); + instance->setClientId(id); + + connectedDone = false; + connectedPromise = std::promise(); + connectedFuture = connectedPromise.get_future(); + + instance->setConnectedCb( + [this]() + { + bool expected = false; + if (connectedDone.compare_exchange_strong(expected, true)) + { + connectedPromise.set_value(true); + } + }); + return instance->connect(); +} + +bool MqttAsyncClientWrapper::connect(const std::string& url) +{ + return connect(url, clientId); +} + +bool MqttAsyncClientWrapper::connect(const std::string& url, const std::string& id) +{ + bool res = createConnection(url, id); + if (res) + { + auto status = connectedFuture.wait_for(milliseconds(successTimeout)); + instance->setConnectedCb(nullptr); + res = (status == std::future_status::ready && connectedFuture.get() == true); + } + return res; +} + +bool MqttAsyncClientWrapper::disconnect() +{ + if (instance->isConnected() != mqtt::MqttConnectionStatus::connected) + { + return true; + } + std::atomic done{false}; + std::promise disconnectedPromise; + auto disconnectedFuture = disconnectedPromise.get_future(); + instance->setDisconnectCb( + [promise = &disconnectedPromise, &done](bool result) + { + bool expected = false; + if (done.compare_exchange_strong(expected, true)) + { + promise->set_value(result); + } + }); + + auto disconnectionOk = instance->disconnect(); + if (!disconnectionOk) + { + return false; + } + + auto status = disconnectedFuture.wait_for(milliseconds(successTimeout)); + instance->setDisconnectCb(nullptr); + return (status == std::future_status::ready && disconnectedFuture.get() == true); +} + +bool MqttAsyncClientWrapper::removeRetainedTopic(const std::string& topic) +{ + return publishMsg(topic, "", true); +} + +bool MqttAsyncClientWrapper::publishMsg(const std::string& topic, const std::string& data, bool retained) +{ + const mqtt::MqttMessage msg(topic, std::vector(data.begin(), data.end()), 1, retained); + return publishMsg(msg); +} + +bool MqttAsyncClientWrapper::publishMsg(const mqtt::MqttMessage& msg) +{ + int token = 0; + + std::promise deliveryPromise; + auto deliveryFuture = deliveryPromise.get_future(); + instance->setDeliveryCompletedCb([promise = &deliveryPromise](int deliveredToken) { promise->set_value(deliveredToken); }); + + Timer sendTimer(successTimeout); + auto ok = instance->publish(msg.getTopic(), + (void*)(msg.getData().data()), + msg.getData().size(), + nullptr, + msg.getQos(), + &token, + msg.getRetained()); + if (!ok || token == 0) + { + instance->setDeliveryCompletedCb(nullptr); + return false; + } + + auto status = deliveryFuture.wait_for(sendTimer.remain()); + instance->setDeliveryCompletedCb(nullptr); + return (status == std::future_status::ready && deliveryFuture.get() == token); +} diff --git a/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.h b/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.h new file mode 100644 index 00000000..7b1ea1b3 --- /dev/null +++ b/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.h @@ -0,0 +1,27 @@ +#pragma once +#include "MqttAsyncClient.h" +#include + +class MqttAsyncClientWrapper +{ +public: + MqttAsyncClientWrapper(std::string clientId = ""); + MqttAsyncClientWrapper(std::shared_ptr instance, std::string clientId = ""); + + bool createConnection(const std::string& url, const std::string& id); + bool connect(const std::string& url); + bool connect(const std::string& url, const std::string& id); + bool disconnect(); + bool removeRetainedTopic(const std::string& topic); + bool publishMsg(const std::string& topic, const std::string& data, bool retained = false); + bool publishMsg(const mqtt::MqttMessage& msg); + + std::shared_ptr instance; + std::promise connectedPromise; + std::future connectedFuture; + std::atomic connectedDone{false}; + + int successTimeout = 5000; + int failureTimeout = 3000; + std::string clientId = "testMqttClientId"; +}; diff --git a/mqtt_streaming_protocol/tests/Timer.h b/mqtt_streaming_protocol/tests/Timer.h new file mode 100644 index 00000000..26eb3fa0 --- /dev/null +++ b/mqtt_streaming_protocol/tests/Timer.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +class Timer +{ +public: + Timer(int ms) + { + start = std::chrono::steady_clock::now(); + timeout = std::chrono::milliseconds(ms); + } + std::chrono::milliseconds remain() const + { + auto now = std::chrono::steady_clock::now(); + const auto elapsed_ms = std::chrono::duration_cast(now - start); + std::chrono::milliseconds newTout = (elapsed_ms >= timeout) ? std::chrono::milliseconds(0) : timeout - elapsed_ms; + return newTout; + } + bool expired() + { + return remain() == std::chrono::milliseconds(0); + } + explicit operator std::chrono::milliseconds() const noexcept + { + return remain(); + } + +protected: + std::chrono::steady_clock::time_point start; + std::chrono::milliseconds timeout; +}; diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp index 95c16fe9..71e76463 100644 --- a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -3,160 +3,26 @@ #include #include #include +#include "Timer.h" +#include "MqttAsyncClientWrapper.h" using namespace mqtt; using namespace std::chrono; -class Timer -{ -public: - Timer(int ms) - { - start = steady_clock::now(); - timeout = milliseconds(ms); - } - milliseconds remain() const { - auto now = steady_clock::now(); - const auto elapsed_ms = std::chrono::duration_cast(now - start); - milliseconds newTout = (elapsed_ms >= timeout) ? milliseconds(0) : timeout - elapsed_ms; - return newTout; - } - bool expired() { - return remain() == milliseconds(0); - } - explicit operator milliseconds() const noexcept - { - return remain(); - } -protected: - std::chrono::steady_clock::time_point start; - std::chrono::milliseconds timeout; -}; - -class MqttAsyncClientWrapper -{ -public: - MqttAsyncClientWrapper() = default; - MqttAsyncClientWrapper(std::shared_ptr instance, std::string clientId = "") - : instance(instance) - { - if (!clientId.empty()) { - this->clientId = clientId; - } - } - bool createConnection(const std::string& url, const std::string& id) { - instance->setServerURL(url); - instance->setClientId(id); - - connectedDone = false; - connectedPromise = std::promise(); - connectedFuture = connectedPromise.get_future(); - - instance->setConnectedCb([this]() { - bool expected = false; - if (connectedDone.compare_exchange_strong(expected, true)) { - connectedPromise.set_value(true); - } - }); - return instance->connect(); - } - - bool connect(const std::string& url) { - return connect(url, clientId); - } - bool connect(const std::string& url, const std::string& id) { - bool res = createConnection(url, id); - if (res) { - auto status = connectedFuture.wait_for(milliseconds(successTimeout)); - instance->setConnectedCb(nullptr); - res = (status == std::future_status::ready && connectedFuture.get() == true); - } - return res; - } - - bool disconnect() { - if (instance->isConnected() != MqttConnectionStatus::connected) { - return true; - } - std::atomic done{false}; - std::promise disconnectedPromise; - auto disconnectedFuture = disconnectedPromise.get_future(); - instance->setDisconnectCb([promise = &disconnectedPromise, &done](bool result) { - bool expected = false; - if (done.compare_exchange_strong(expected, true)) { - promise->set_value(result); - } - }); - - auto disconnectionOk = instance->disconnect(); - if (!disconnectionOk) { - return false; - } - - auto status = disconnectedFuture.wait_for(milliseconds(successTimeout)); - instance->setDisconnectCb(nullptr); - return (status == std::future_status::ready && disconnectedFuture.get() == true); - } - - bool removeRetainedTopic(const std::string& topic) { - return publishMsg(topic, "", true); - } - - bool publishMsg(const std::string& topic, const std::string& data, bool retained = false) { - const MqttMessage msg(topic, std::vector(data.begin(), data.end()), 1, retained); - return publishMsg(msg); - } - - bool publishMsg(const MqttMessage& msg) { - int token = 0; - - std::promise deliveryPromise; - auto deliveryFuture = deliveryPromise.get_future(); - instance->setDeliveryCompletedCb([promise = &deliveryPromise](int deliveredToken) { - promise->set_value(deliveredToken); - }); - - Timer sendTimer(successTimeout); - auto ok = instance->publish(msg.getTopic(), - (void *) (msg.getData().data()), - msg.getData().size(), - nullptr, - msg.getQos(), - &token, - msg.getRetained()); - if (!ok || token == 0) { - instance->setDeliveryCompletedCb(nullptr); - return false; - } - - auto status = deliveryFuture.wait_for(sendTimer.remain()); - instance->setDeliveryCompletedCb(nullptr); - return (status == std::future_status::ready && deliveryFuture.get() == token); - } - - std::string buildTopicName() { - return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()); - } - - std::shared_ptr instance; - std::promise connectedPromise; - std::future connectedFuture; - std::atomic connectedDone{false}; - - int successTimeout = 5000; - int failureTimeout = 3000; - std::string clientId = "testMqttClientId"; -}; - class MqttStreamingProtocolTest : public ::testing::Test, public MqttAsyncClientWrapper { protected: void SetUp() override { instance = std::make_shared(); + clientId = std::string("clientId_") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()); } void TearDown() override { instance.reset(); } + + std::string buildTopicName() { + return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()); + } }; TEST_F(MqttStreamingProtocolTest, Connection) From af65ba3f2ccc42d95779db6e3708f1649bd4e2dd Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 24 Oct 2025 12:06:19 +0200 Subject: [PATCH 19/55] mqtt: unnecessary include removing --- .../mqtt_receiver_fb_impl.h | 3 --- .../src/mqtt_receiver_fb_impl.cpp | 12 ------------ .../tests/test_mqtt_streaming_client_module.cpp | 3 --- 3 files changed, 18 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h index 5d9ada7d..919541d8 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -16,10 +16,7 @@ #pragma once #include -#include -#include #include -#include #include #include "MqttAsyncClient.h" diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index a4b7aad9..a1278022 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -1,18 +1,6 @@ #include -#include "opendaq/data_packet_ptr.h" -#include "opendaq/packet_factory.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include "mqtt_streaming_client_module/constants.h" -#include "MqttDataWrapper.h" BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp index 6d782a41..7c3c8620 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp @@ -1,11 +1,8 @@ #include "mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h" -#include -#include #include #include #include #include -#include #include #include #include From 1df968ef9decf344cbfa27279ac3e8aaf1890b67 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 24 Oct 2025 15:05:47 +0200 Subject: [PATCH 20/55] mqtt: new device property - DiscoveryTimeout; renaming InitDelay -> ConnectTimeout; --- .../mqtt_streaming_client_module/constants.h | 4 ++- .../src/mqtt_streaming_client_module_impl.cpp | 3 +- .../src/mqtt_streaming_device_impl.cpp | 30 ++++++++++++------- .../test_mqtt_streaming_client_module.cpp | 11 ++++--- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h index 6f20f428..50e7cc53 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h @@ -20,12 +20,14 @@ static constexpr uint16_t DEFAULT_PORT = 1883; static constexpr const char* DEFAULT_USERNAME = ""; static constexpr const char* DEFAULT_PASSWORD = ""; static constexpr uint32_t DEFAULT_INIT_DELAY = 3000; // ms +static constexpr uint32_t DEFAULT_DISCOVERY_DELAY = 3000; // ms static constexpr const char* PROPERTY_NAME_MQTT_BROKER_ADDRESS = "MqttBrokerAddress"; static constexpr const char* PROPERTY_NAME_MQTT_BROKER_PORT = "MqttBrokerPort"; static constexpr const char* PROPERTY_NAME_MQTT_USERNAME = "MqttUsername"; static constexpr const char* PROPERTY_NAME_MQTT_PASSWORD = "MqttPassword"; -static constexpr const char* PROPERTY_NAME_INIT_DELAY = "InitDelay"; +static constexpr const char* PROPERTY_NAME_CONNECT_TIMEOUT = "ConnectTimeout"; +static constexpr const char* PROPERTY_NAME_DISCOVERY_TIMEOUT = "DiscoveryTimeout"; static constexpr const char* PROPERTY_NAME_SIGNAL_LIST = "SignalList"; static constexpr const char* RAW_FB_NAME = "@rawMqttFb"; diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp index e76136a5..a6dfbdc5 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp @@ -248,7 +248,8 @@ PropertyObjectPtr MqttStreamingClientModule::createDefaultConfig() config.addProperty(StringProperty(PROPERTY_NAME_MQTT_USERNAME, DEFAULT_USERNAME)); config.addProperty(StringProperty(PROPERTY_NAME_MQTT_PASSWORD, DEFAULT_PASSWORD)); config.addProperty(IntProperty(PROPERTY_NAME_MQTT_BROKER_PORT, DEFAULT_PORT)); - config.addProperty(IntProperty(PROPERTY_NAME_INIT_DELAY, DEFAULT_INIT_DELAY)); + config.addProperty(IntProperty(PROPERTY_NAME_CONNECT_TIMEOUT, DEFAULT_INIT_DELAY)); + config.addProperty(IntProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT, DEFAULT_DISCOVERY_DELAY)); return config; } diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index fbd0a1d2..c2168756 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -36,19 +36,20 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const Co connectionString = std::string(DaqMqttDevicePrefix) + "://" + connectionSettings.mqttUrl + ":" + std::to_string(connectionSettings.port); - int initTimeout = config.getPropertyValue(PROPERTY_NAME_INIT_DELAY); + int connectTimeout = config.getPropertyValue(PROPERTY_NAME_CONNECT_TIMEOUT); + int discoveryTimeout = config.getPropertyValue(PROPERTY_NAME_DISCOVERY_TIMEOUT); initComponentStatus(); initBaseFunctionalBlocks(); initMqttSubscriber(); - if (!waitForConnection(initTimeout)) + if (!waitForConnection(connectTimeout)) { - LOG_E("MQTT: could not connect to MQTT broker within {} ms", initTimeout); - DAQ_THROW_EXCEPTION(CreateFailedException, "could not connect to MQTT broker within {} ms", initTimeout); + LOG_E("MQTT: could not connect to MQTT broker within {} ms", connectTimeout); + DAQ_THROW_EXCEPTION(CreateFailedException, "could not connect to MQTT broker within {} ms", connectTimeout); } LOG_I("MQTT: Connection established"); - receiveSignalTopics(initTimeout); + receiveSignalTopics(discoveryTimeout); // Build function block types based on received signal lists only once buildFunctionBlockTypes(); } @@ -128,14 +129,21 @@ bool MqttStreamingDeviceImpl::waitForConnection(const int timeoutMs) void MqttStreamingDeviceImpl::receiveSignalTopics(const int timeoutMs) { - subscriber->setMessageArrivedCb( - std::bind(&MqttStreamingDeviceImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); - subscriber->subscribe(TOPIC_ALL_SIGNALS, 1); + if (timeoutMs > 0) + { + subscriber->setMessageArrivedCb( + std::bind(&MqttStreamingDeviceImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); + subscriber->subscribe(TOPIC_ALL_SIGNALS, 1); - std::this_thread::sleep_for(std::chrono::milliseconds(timeoutMs)); // TODO : remove it! + std::this_thread::sleep_for(std::chrono::milliseconds(timeoutMs)); // TODO : remove it! - subscriber->unsubscribe(TOPIC_ALL_SIGNALS); - subscriber->setMessageArrivedCb(nullptr); + subscriber->unsubscribe(TOPIC_ALL_SIGNALS); + subscriber->setMessageArrivedCb(nullptr); + } + else + { + LOG_I("Signal discovering step was skipped"); + } } void MqttStreamingDeviceImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg) diff --git a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp index 7c3c8620..89896268 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp @@ -169,25 +169,28 @@ TEST_F(MqttStreamingClientModuleTest, DefaultDeviceConfig) auto defaultConfig = deviceTypes.get(DaqMqttDeviceTypeId).createDefaultConfig(); ASSERT_TRUE(defaultConfig.assigned()); - ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 5u); + ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 6u); ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS)); ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_PORT)); ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_USERNAME)); ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_PASSWORD)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_INIT_DELAY)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_CONNECT_TIMEOUT)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT)); ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS).getValueType(), CoreType::ctString); ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_PORT).getValueType(), CoreType::ctInt); ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_USERNAME).getValueType(), CoreType::ctString); ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_PASSWORD).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_INIT_DELAY).getValueType(), CoreType::ctInt); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_CONNECT_TIMEOUT).getValueType(), CoreType::ctInt); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT).getValueType(), CoreType::ctInt); ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS), DEFAULT_BROKER_ADDRESS); ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT), DEFAULT_PORT); ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME), DEFAULT_USERNAME); ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD), DEFAULT_PASSWORD); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_INIT_DELAY), DEFAULT_INIT_DELAY); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CONNECT_TIMEOUT), DEFAULT_INIT_DELAY); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_DISCOVERY_TIMEOUT), DEFAULT_DISCOVERY_DELAY); } TEST_F(MqttStreamingClientModuleTest, CreatingDevice) From 5511ad55fb44d7730ba82664649f29aedecd95b8 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 24 Oct 2025 15:39:24 +0200 Subject: [PATCH 21/55] mqtt: topic validation for JSON config --- mqtt_streaming_protocol/src/MqttDataWrapper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 93ca3f45..dda6abd7 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -168,6 +168,8 @@ std::unordered_map MqttDataWrapper::extr { const rapidjson::Value& array = it->value; const std::string topic = it->name.GetString(); + if (!validateTopic(topic, loggerComponent)) + continue; if (!array.IsArray()) { LOG_W("Wrong description for \"{}\" topic. Skip!", topic); From 2ea3f1158af44b3748070a42150c1ce1381bd95a Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 16:01:03 +0100 Subject: [PATCH 22/55] mqtt: if JSON config for JSON FB contains a extraction field that field is required in a data msg --- mqtt_streaming_protocol/src/MqttDataWrapper.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index dda6abd7..01d53f82 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -319,6 +319,10 @@ std::vector> MqttDataWrapper::extractDataSample { LOG_W("Not all required fields are present."); } + else if (!msgDescriptor.tsFieldName.empty() && hasTS == false) + { + LOG_W("Timestamp field is expected but missing."); + } else { // TODO : value [1, 2, 3, ...] support From c2b20a6d45f0e00864ebdc8970bde0a2d9c0feac Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 16:02:51 +0100 Subject: [PATCH 23/55] mqtt: check if a topic descriptor is present --- mqtt_streaming_protocol/src/MqttDataWrapper.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 01d53f82..92415f35 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -222,12 +222,15 @@ void MqttDataWrapper::setOutputSignals( void MqttDataWrapper::createAndSendDataPacket(const std::string& topic, const std::string& json) { auto msgDescriptors = topicDescriptors.find(topic); - for (const auto& dsc : msgDescriptors->second) + if (msgDescriptors != topicDescriptors.end()) { - auto packets = extractDataSamples(topic, dsc, json); - for (const auto& [signalId, data] : packets) + for (const auto& dsc : msgDescriptors->second) { - sendDataSamples(signalId, data); + auto packets = extractDataSamples(topic, dsc, json); + for (const auto& [signalId, data] : packets) + { + sendDataSamples(signalId, data); + } } } } From 87bb56e0638786e95a9e3cdf9c4eafdae8913b0f Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 16:07:33 +0100 Subject: [PATCH 24/55] mqtt: static methods for MQTT FBs for building signal names --- .../mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h | 4 ++-- .../mqtt_streaming_client_module/mqtt_receiver_fb_impl.h | 5 +++-- .../src/mqtt_raw_receiver_fb_impl.cpp | 2 +- mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h index c8a594d6..5334845f 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h @@ -34,6 +34,8 @@ class MqttRawReceiverFbImpl final : public FunctionBlock const PropertyObjectPtr& config = nullptr); ~MqttRawReceiverFbImpl() override; + static std::string buildSignalNameFromTopic(std::string topic); + private: std::unordered_map outputSignals; @@ -51,8 +53,6 @@ class MqttRawReceiverFbImpl final : public FunctionBlock void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); - std::string buildSignalNameFromTopic(std::string topic) const; - void subscribeToTopics(); void unsubscribeFromTopics(); }; diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h index 919541d8..1ecf69ee 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -35,6 +35,9 @@ class MqttReceiverFbImpl final : public FunctionBlock const PropertyObjectPtr& config = nullptr); ~MqttReceiverFbImpl() override; + static std::string buildSignalNameFromTopic(std::string topic, const std::string& signalName); + static std::string buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName); + private: mqtt::MqttDataWrapper jsonDataWorker; std::unordered_map outputSignals; @@ -54,8 +57,6 @@ class MqttReceiverFbImpl final : public FunctionBlock void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); - std::string buildSignalNameFromTopic(std::string topic, const std::string& signalName) const; - std::string buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName) const; std::set getSubscribedTopics() const; }; diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index fc89ec05..4000169d 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -114,7 +114,7 @@ void MqttRawReceiverFbImpl::createSignals() } } -std::string MqttRawReceiverFbImpl::buildSignalNameFromTopic(std::string topic) const +std::string MqttRawReceiverFbImpl::buildSignalNameFromTopic(std::string topic) { boost::replace_all(topic, "/", "_"); topic += "_Mqtt"; diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index a1278022..1ab3785c 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -128,14 +128,14 @@ void MqttReceiverFbImpl::createSignals() jsonDataWorker.setOutputSignals(&outputSignals); } -std::string MqttReceiverFbImpl::buildSignalNameFromTopic(std::string topic, const std::string& signalName) const +std::string MqttReceiverFbImpl::buildSignalNameFromTopic(std::string topic, const std::string& signalName) { boost::replace_all(topic, "/", "_"); topic += "_Mqtt_" + signalName; return topic; } -std::string MqttReceiverFbImpl::buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName) const +std::string MqttReceiverFbImpl::buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName) { boost::replace_all(topic, "/", "_"); topic += std::string("_Mqtt") + "_domain" + signalName; From 9c29ae66fde507b661542c66dda27e78ac45be92 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 16:09:56 +0100 Subject: [PATCH 25/55] mqtt: const for method arguments --- .../mqtt_streaming_client_module/mqtt_receiver_fb_impl.h | 4 ++-- mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h index 1ecf69ee..36af9aaa 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -49,13 +49,13 @@ class MqttReceiverFbImpl final : public FunctionBlock void createSignals(); - void parseMessage(mqtt::MqttMessage& msg); + void parseMessage(const mqtt::MqttMessage& msg); void createDataPacket(const std::string& topic, const std::string& json); void initProperties(const PropertyObjectPtr& config); void readProperties(); - void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); + void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg); std::set getSubscribedTopics() const; }; diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index 1ab3785c..76a7a7a6 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -43,7 +43,7 @@ MqttReceiverFbImpl::~MqttReceiverFbImpl() } } -void MqttReceiverFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg) +void MqttReceiverFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg) { parseMessage(msg); } @@ -88,7 +88,7 @@ void MqttReceiverFbImpl::createDataPacket(const std::string& topic, const std::s jsonDataWorker.createAndSendDataPacket(topic, json); } -void MqttReceiverFbImpl::parseMessage(mqtt::MqttMessage& msg) +void MqttReceiverFbImpl::parseMessage(const mqtt::MqttMessage& msg) { std::string topic(msg.getTopic()); std::string jsonObjStr(msg.getData().begin(), msg.getData().end()); From b046f7cdcb458e988586d8ad17fc8c8b96dd2501 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 16:13:26 +0100 Subject: [PATCH 26/55] mqtt: JSON FB - check if subscriber is set, otherwise set a component status error --- .../src/mqtt_receiver_fb_impl.cpp | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index 76a7a7a6..0718020e 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -22,24 +22,34 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, initProperties(type.createDefaultConfig()); createSignals(); - for (const auto& topic : getSubscribedTopics()) + if (subscriber) { - subscriber - ->setMessageArrivedCb(topic, - std::bind(&MqttReceiverFbImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); - auto ok = subscriber->subscribe(topic, 1); - if (!ok) - LOG_W("Failed to subscribe to the topic: {}", topic); + for (const auto& topic : getSubscribedTopics()) + { + subscriber + ->setMessageArrivedCb(topic, + std::bind(&MqttReceiverFbImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); + auto ok = subscriber->subscribe(topic, 1); + if (!ok) + LOG_W("Failed to subscribe to the topic: {}", topic); + } + setComponentStatus(ComponentStatus::Ok); + } + else + { + setComponentStatusWithMessage(ComponentStatus::Error, "MQTT subscriber client is not set."); } - setComponentStatus(ComponentStatus::Ok); } MqttReceiverFbImpl::~MqttReceiverFbImpl() { - for (const auto& topic : getSubscribedTopics()) + if (subscriber) { - subscriber->setMessageArrivedCb(topic, nullptr); - subscriber->unsubscribe(topic); + for (const auto& topic : getSubscribedTopics()) + { + subscriber->setMessageArrivedCb(topic, nullptr); + subscriber->unsubscribe(topic); + } } } From 1fd658723e95cb65dce6d6b49ad8f4ff07734b5e Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 16:16:16 +0100 Subject: [PATCH 27/55] mqtt: tests for the MQTT module --- .../mqtt_raw_receiver_fb_impl.h | 2 +- .../mqtt_receiver_fb_impl.h | 1 + .../tests/CMakeLists.txt | 5 + .../tests/test_daq_test_helper.h | 66 ++ .../tests/test_data.h | 363 ++++++++++ .../tests/test_mqtt_device.cpp | 143 ++++ .../tests/test_mqtt_json_fb.cpp | 620 ++++++++++++++++++ .../tests/test_mqtt_raw_fb.cpp | 290 ++++++++ .../test_mqtt_streaming_client_module.cpp | 365 +---------- 9 files changed, 1494 insertions(+), 361 deletions(-) create mode 100644 mqtt_streaming_client_module/tests/test_daq_test_helper.h create mode 100644 mqtt_streaming_client_module/tests/test_data.h create mode 100644 mqtt_streaming_client_module/tests/test_mqtt_device.cpp create mode 100644 mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp create mode 100644 mqtt_streaming_client_module/tests/test_mqtt_raw_fb.cpp diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h index 5334845f..b4cd537b 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h @@ -24,7 +24,7 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE class MqttRawReceiverFbImpl final : public FunctionBlock { - friend class MqttStreamingClientModuleTest; + friend class MqttRawFbTest; public: explicit MqttRawReceiverFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h index 36af9aaa..5b0b609b 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -26,6 +26,7 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE class MqttReceiverFbImpl final : public FunctionBlock { + friend class MqttJsonFbHelper; public: explicit MqttReceiverFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, diff --git a/mqtt_streaming_client_module/tests/CMakeLists.txt b/mqtt_streaming_client_module/tests/CMakeLists.txt index 90583e3c..0c88bb66 100644 --- a/mqtt_streaming_client_module/tests/CMakeLists.txt +++ b/mqtt_streaming_client_module/tests/CMakeLists.txt @@ -2,7 +2,12 @@ set(MODULE_NAME mqtt_stream_cli_module) set(TEST_APP test_${MODULE_NAME}) set(TEST_SOURCES test_mqtt_streaming_client_module.cpp + test_mqtt_device.cpp + test_mqtt_raw_fb.cpp + test_mqtt_json_fb.cpp + test_daq_test_helper.h test_app.cpp + test_data.h ) add_executable(${TEST_APP} ${TEST_SOURCES} diff --git a/mqtt_streaming_client_module/tests/test_daq_test_helper.h b/mqtt_streaming_client_module/tests/test_daq_test_helper.h new file mode 100644 index 00000000..fe7787f7 --- /dev/null +++ b/mqtt_streaming_client_module/tests/test_daq_test_helper.h @@ -0,0 +1,66 @@ +#pragma once +#include +#include +#include + +class DaqTestHelper +{ +public: + daq::InstancePtr daqInstance; + daq::GenericDevicePtr device; + + void StartUp(std::string connectionStr = "daq.mqtt://127.0.0.1", int discoveryTimeoutMs = 0) + { + DaqInstanceInit(); + DaqMqttDeviceInit(connectionStr, discoveryTimeoutMs); + } + + daq::InstancePtr DaqInstanceInit() + { + if (!daqInstance.assigned()) + daqInstance = daq::Instance(); + return daqInstance; + } + + daq::GenericDevicePtr DaqMqttDeviceInit(std::string connectionStr, int discoveryTimeoutMs = -1) + { + if (!device.assigned()) + { + if (discoveryTimeoutMs >= 0) + { + daq::ModulePtr module; + createModule(&module, daq::NullContext()); + + auto config = module.getAvailableDeviceTypes().get(daq::modules::mqtt_streaming_client_module::DaqMqttDeviceTypeId).createDefaultConfig(); + config.setPropertyValue(daq::modules::mqtt_streaming_client_module::PROPERTY_NAME_DISCOVERY_TIMEOUT, 0); + device = daqInstance.addDevice(connectionStr, config); + } + else + { + device = daqInstance.addDevice(connectionStr); + } + } + return device; + } + + daq::PropertyObjectPtr DaqMqttDeviceConfig(int discoveryTimeoutMs = -1) + { + daq::ModulePtr module; + createModule(&module, daq::NullContext()); + + auto config = module.getAvailableDeviceTypes().get(daq::modules::mqtt_streaming_client_module::DaqMqttDeviceTypeId).createDefaultConfig(); + if (discoveryTimeoutMs >= 0) + { + config.setPropertyValue(daq::modules::mqtt_streaming_client_module::PROPERTY_NAME_DISCOVERY_TIMEOUT, 0); + } + + return config; + } + + static daq::ModulePtr CreateModule() + { + daq::ModulePtr module; + createModule(&module, daq::NullContext()); + return module; + } +}; diff --git a/mqtt_streaming_client_module/tests/test_data.h b/mqtt_streaming_client_module/tests/test_data.h new file mode 100644 index 00000000..0615dab5 --- /dev/null +++ b/mqtt_streaming_client_module/tests/test_data.h @@ -0,0 +1,363 @@ +#pragma once +#include +#include +#include + +inline const std::string VALID_JSON_0 = R"json({ + "openDAQ/RefDev0/IO/AI/RefCh0/Sig/AI0": [ + { + "AI0": { + "Value": "value", + "Timestamp": "timestamp", + "Unit": [ + "V", + "volts", + "voltage" + ] + } + } + ], + "openDAQ/RefDev0/IO/AI/RefCh1/Sig/AI1": [ + { + "AI1": { + "Value": "value", + "Timestamp": "timestamp", + "Unit": [ + ] + } + } + ], + "openDAQ/RefDev0/IO/AI/RefCh2/Sig/AI2": [ + { + "AI2": { + "Value": "value", + "Timestamp": "timestamp", + "Unit": [ + "V" + ] + } + } + ], + "openDAQ/RefDev0/IO/AI/RefCh3/Sig/AI3": [ + { + "AI3": { + "Value": "value", + "Timestamp": "timestamp" + } + } + ] +} +)json"; + +inline const std::string VALID_JSON_1 = R"json({ + "/mirip/UNet3AC2/sensor/data":[ + { + "temp":{ + "Value":"temp", + "Timestamp":"ts", + "Unit":[ + "°C" + ] + } + }, + { + "humidity":{ + "Value":"humi", + "Timestamp":"ts", + "Unit":[ + "%" + ] + } + }, + { + "tds":{ + "Value":"tds_value", + "Timestamp":"ts", + "Unit":[ + "ppm", "parts per million", "Total dissolved solids" + ] + } + } + ] +} +)json"; + +inline const std::string VALID_JSON_2 = R"json({ + "/mirip/UNet3AC2/sensor/data0":[ + { + "temp":{ + "Value":"temp", + "Timestamp":"ts", + "Unit":[ + "°C" + ] + } + }, + { + "humidity":{ + "Value":"humi", + "Timestamp":"ts", + "Unit":[ + "%" + ] + } + }, + { + "tds":{ + "Value":"tds_value", + "Timestamp":"ts", + "Unit":[ + "ppm", "parts per million", "Total dissolved solids" + ] + } + } + ], + "/mirip/UNet3AC2/sensor/data1":[ + { + "temp":{ + "Value":"temp", + "Timestamp":"ts", + "Unit":[ + "°C" + ] + } + }, + { + "humidity":{ + "Value":"humi", + "Timestamp":"ts", + "Unit":[ + "%" + ] + } + } + ], + "/mirip/UNet3AC2/sensor/data2":[ + { + "temp":{ + "Value":"temp", + "Timestamp":"ts", + "Unit":[ + "°C" + ] + } + } + ] +} +)json"; + +inline const std::string WILDCARD_JSON_0 = R"json({ + "/mirip/UNet3AC2/+/data0":[ + { + "temp":{ + "Value":"temp", + "Timestamp":"ts", + "Unit":[ + "°C" + ] + } + }, + { + "humidity":{ + "Value":"humi", + "Timestamp":"ts", + "Unit":[ + "%" + ] + } + }, + { + "tds":{ + "Value":"tds_value", + "Timestamp":"ts", + "Unit":[ + "ppm", "parts per million", "Total dissolved solids" + ] + } + } + ], + "/mirip/#":[ + { + "temp":{ + "Value":"temp", + "Timestamp":"ts", + "Unit":[ + "°C" + ] + } + }, + { + "humidity":{ + "Value":"humi", + "Timestamp":"ts", + "Unit":[ + "%" + ] + } + } + ], + "/mirip/UNet3AC2/sensor/data2":[ + { + "temp":{ + "Value":"temp", + "Timestamp":"ts", + "Unit":[ + "°C" + ] + } + } + ] +} +)json"; + +inline const std::string INVALID_JSON_0 = R"json({ + "/mirip/UNet3AC2/+/data0":[ + { + "temp":{ + } + } + ] +} +)json"; + +inline const std::string INVALID_JSON_1 = R"json({ + "/mirip/UNet3AC2/+/data0":[ + { + "temp": 0 + } + ] +} +)json"; + +inline const std::string INVALID_JSON_2 = R"json({ + "/mirip/UNet3AC2/+/data0":[ + ] +} +)json"; + +inline const std::string INVALID_JSON_3 = R"json({ + "/mirip/UNet3AC2/+/data0": "invalid_value" +} +)json"; + +inline const std::string VALID_JSON_CONFIG_0 = R"json({ + :[ + { + "temperature":{ + "Value":"temp", + "Timestamp":"ts", + "Unit":[ + "°C" + ] + } + } + ] +} +)json"; + +inline const std::string VALID_JSON_DATA_0 = R"json({ + "temp": , "ts": +} +)json"; + +inline const std::string VALID_JSON_CONFIG_1 = R"json({ + :[ + { + "temperature":{ + "Value":"temp", + "Unit":[ + "°C" + ] + } + } + ] +} +)json"; + +inline const std::string VALID_JSON_DATA_1 = R"json({ + "temp": +} +)json"; + +inline const std::string VALID_JSON_CONFIG_2 = R"json({ + :[ + { + "temperature":{ + "Value":"temperature", + "Timestamp":"timestamp", + "Unit":[ + "°C" + ] + } + }, + { + "humi":{ + "Value":"humi", + "Timestamp":"timestamp" + } + }, + { + "pressure":{ + "Value":"pr" + } + } + ] +} +)json"; + +inline const std::string VALID_JSON_DATA_2 = R"json({ + "timestamp": , "temperature": , "humi": , "pr": +} +)json"; + +inline const std::string MISSING_FIELD_JSON_DATA_2 = R"json({ + "timestamp": , "temperature": , "pr": +} +)json"; + +inline const std::vector> DATA_DOUBLE_INT_0 = {{23.50000001, 1761567115}, + {-0.00000005583, 1761567116}, + {19.84916651651, 1761567117}, + {-30.28078754, 1761567118}, + {50.11245348484001, 1761567119}}; +inline const std::vector> DATA_DOUBLE_INT_1 = {{3.5, 1761567115000}, + {5.0, 1761567116000}, + {-9.8, 1761567117000}, + {0.2, 1761567118000}, + {0.1, 1761567119000}}; +inline const std::vector> DATA_DOUBLE_INT_2 = {{223.5, 1761567115000000}, + {-345.0, 1761567116000000}, + {519.8, 1761567117000000}, + {380.2, 1761567118000000}, + {570.1, 1761567119000000}}; + +inline const std::vector> DATA_INT_INT_0 = {{2307, 1761567115}, + {4500, 1761567116}, + {198, 1761567117}, + {380, 1761567118}, + {500, 1761567119}}; +inline const std::vector> DATA_INT_INT_1 = {{3, 1761567115000}, + {55358, 1761567116000}, + {9525, 1761567117000}, + {-454, 1761567118000}, + {454490, 1761567119000}}; +inline const std::vector> DATA_INT_INT_2 = {{-7223, 1761567115000000}, + {-3475, 1761567116000000}, + {5719, 1761567117000000}, + {380, 1761567118000000}, + {5, 1761567119000000}}; +inline const std::vector> DATA_DOUBLE_STR_0 = {{23070.008, "2025-10-27T12:45:15Z"}, + {4500.4883, " 2025-10-27T12:45:16Z"}, + {198.0000052, "2025-10-27T12:45:17Z "}, + {380.3484, " 2025-10-27T12:45:18Z "}, + {500.0, "2025-10-27T12:45:19Z"}}; +inline const std::vector> DATA_DOUBLE_STR_1 = {{3.454, "2025-10-27 12:45:15"}, + {55358.4, " 2025-10-27 12:45:16 "}, + {9525.5, " 2025-10-27 12:45:17 "}, + {-454.9, "2025-10-27 12:45:18 "}, + {454490.44, " 2025-10-27 12:45:19"}}; +inline const std::vector> DATA_DOUBLE_STR_2 = {{-7223.45, " 2025-10-27 12:45:15.001"}, + {-3475.5, "2025-10-27 12:45:15.002 "}, + {5719.9, " 2025-10-27 12:45:15.003 "}, + {380.1, " 2025-10-27 12:45:15.004 "}, + {5, " 2025-10-27 12:45:15.005"}}; diff --git a/mqtt_streaming_client_module/tests/test_mqtt_device.cpp b/mqtt_streaming_client_module/tests/test_mqtt_device.cpp new file mode 100644 index 00000000..6bea4ebd --- /dev/null +++ b/mqtt_streaming_client_module/tests/test_mqtt_device.cpp @@ -0,0 +1,143 @@ +#include "test_daq_test_helper.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace daq; +using namespace daq::modules::mqtt_streaming_client_module; + +namespace daq::modules::mqtt_streaming_client_module +{ +class MqttDeviceTest : public testing::Test, public DaqTestHelper +{ +}; +} // namespace daq::modules::mqtt_streaming_client_module + +TEST_F(MqttDeviceTest, DefaultDeviceConfig) +{ + const auto module = CreateModule(); + + DictPtr deviceTypes; + ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); + ASSERT_EQ(deviceTypes.getCount(), 1u); + + ASSERT_TRUE(deviceTypes.hasKey(DaqMqttDeviceTypeId)); + auto defaultConfig = deviceTypes.get(DaqMqttDeviceTypeId).createDefaultConfig(); + ASSERT_TRUE(defaultConfig.assigned()); + + ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 6u); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_PORT)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_USERNAME)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_PASSWORD)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_CONNECT_TIMEOUT)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT)); + + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_PORT).getValueType(), CoreType::ctInt); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_USERNAME).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_PASSWORD).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_CONNECT_TIMEOUT).getValueType(), CoreType::ctInt); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT).getValueType(), CoreType::ctInt); + + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS), DEFAULT_BROKER_ADDRESS); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT), DEFAULT_PORT); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME), DEFAULT_USERNAME); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD), DEFAULT_PASSWORD); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CONNECT_TIMEOUT), DEFAULT_INIT_DELAY); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_DISCOVERY_TIMEOUT), DEFAULT_DISCOVERY_DELAY); +} + +TEST_F(MqttDeviceTest, CreatingDeviceWithDefaultConfig) +{ + const auto instance = Instance(); + daq::GenericDevicePtr device; + ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1")); + ASSERT_EQ(device.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); + ASSERT_EQ(device.getInfo().getName(), MQTT_DEVICE_NAME); + auto devices = instance.getDevices(); + bool contain = false; + daq::GenericDevicePtr deviceFromList; + for (const auto& d : devices) + { + contain = (d.getName() == MQTT_DEVICE_NAME); + if (contain) + { + deviceFromList = d; + break; + } + } + ASSERT_TRUE(contain); + ASSERT_TRUE(deviceFromList.assigned()); + ASSERT_EQ(deviceFromList.getInfo().getName(), device.getInfo().getName()); + ASSERT_TRUE(deviceFromList == device); +} + +TEST_F(MqttDeviceTest, CreatingDeviceWithCustomConfig) +{ + const auto instance = Instance(); + daq::GenericDevicePtr device; + auto config = DaqMqttDeviceConfig(100); + ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1", config)); + ASSERT_EQ(device.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); +} + +TEST_F(MqttDeviceTest, CreatingDeviceWithEmptyConfig) +{ + const auto instance = Instance(); + daq::GenericDevicePtr device; + auto config = PropertyObject(); + ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1", config)); + ASSERT_EQ(device.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); +} + +TEST_F(MqttDeviceTest, CreatingDeviceWithPartialConfig) +{ + const auto instance = Instance(); + daq::GenericDevicePtr device; + auto config = PropertyObject(); + config.addProperty(IntProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT, 100)); + ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1", config)); + ASSERT_EQ(device.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); +} + +TEST_F(MqttDeviceTest, CreatingSeveralDevices) +{ + const auto instance = Instance(); + daq::GenericDevicePtr device; + auto config = DaqMqttDeviceConfig(100); + ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1", config)); + ASSERT_EQ(device.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); + ASSERT_EQ(device.getInfo().getName(), MQTT_DEVICE_NAME); + daq::GenericDevicePtr anotherDevice; + ASSERT_THROW(anotherDevice = instance.addDevice("daq.mqtt://127.0.0.1", config), AlreadyExistsException); +} + +TEST_F(MqttDeviceTest, RemovingDevice) +{ + const auto instance = Instance(); + daq::GenericDevicePtr device; + auto config = DaqMqttDeviceConfig(100); + ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1", config)); + ASSERT_NO_THROW(instance.removeDevice(device)); +} + +TEST_F(MqttDeviceTest, CheckDeviceFunctionalBlocks) +{ + StartUp(); + daq::DictPtr fbTypes; + ASSERT_NO_THROW(fbTypes = device.getAvailableFunctionBlockTypes()); + ASSERT_GE(fbTypes.getCount(), 2); + ASSERT_TRUE(fbTypes.hasKey(RAW_FB_NAME)); + ASSERT_TRUE(fbTypes.hasKey(JSON_FB_NAME)); +} diff --git a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp new file mode 100644 index 00000000..c9ac4dc5 --- /dev/null +++ b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp @@ -0,0 +1,620 @@ +#include "MqttAsyncClientWrapper.h" +#include "mqtt_streaming_client_module/mqtt_receiver_fb_impl.h" +#include "test_daq_test_helper.h" +#include "test_data.h" +#include "timestampConverter.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace daq; +using namespace daq::modules::mqtt_streaming_client_module; + +bool almostEqual(double a, double b, double relEpsilon = 1e-9, double absEpsilon = 1e-12) +{ + return std::fabs(a - b) <= std::max(absEpsilon, relEpsilon * std::max(std::fabs(a), std::fabs(b))); +} + +std::string doubleToString(double value, int precision = 12) +{ + std::ostringstream out; + out << std::fixed << std::setprecision(precision) << value; + return out.str(); +} + +namespace daq::modules::mqtt_streaming_client_module +{ +class MqttJsonFbHelper +{ +public: + std::unique_ptr obj; + + void onSignalsMessage(const mqtt::MqttMessage& msg) + { + mqtt::MqttAsyncClient unused; + obj->onSignalsMessage(unused, msg); + } + + void CreateJsonFB(const std::string& jsonConfig) + { + auto config = PropertyObject(); + config.addProperty(StringProperty(PROPERTY_NAME_SIGNAL_LIST, String(""))); + const auto fbType = FunctionBlockType(JSON_FB_NAME, JSON_FB_NAME, "", config); + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); + obj = std::make_unique(NullContext(), nullptr, fbType, "localId", nullptr, config); + } + + auto getSignals() + { + auto signalList = List(); + obj->getSignals(&signalList); + return signalList; + } + + std::string buildTopicName() + { + return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()); + } + + std::string buildClientId() + { + return std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + "_ClientId"; + } + + template std::string replacePlaceholder(const std::string& jsonTemplate, const std::string& ph, const T& value) + { + std::string result = jsonTemplate; + size_t pos = result.find(ph); + if (pos != std::string::npos) + { + std::string replacement; + if constexpr (std::is_same_v) + { + replacement = '"' + value + '"'; + } + else if constexpr (std::is_same_v || std::is_same_v) + { + replacement = doubleToString(value); + } + else + { + replacement = std::to_string(value); + } + result.replace(pos, ph.length(), replacement); + } + return result; + } + + template std::vector replacePlaceholders(const std::vector>& data, const std::string& jsonTemplate) + { + std::vector result; + for (const auto& [value, ts] : data) + { + auto str = replacePlaceholder(jsonTemplate, "", value); + str = replacePlaceholder(str, "", ts); + result.push_back(str); + } + return result; + } + + template + std::vector> + transferData(const std::vector>& data, const std::string& jsonConfigTemplate, const std::string& jsonDataTemplate) + { + return transferData>(data, jsonConfigTemplate, jsonDataTemplate); + } + + template + std::vector transferDataWithoutDomain(const std::vector>& data, + const std::string& jsonConfigTemplate, + const std::string& jsonDataTemplate) + { + return transferData(data, jsonConfigTemplate, jsonDataTemplate); + } + + template + bool compareData(const std::vector>& data0, const std::vector>& data1, bool compareTs = true) + { + if (data0.size() != data1.size()) + return false; + for (std::size_t i = 0; i < data0.size(); ++i) + { + const auto& [value0, ts0] = data0[i]; + const auto& [value1, ts1] = data1[i]; + if (!almostEqual(static_cast(value0), static_cast(value1))) + return false; + if (compareTs) + { + if constexpr (std::is_same_v) + { + if (mqtt::utils::numericToMicroseconds(ts0) != ts1 && ts1 != 0) + return false; + } + else if constexpr (std::is_same_v) + { + if (mqtt::utils::toUnixTicks(ts0) != ts1 && ts1 != 0) + return false; + } + else + { + return false; + } + } + } + return true; + } + + template + bool compareData(const std::vector>& data0, const std::vector& data1) + { + if (data0.size() != data1.size()) + return false; + for (std::size_t i = 0; i < data0.size(); ++i) + { + const auto& [value0, ts0] = data0[i]; + const auto& value1 = data1[i]; + if (!almostEqual(static_cast(value0), static_cast(value1))) + return false; + } + return true; + } + + std::vector> read(const StreamReaderPtr& reader, bool withDomain = true) + { + std::vector> result; + while (!reader.getEmpty()) + { + daq::SizeT cnt = 1; + std::pair dataToReceiveEntry; + if (withDomain) + reader.readWithDomain(&dataToReceiveEntry.first, &dataToReceiveEntry.second, &cnt); + else + reader.read(&dataToReceiveEntry.first, &cnt); + if (cnt == 1) + result.push_back(dataToReceiveEntry); + } + return result; + } + +private: + template std::vector transferData(const std::vector>& data, const std::string& jsonConfigTemplate, const std::string& jsonDataTemplate) + { + const auto topic = buildTopicName(); + const auto jsonConfig = replacePlaceholder(jsonConfigTemplate, "", topic); + CreateJsonFB(jsonConfig); + + auto signalList = getSignals(); + auto reader = daq::StreamReader(signalList[0]); + + auto msgs = replacePlaceholders(data, jsonDataTemplate); + for (const auto& str : msgs) + { + onSignalsMessage({topic, std::vector(str.begin(), str.end()), 1, 0}); + } + + std::vector dataToReceive; + while (!reader.getEmpty()) + { + daq::SizeT cnt = 1; + returnT dataToReceiveEntry; + if constexpr (std::is_same_v) + { + reader.read(&dataToReceiveEntry, &cnt); + } + else if constexpr (std::is_same_v>) + { + reader.readWithDomain(&dataToReceiveEntry.first, &dataToReceiveEntry.second, &cnt); + } + if (cnt == 1) + dataToReceive.push_back(dataToReceiveEntry); + } + return dataToReceive; + } +}; + +class MqttJsonFbTest : public testing::Test, public DaqTestHelper, public MqttJsonFbHelper +{ +}; +class MqttJsonFbRightJsonConfigPTest : public ::testing::TestWithParam>, + public DaqTestHelper, + public MqttJsonFbHelper +{ +}; +class MqttJsonFbWrongJsonConfigPTest : public ::testing::TestWithParam, public DaqTestHelper, public MqttJsonFbHelper +{ +}; +class MqttJsonFbDoubleDataPTest : public ::testing::TestWithParam>>, + public DaqTestHelper, + public MqttJsonFbHelper +{ +}; +class MqttJsonFbIntDataPTest : public ::testing::TestWithParam>>, + public DaqTestHelper, + public MqttJsonFbHelper +{ +}; +class MqttJsonFbStringTsPTest : public ::testing::TestWithParam>>, + public DaqTestHelper, + public MqttJsonFbHelper +{ +}; +} // namespace daq::modules::mqtt_streaming_client_module + +TEST_F(MqttJsonFbTest, DefaultConfig) +{ + StartUp(); + daq::DictPtr fbTypes; + daq::FunctionBlockTypePtr fbt; + daq::PropertyObjectPtr defaultConfig; + ASSERT_NO_THROW(fbTypes = device.getAvailableFunctionBlockTypes()); + ASSERT_NO_THROW(fbt = fbTypes.get(JSON_FB_NAME)); + ASSERT_NO_THROW(defaultConfig = fbt.createDefaultConfig()); + + ASSERT_TRUE(defaultConfig.assigned()); + + ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 1u); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SIGNAL_LIST)); + + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SIGNAL_LIST).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtr().getLength(), 0u); +} + +TEST_F(MqttJsonFbTest, Config) +{ + StartUp(); + auto config = device.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, VALID_JSON_0); + daq::FunctionBlockPtr jsonFb; + ASSERT_NO_THROW(jsonFb = device.addFunctionBlock(JSON_FB_NAME, config)); + + const auto allProperties = jsonFb.getAllProperties(); + ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount()); + + for (const auto& pror : config.getAllProperties()) + { + const auto propName = pror.getName(); + ASSERT_TRUE(jsonFb.hasProperty(propName)); + ASSERT_EQ(jsonFb.getPropertyValue(propName), config.getPropertyValue(propName)); + } +} + +TEST_F(MqttJsonFbTest, Creation) +{ + StartUp(); + daq::FunctionBlockPtr jsonFb; + ASSERT_NO_THROW(jsonFb = device.addFunctionBlock(JSON_FB_NAME)); + ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + ASSERT_EQ(jsonFb.getName(), JSON_FB_NAME); + auto fbs = device.getFunctionBlocks(); + bool contain = false; + daq::GenericFunctionBlockPtr fbFromList; + for (const auto& fb : fbs) + { + contain = (fb.getName() == JSON_FB_NAME); + if (contain) + { + fbFromList = fb; + break; + } + } + ASSERT_TRUE(contain); + ASSERT_TRUE(fbFromList.assigned()); + ASSERT_EQ(fbFromList.getName(), jsonFb.getName()); + ASSERT_TRUE(fbFromList == jsonFb); +} + +TEST_F(MqttJsonFbTest, CreationWithDefaultConfig) +{ + StartUp(); + daq::FunctionBlockPtr jsonFb; + ASSERT_NO_THROW(jsonFb = device.addFunctionBlock(JSON_FB_NAME)); + auto signals = jsonFb.getSignals(); + ASSERT_EQ(signals.getCount(), 0u); + ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); +} + +TEST_F(MqttJsonFbTest, CreationWithPartialConfig) +{ + // If FB has only one property, partial config is equivalent to custom config + StartUp(); + daq::FunctionBlockPtr jsonFb; + auto config = PropertyObject(); + config.addProperty(StringProperty(PROPERTY_NAME_SIGNAL_LIST, String(VALID_JSON_0))); + ASSERT_NO_THROW(jsonFb = device.addFunctionBlock(JSON_FB_NAME, config)); + ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); +} + +TEST_F(MqttJsonFbTest, CreationWithCustomConfig) +{ + // If FB has only one property, partial config is equivalent to custom config + StartUp(); + daq::FunctionBlockPtr jsonFb; + auto config = device.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, String(VALID_JSON_0)); + ASSERT_NO_THROW(jsonFb = device.addFunctionBlock(JSON_FB_NAME, config)); + ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); +} + +TEST_P(MqttJsonFbRightJsonConfigPTest, CheckNumberOfSignal) +{ + auto [configStr, signalCnt] = GetParam(); + StartUp(); + + auto configString = String(configStr); + + auto config = device.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, configString); + daq::FunctionBlockPtr jsonFb; + ASSERT_NO_THROW(jsonFb = device.addFunctionBlock(JSON_FB_NAME, config)); + auto signals = jsonFb.getSignals(); + ASSERT_EQ(signals.getCount(), signalCnt); + ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); +} + +INSTANTIATE_TEST_SUITE_P(SignalNumbersTest, + MqttJsonFbRightJsonConfigPTest, + ::testing::Values(std::make_pair(VALID_JSON_0, 4), + std::make_pair(VALID_JSON_1, 3), + std::make_pair(VALID_JSON_2, 6))); + +TEST_F(MqttJsonFbTest, SignalListWithWildcard) +{ + StartUp(); + + auto configString = String(WILDCARD_JSON_0); + + auto config = device.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, configString); + daq::FunctionBlockPtr jsonFb; + ASSERT_NO_THROW(jsonFb = device.addFunctionBlock(JSON_FB_NAME, config)); + auto signals = jsonFb.getSignals(); + ASSERT_EQ(signals.getCount(), 1u); + // TODO : check status to Warning when wildcard is used + // ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), + // Enumeration("ComponentStatusType", "Warning", daqInstance.getContext().getTypeManager())); +} + +TEST_P(MqttJsonFbWrongJsonConfigPTest, WrongJsonConfig) +{ + auto configStr = GetParam(); + StartUp(); + + auto configString = String(configStr); + + auto config = device.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, configString); + daq::FunctionBlockPtr jsonFb; + ASSERT_NO_THROW(jsonFb = device.addFunctionBlock(JSON_FB_NAME, config)); + auto signals = jsonFb.getSignals(); + ASSERT_EQ(signals.getCount(), 0u); + // TODO : check status to Error when config is invalid + // ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), + // Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); +} + +INSTANTIATE_TEST_SUITE_P(WrongJsonConfigTest, + MqttJsonFbWrongJsonConfigPTest, + ::testing::Values(INVALID_JSON_0, INVALID_JSON_1, INVALID_JSON_2, INVALID_JSON_3)); + +TEST_P(MqttJsonFbDoubleDataPTest, DataTransferOneSignalDouble) +{ + const auto dataToSend = GetParam(); + auto dataToReceive = transferData(dataToSend, VALID_JSON_CONFIG_0, VALID_JSON_DATA_0); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); +} + +TEST_P(MqttJsonFbDoubleDataPTest, DataTransferOneSignalDoubleWithoutDomain) +{ + const auto dataToSend = GetParam(); + auto dataToReceive = transferDataWithoutDomain(dataToSend, VALID_JSON_CONFIG_1, VALID_JSON_DATA_1); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); +} + +INSTANTIATE_TEST_SUITE_P(DataTransferOneSignalDouble, + MqttJsonFbDoubleDataPTest, + ::testing::Values(DATA_DOUBLE_INT_0, DATA_DOUBLE_INT_1, DATA_DOUBLE_INT_2)); + +TEST_P(MqttJsonFbIntDataPTest, DataTransferOneSignalInt) +{ + const auto dataToSend = GetParam(); + auto dataToReceive = transferData(dataToSend, VALID_JSON_CONFIG_0, VALID_JSON_DATA_0); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); +} + +TEST_P(MqttJsonFbIntDataPTest, DataTransferOneSignalIntWithoutDomain) +{ + const auto dataToSend = GetParam(); + auto dataToReceive = transferDataWithoutDomain(dataToSend, VALID_JSON_CONFIG_1, VALID_JSON_DATA_1); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); +} + +INSTANTIATE_TEST_SUITE_P(DataTransferOneSignalInt, + MqttJsonFbIntDataPTest, + ::testing::Values(DATA_INT_INT_0, DATA_INT_INT_1, DATA_INT_INT_2)); + +TEST_P(MqttJsonFbStringTsPTest, DataTransferOneSignalIntDomainString) +{ + const auto dataToSend = GetParam(); + auto dataToReceive = transferData(dataToSend, VALID_JSON_CONFIG_0, VALID_JSON_DATA_0); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); +} + +INSTANTIATE_TEST_SUITE_P(DataTransferOneSignalInt, + MqttJsonFbStringTsPTest, + ::testing::Values(DATA_DOUBLE_STR_0, DATA_DOUBLE_STR_1, DATA_DOUBLE_STR_2)); + +TEST_F(MqttJsonFbTest, DataTransferSeveralSignals) +{ + const auto topic = buildTopicName(); + const auto jsonConfig = replacePlaceholder(VALID_JSON_CONFIG_2, "", topic); + CreateJsonFB(jsonConfig); + + auto signalList = getSignals(); + ASSERT_EQ(signalList.getCount(), 3u); + + std::vector> readers; + const std::vector originalNames{"temperature", "humi", "pressure"}; + std::vector names; + for (const auto& name : originalNames) + names.emplace_back(MqttReceiverFbImpl::buildSignalNameFromTopic(topic, name)); + + for (const auto& name : names) + { + for (const auto& signal : signalList) + { + if (signal.getName().toStdString() == name) + { + readers.emplace_back(std::pair(daq::StreamReader(signal), signal)); + break; + } + } + } + ASSERT_EQ(readers.size(), 3u); + + for (int cnt = 0; cnt < DATA_DOUBLE_INT_0.size(); ++cnt) + { + auto str = VALID_JSON_DATA_2; + str = replacePlaceholder(str, "", DATA_DOUBLE_INT_0[cnt].second); + str = replacePlaceholder(str, "", DATA_DOUBLE_INT_0[cnt].first); + str = replacePlaceholder(str, "", DATA_DOUBLE_INT_1[cnt].first); + str = replacePlaceholder(str, "", DATA_DOUBLE_INT_2[cnt].first); + onSignalsMessage({topic, std::vector(str.begin(), str.end()), 1, 0}); + } + + std::vector>> dataToReceive(readers.size()); + for (int i = 0; i < readers.size(); ++i) + { + auto& [reader, signal] = readers[i]; + dataToReceive[i] = read(reader, signal.getDomainSignal().assigned()); + } + EXPECT_EQ(DATA_DOUBLE_INT_0.size(), dataToReceive[0].size()); + EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive[0])); + + EXPECT_EQ(DATA_DOUBLE_INT_1.size(), dataToReceive[1].size()); + EXPECT_TRUE(compareData(DATA_DOUBLE_INT_1, dataToReceive[1])); + + EXPECT_EQ(DATA_DOUBLE_INT_2.size(), dataToReceive[2].size()); + EXPECT_TRUE(compareData(DATA_DOUBLE_INT_2, dataToReceive[2], false)); +} + +TEST_F(MqttJsonFbTest, DataTransferMissingFieldOneSignal) +{ + const auto topic = buildTopicName(); + const auto jsonConfig = replacePlaceholder(VALID_JSON_CONFIG_0, "", topic); + CreateJsonFB(jsonConfig); + + auto reader = daq::StreamReader(getSignals()[0]); + + for (int cnt = 0; cnt < DATA_DOUBLE_INT_0.size(); ++cnt) + { + auto str = VALID_JSON_DATA_1; + str = replacePlaceholder(str, "", DATA_DOUBLE_INT_0[cnt].first); + onSignalsMessage({topic, std::vector(str.begin(), str.end()), 1, 0}); + } + std::vector> dataToReceive = read(reader, false); + ASSERT_EQ(dataToReceive.size(), 0); +} + +TEST_F(MqttJsonFbTest, DataTransferMissingFieldSeveralSignals) +{ + const auto topic = buildTopicName(); + const auto jsonConfig = replacePlaceholder(VALID_JSON_CONFIG_2, "", topic); + CreateJsonFB(jsonConfig); + + auto signalList = getSignals(); + ASSERT_EQ(signalList.getCount(), 3u); + + std::vector> readers; + const std::vector originalNames{"temperature", "humi", "pressure"}; + std::vector names; + for (const auto& name : originalNames) + names.emplace_back(MqttReceiverFbImpl::buildSignalNameFromTopic(topic, name)); + + for (const auto& name : names) + { + for (const auto& signal : signalList) + { + if (signal.getName().toStdString() == name) + { + readers.emplace_back(std::pair(daq::StreamReader(signal), signal)); + break; + } + } + } + ASSERT_EQ(readers.size(), 3u); + + for (int cnt = 0; cnt < DATA_DOUBLE_INT_0.size(); ++cnt) + { + auto str = MISSING_FIELD_JSON_DATA_2; + str = replacePlaceholder(str, "", DATA_DOUBLE_INT_0[cnt].second); + str = replacePlaceholder(str, "", DATA_DOUBLE_INT_0[cnt].first); + str = replacePlaceholder(str, "", DATA_DOUBLE_INT_2[cnt].first); + onSignalsMessage({topic, std::vector(str.begin(), str.end()), 1, 0}); + } + + std::vector>> dataToReceive(readers.size()); + for (int i = 0; i < readers.size(); ++i) + { + auto& [reader, signal] = readers[i]; + dataToReceive[i] = read(reader, signal.getDomainSignal().assigned()); + } + EXPECT_EQ(DATA_DOUBLE_INT_0.size(), dataToReceive[0].size()); + EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive[0])); + + EXPECT_EQ(0u, dataToReceive[1].size()); + + EXPECT_EQ(DATA_DOUBLE_INT_2.size(), dataToReceive[2].size()); + EXPECT_TRUE(compareData(DATA_DOUBLE_INT_2, dataToReceive[2], false)); +} + +TEST_F(MqttJsonFbTest, FullDataTransfer) +{ + const std::string topic = buildTopicName(); + const auto jsonConfig = replacePlaceholder(VALID_JSON_CONFIG_0, "", topic); + + const auto instance = Instance(); + auto device = instance.addDevice("daq.mqtt://127.0.0.1", DaqMqttDeviceConfig(100)); + + auto config = device.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); + auto singal = device.addFunctionBlock(JSON_FB_NAME, config).getSignals()[0]; + + auto reader = daq::StreamReader(singal); + + MqttAsyncClientWrapper publisher(std::make_shared(), buildClientId()); + ASSERT_TRUE(publisher.connect("127.0.0.1")); + + for (const auto& [value, ts] : DATA_DOUBLE_INT_0) + { + auto str = VALID_JSON_DATA_0; + str = replacePlaceholder(str, "", ts); + str = replacePlaceholder(str, "", value); + ASSERT_TRUE(publisher.publishMsg({topic, std::vector(str.begin(), str.end()), 1, 0})); + } + + const std::vector> dataToReceive = read(reader, true); + + ASSERT_EQ(DATA_DOUBLE_INT_0.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive)); +} diff --git a/mqtt_streaming_client_module/tests/test_mqtt_raw_fb.cpp b/mqtt_streaming_client_module/tests/test_mqtt_raw_fb.cpp new file mode 100644 index 00000000..18545199 --- /dev/null +++ b/mqtt_streaming_client_module/tests/test_mqtt_raw_fb.cpp @@ -0,0 +1,290 @@ +#include "MqttAsyncClientWrapper.h" +#include "mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h" +#include "test_daq_test_helper.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace daq; +using namespace daq::modules::mqtt_streaming_client_module; + +namespace daq::modules::mqtt_streaming_client_module +{ +class MqttRawFbTest : public testing::Test, public DaqTestHelper +{ +public: + std::unique_ptr obj; + + void onSignalsMessage(mqtt::MqttMessage& msg) + { + mqtt::MqttAsyncClient unused; + obj->onSignalsMessage(unused, msg); + } + + void CreateRawFB(std::vector topics) + { + auto config = PropertyObject(); + config.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, List())); + const auto fbType = FunctionBlockType(RAW_FB_NAME, RAW_FB_NAME, "", config); + auto topicList = List(); + for (auto& topic : topics) + { + addToList(topicList, std::move(topic)); + } + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + obj = std::make_unique(NullContext(), nullptr, fbType, "localId", nullptr, config); + } + + std::string buildTopicName() + { + return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()); + } +}; +} // namespace daq::modules::mqtt_streaming_client_module + +TEST_F(MqttRawFbTest, DefaultRawFbConfig) +{ + StartUp(); + daq::DictPtr fbTypes; + daq::FunctionBlockTypePtr fbt; + daq::PropertyObjectPtr defaultConfig; + ASSERT_NO_THROW(fbTypes = device.getAvailableFunctionBlockTypes()); + ASSERT_NO_THROW(fbt = fbTypes.get(RAW_FB_NAME)); + ASSERT_NO_THROW(defaultConfig = fbt.createDefaultConfig()); + + ASSERT_TRUE(defaultConfig.assigned()); + + ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 1u); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SIGNAL_LIST)); + + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SIGNAL_LIST).getValueType(), CoreType::ctList); + ASSERT_TRUE(defaultConfig.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtr().empty()); +} + +TEST_F(MqttRawFbTest, CreateRawFunctionalBlocks) +{ + StartUp(); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME)); + ASSERT_EQ(rawFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + ASSERT_EQ(rawFb.getName(), RAW_FB_NAME); + auto fbs = device.getFunctionBlocks(); + bool contain = false; + daq::GenericFunctionBlockPtr fbFromList; + for (const auto& fb : fbs) + { + contain = (fb.getName() == RAW_FB_NAME); + if (contain) + { + fbFromList = fb; + break; + } + } + ASSERT_TRUE(contain); + ASSERT_TRUE(fbFromList.assigned()); + ASSERT_EQ(fbFromList.getName(), rawFb.getName()); + ASSERT_TRUE(fbFromList == rawFb); +} + +TEST_F(MqttRawFbTest, CheckRawFbWithEmptyConfig) +{ + StartUp(); + daq::FunctionBlockPtr rawFb; + auto config = PropertyObject(); + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); + auto signals = rawFb.getSignals(); + ASSERT_EQ(signals.getCount(), 0u); +} + +TEST_F(MqttRawFbTest, CheckRawFbWithDefaultConfig) +{ + StartUp(); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME)); + auto signals = rawFb.getSignals(); + ASSERT_EQ(signals.getCount(), 0u); +} + +TEST_F(MqttRawFbTest, CheckRawFbWithPartialConfig) +{ + // If FB has only one property, partial config is equivalent to custom config + StartUp(); + daq::FunctionBlockPtr rawFb; + auto config = PropertyObject(); + config.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, List())); + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); +} + +TEST_F(MqttRawFbTest, CheckRawFbWithCustomConfig) +{ + // If FB has only one property, partial config is equivalent to custom config + StartUp(); + daq::FunctionBlockPtr rawFb; + auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, List(buildTopicName())); + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); +} + +TEST_F(MqttRawFbTest, CheckRawFbSignalList) +{ + constexpr uint NUM_TOPICS = 5u; + StartUp(); + + const auto topic = buildTopicName(); + auto topicList = List(); + for (int i = 0; i < NUM_TOPICS; ++i) + { + addToList(topicList, fmt::format("{}_{}", topic, i)); + } + auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); + auto signals = rawFb.getSignals(); + ASSERT_EQ(signals.getCount(), NUM_TOPICS); +} + +TEST_F(MqttRawFbTest, CheckRawFbSignalListWithWildcard) +{ + StartUp(); + + auto topicList = List(); + addToList(topicList, ""); + addToList(topicList, "goodTopic/test/topic"); + addToList(topicList, "badTopic/+/test/topic"); + addToList(topicList, "badTopic/+/+/topic"); + addToList(topicList, "badTopic/#"); + addToList(topicList, "goodTopic/test/newTopic"); + + auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); + auto signals = rawFb.getSignals(); + ASSERT_EQ(signals.getCount(), 2u); +} + +TEST_F(MqttRawFbTest, CheckRawFbConfig) +{ + constexpr uint NUM_TOPICS = 5u; + StartUp(); + + const auto topic = buildTopicName(); + auto topicList = List(); + for (int i = 0; i < NUM_TOPICS; ++i) + { + addToList(topicList, fmt::format("{}_{}", topic, i)); + } + auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + daq::FunctionBlockPtr rawFb; + ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); + + const auto allProperties = rawFb.getAllProperties(); + ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount()); + + for (const auto& pror : config.getAllProperties()) + { + const auto propName = pror.getName(); + ASSERT_TRUE(rawFb.hasProperty(propName)); + ASSERT_EQ(rawFb.getPropertyValue(propName), config.getPropertyValue(propName)); + } +} + +TEST_F(MqttRawFbTest, CheckRawFbDataTransfer) +{ + const auto topic = buildTopicName(); + const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, + std::vector{0x11, 0x12, 0x13, 0x14}, + std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, + std::vector{0x31}, + std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; + std::vector> dataToReceive; + + CreateRawFB({topic}); + + auto signalList = List(); + obj->getSignals(&signalList); + auto reader = daq::PacketReader(signalList[0]); + + for (const auto& data : dataToSend) + { + mqtt::MqttMessage msg = {topic, data, 1, 0}; + onSignalsMessage(msg); + } + + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.push_back(std::move(readData)); + } + } + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_EQ(dataToSend, dataToReceive); +} + +TEST_F(MqttRawFbTest, CheckRawFbFullDataTransfer) +{ + const std::string topic = buildTopicName(); + + StartUp(); + + auto topicList = List(); + addToList(topicList, topic); + auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + auto singal = device.addFunctionBlock(RAW_FB_NAME, config).getSignals()[0]; + auto reader = daq::PacketReader(singal); + + MqttAsyncClientWrapper publisher(std::make_shared(), "testPublisherId"); + ASSERT_TRUE(publisher.connect("127.0.0.1")); + + const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, + std::vector{0x11, 0x12, 0x13, 0x14}, + std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, + std::vector{0x31}, + std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; + std::vector> dataToReceive; + + for (const auto& data : dataToSend) + { + mqtt::MqttMessage msg = {topic, data, 1, 0}; + ASSERT_TRUE(publisher.publishMsg(msg)); + } + + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.push_back(std::move(readData)); + } + } + + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_EQ(dataToSend, dataToReceive); +} diff --git a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp index 89896268..6a7fbf03 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp @@ -1,63 +1,21 @@ -#include "mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h" +#include "test_daq_test_helper.h" +#include #include #include #include -#include -#include -#include -#include - -#include -#include - -#include -#include #include -#include -#include "MqttAsyncClientWrapper.h" +#include using namespace daq; using namespace daq::modules::mqtt_streaming_client_module; + namespace daq::modules::mqtt_streaming_client_module { -class MqttStreamingClientModuleTest : public testing::Test +class MqttStreamingClientModuleTest : public testing::Test, public DaqTestHelper { -public: - std::unique_ptr obj; - - void onSignalsMessage(mqtt::MqttMessage& msg) - { - mqtt::MqttAsyncClient unused; - obj->onSignalsMessage(unused, msg); - } - - void CreateRawFB(std::vector topics) - { - auto config = PropertyObject(); - config.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, List())); - const auto fbType = FunctionBlockType(RAW_FB_NAME, RAW_FB_NAME, "", config); - auto topicList = List(); - for (auto& topic : topics) - { - addToList(topicList, std::move(topic)); - } - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); - obj = std::make_unique(NullContext(), nullptr, fbType, "localId", nullptr, config); - } - - std::string buildTopicName() { - return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()); - } }; } // namespace daq::modules::mqtt_streaming_client_module -static ModulePtr CreateModule() -{ - ModulePtr module; - createModule(&module, NullContext()); - return module; -} - TEST_F(MqttStreamingClientModuleTest, CreateModule) { IModule* module = nullptr; @@ -156,316 +114,3 @@ TEST_F(MqttStreamingClientModuleTest, GetAvailableComponentTypes) ASSERT_EQ(versionInfoDeviceType.getPatch(), MQTT_STREAM_CLI_MODULE_PATCH_VERSION); } } - -TEST_F(MqttStreamingClientModuleTest, DefaultDeviceConfig) -{ - const auto module = CreateModule(); - - DictPtr deviceTypes; - ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); - ASSERT_EQ(deviceTypes.getCount(), 1u); - - ASSERT_TRUE(deviceTypes.hasKey(DaqMqttDeviceTypeId)); - auto defaultConfig = deviceTypes.get(DaqMqttDeviceTypeId).createDefaultConfig(); - ASSERT_TRUE(defaultConfig.assigned()); - - ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 6u); - - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_PORT)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_USERNAME)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_PASSWORD)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_CONNECT_TIMEOUT)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT)); - - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_PORT).getValueType(), CoreType::ctInt); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_USERNAME).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_PASSWORD).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_CONNECT_TIMEOUT).getValueType(), CoreType::ctInt); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT).getValueType(), CoreType::ctInt); - - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS), DEFAULT_BROKER_ADDRESS); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT), DEFAULT_PORT); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME), DEFAULT_USERNAME); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD), DEFAULT_PASSWORD); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CONNECT_TIMEOUT), DEFAULT_INIT_DELAY); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_DISCOVERY_TIMEOUT), DEFAULT_DISCOVERY_DELAY); -} - -TEST_F(MqttStreamingClientModuleTest, CreatingDevice) -{ - const auto instance = Instance(); - daq::GenericDevicePtr device; - ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1")); - ASSERT_EQ(device.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); - ASSERT_EQ(device.getInfo().getName(), MQTT_DEVICE_NAME); - auto devices = instance.getDevices(); - bool contain = false; - daq::GenericDevicePtr deviceFromList; - for (const auto& d : devices) - { - contain = (d.getName() == MQTT_DEVICE_NAME); - if (contain) - { - deviceFromList = d; - break; - } - } - ASSERT_TRUE(contain); - ASSERT_TRUE(deviceFromList.assigned()); - ASSERT_EQ(deviceFromList.getInfo().getName(), device.getInfo().getName()); - ASSERT_TRUE(deviceFromList == device); -} - -TEST_F(MqttStreamingClientModuleTest, CreatingSeveralDevices) -{ - const auto instance = Instance(); - daq::GenericDevicePtr device; - ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1")); - ASSERT_EQ(device.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); - ASSERT_EQ(device.getInfo().getName(), MQTT_DEVICE_NAME); - daq::GenericDevicePtr anotherDevice; - ASSERT_THROW(anotherDevice = instance.addDevice("daq.mqtt://127.0.0.1"), AlreadyExistsException); -} - -TEST_F(MqttStreamingClientModuleTest, RemovingDevice) -{ - const auto instance = Instance(); - daq::GenericDevicePtr device; - ASSERT_NO_THROW(device = instance.addDevice("daq.mqtt://127.0.0.1")); - ASSERT_NO_THROW(instance.removeDevice(device)); -} - -TEST_F(MqttStreamingClientModuleTest, CheckDeviceFunctionalBlocks) -{ - const auto instance = Instance(); - auto device = instance.addDevice("daq.mqtt://127.0.0.1"); - daq::DictPtr fbTypes; - ASSERT_NO_THROW(fbTypes = device.getAvailableFunctionBlockTypes()); - ASSERT_GE(fbTypes.getCount(), 2); - ASSERT_TRUE(fbTypes.hasKey(RAW_FB_NAME)); - ASSERT_TRUE(fbTypes.hasKey(JSON_FB_NAME)); -} - -TEST_F(MqttStreamingClientModuleTest, DefaultRawFbConfig) -{ - const auto instance = Instance(); - auto device = instance.addDevice("daq.mqtt://127.0.0.1"); - daq::DictPtr fbTypes; - daq::FunctionBlockTypePtr fbt; - daq::PropertyObjectPtr defaultConfig; - ASSERT_NO_THROW(fbTypes = device.getAvailableFunctionBlockTypes()); - ASSERT_NO_THROW(fbt = fbTypes.get(RAW_FB_NAME)); - ASSERT_NO_THROW(defaultConfig = fbt.createDefaultConfig()); - - ASSERT_TRUE(defaultConfig.assigned()); - - ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 1u); - - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SIGNAL_LIST)); - - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SIGNAL_LIST).getValueType(), CoreType::ctList); - ASSERT_TRUE(defaultConfig.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtr().empty()); -} - -TEST_F(MqttStreamingClientModuleTest, CreateRawFunctionalBlocks) -{ - const auto instance = Instance(); - auto device = instance.addDevice("daq.mqtt://127.0.0.1"); - daq::FunctionBlockPtr rawFb; - ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME)); - ASSERT_EQ(rawFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); - ASSERT_EQ(rawFb.getName(), RAW_FB_NAME); - auto fbs = device.getFunctionBlocks(); - bool contain = false; - daq::GenericFunctionBlockPtr fbFromList; - for (const auto& fb : fbs) - { - contain = (fb.getName() == RAW_FB_NAME); - if (contain) - { - fbFromList = fb; - break; - } - } - ASSERT_TRUE(contain); - ASSERT_TRUE(fbFromList.assigned()); - ASSERT_EQ(fbFromList.getName(), rawFb.getName()); - ASSERT_TRUE(fbFromList == rawFb); -} - -TEST_F(MqttStreamingClientModuleTest, CheckRawFbEmptySignalList) -{ - const auto instance = Instance(); - auto device = instance.addDevice("daq.mqtt://127.0.0.1"); - daq::FunctionBlockPtr rawFb; - ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME)); - auto signals = rawFb.getSignals(); - ASSERT_EQ(signals.getCount(), 0u); -} - -TEST_F(MqttStreamingClientModuleTest, CheckRawFbSignalList) -{ - constexpr uint NUM_TOPICS = 5u; - const auto instance = Instance(); - auto device = instance.addDevice("daq.mqtt://127.0.0.1"); - - const auto topic = buildTopicName(); - auto topicList = List(); - for (int i = 0; i < NUM_TOPICS; ++i) - { - addToList(topicList, fmt::format("{}_{}", topic, i)); - } - auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); - daq::FunctionBlockPtr rawFb; - ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); - auto signals = rawFb.getSignals(); - ASSERT_EQ(signals.getCount(), NUM_TOPICS); -} - -TEST_F(MqttStreamingClientModuleTest, CheckRawFbSignalListWithWildcard) -{ - const auto instance = Instance(); - auto device = instance.addDevice("daq.mqtt://127.0.0.1"); - - auto topicList = List(); - addToList(topicList, ""); - addToList(topicList, "goodTopic/test/topic"); - addToList(topicList, "badTopic/+/test/topic"); - addToList(topicList, "badTopic/+/+/topic"); - addToList(topicList, "badTopic/#"); - addToList(topicList, "goodTopic/test/newTopic"); - - auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); - daq::FunctionBlockPtr rawFb; - ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); - auto signals = rawFb.getSignals(); - ASSERT_EQ(signals.getCount(), 2u); -} - -TEST_F(MqttStreamingClientModuleTest, CheckRawFbConfig) -{ - constexpr uint NUM_TOPICS = 5u; - const auto instance = Instance(); - auto device = instance.addDevice("daq.mqtt://127.0.0.1"); - - const auto topic = buildTopicName(); - auto topicList = List(); - for (int i = 0; i < NUM_TOPICS; ++i) - { - addToList(topicList, fmt::format("{}_{}", topic, i)); - } - auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); - daq::FunctionBlockPtr rawFb; - ASSERT_NO_THROW(rawFb = device.addFunctionBlock(RAW_FB_NAME, config)); - - const auto allProperties = rawFb.getAllProperties(); - ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount()); - - for (const auto& pror : config.getAllProperties()) - { - const auto propName = pror.getName(); - ASSERT_TRUE(rawFb.hasProperty(propName)); - ASSERT_EQ(rawFb.getPropertyValue(propName), config.getPropertyValue(propName)); - } -} - -TEST_F(MqttStreamingClientModuleTest, CheckRawFbDataTransfer) -{ - const auto topic = buildTopicName(); - const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, - std::vector{0x11, 0x12, 0x13, 0x14}, - std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, - std::vector{0x31}, - std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; - std::vector> dataToReceive; - - CreateRawFB({topic}); - - auto signalList = List(); - obj->getSignals(&signalList); - auto reader = daq::PacketReader(signalList[0]); - - for (const auto& data : dataToSend) - { - mqtt::MqttMessage msg = {topic, data, 1, 0}; - onSignalsMessage(msg); - } - - while (!reader.getEmpty()) - { - auto packet = reader.read(); - if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) - { - continue; - } - if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) - { - std::vector readData(dataPacket.getDataSize()); - memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); - dataToReceive.push_back(std::move(readData)); - } - } - ASSERT_EQ(dataToSend.size(), dataToReceive.size()); - ASSERT_EQ(dataToSend, dataToReceive); -} - -TEST_F(MqttStreamingClientModuleTest, CheckRawFbFullDataTransfer) -{ - const std::string topic = buildTopicName(); - - const auto instance = Instance(); - auto device = instance.addDevice("daq.mqtt://127.0.0.1"); - - auto topicList = List(); - addToList(topicList, topic); - auto config = device.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); - auto singal = device.addFunctionBlock(RAW_FB_NAME, config).getSignals()[0]; - auto reader = daq::PacketReader(singal); - - MqttAsyncClientWrapper publisher(std::make_shared(), "testPublisherId"); - ASSERT_TRUE(publisher.connect("127.0.0.1")); - - const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, - std::vector{0x11, 0x12, 0x13, 0x14}, - std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, - std::vector{0x31}, - std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; - std::vector> dataToReceive; - - for (const auto& data : dataToSend) - { - mqtt::MqttMessage msg = {topic, data, 1, 0}; - ASSERT_TRUE(publisher.publishMsg(msg)); - } - - while (!reader.getEmpty()) - { - auto packet = reader.read(); - if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) - { - continue; - } - if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) - { - std::vector readData(dataPacket.getDataSize()); - memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); - dataToReceive.push_back(std::move(readData)); - } - } - - ASSERT_EQ(dataToSend.size(), dataToReceive.size()); - ASSERT_EQ(dataToSend, dataToReceive); -} From 6e144e5a5ce126869eb35e19cbc8f61f807d2204 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 16:41:52 +0100 Subject: [PATCH 28/55] mqtt: tests for timestamp parsing --- .../tests/test_mqtt_streaming_protocol.cpp | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp index 71e76463..0cbe6184 100644 --- a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -1,10 +1,11 @@ #include "MqttAsyncClient.h" +#include "MqttAsyncClientWrapper.h" +#include "Timer.h" +#include "timestampConverter.h" #include #include #include #include -#include "Timer.h" -#include "MqttAsyncClientWrapper.h" using namespace mqtt; using namespace std::chrono; @@ -25,6 +26,9 @@ class MqttStreamingProtocolTest : public ::testing::Test, public MqttAsyncClien } }; +using JsonTimestampParsingIntPTest = ::testing::TestWithParam>; +using JsonTimestampParsingStrPTest = ::testing::TestWithParam>; + TEST_F(MqttStreamingProtocolTest, Connection) { auto ok = createConnection("127.0.0.1", clientId); @@ -285,3 +289,35 @@ TEST_F(MqttStreamingProtocolTest, PublishingWithoutConnection) auto ok = instance->publish(topic, (void *)(data.c_str()), data.size(), nullptr, 1, &token, false); ASSERT_FALSE(ok); } + +TEST_P(JsonTimestampParsingIntPTest, IntTimestampParsing) +{ + auto [input, output] = GetParam(); + ASSERT_EQ(mqtt::utils::numericToMicroseconds(input), output); +} + +INSTANTIATE_TEST_SUITE_P(IntTimestampParsing, + JsonTimestampParsingIntPTest, + ::testing::Values(std::pair(1761664976, 1761664976000000ULL), // seconds + std::pair(1761664976123, 1761664976123000ULL), // milliseconds + std::pair(1761664976123456, 1761664976123456ULL), // microseconds + std::pair(1761664976123456789, 1761664976123456ULL) // nanoseconds + )); + +TEST_P(JsonTimestampParsingStrPTest, StrTimestampParsing) +{ + auto [input, output] = GetParam(); + ASSERT_EQ(mqtt::utils::toUnixTicks(input), output); +} + +INSTANTIATE_TEST_SUITE_P(StrTimestampParsing, + JsonTimestampParsingStrPTest, + ::testing::Values(std::pair("2025-10-28 15:22:56", 1761664976000000ULL), + std::pair(" 2025-10-28 15:22:56 ", 1761664976000000ULL), + std::pair(" 2025-10-28 15:22:56.123", 1761664976123000ULL), + std::pair(" 2025-10-28T15:22:56Z ", 1761664976000000ULL), + std::pair(" 1761664976", 1761664976000000ULL), // seconds + std::pair("1761664976123 ", 1761664976123000ULL), // milliseconds + std::pair(" 1761664976123456 ", 1761664976123456ULL), // microseconds + std::pair(" 1761664976123456789 ", 1761664976123456ULL) // nanoseconds + )); From fe07c7206ea15240453b4cc0ab4b6a0fd95ef285 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 17:15:25 +0100 Subject: [PATCH 29/55] mqtt: tests for cheching a signal Unit in the JSON FB --- .../tests/test_data.h | 45 +++++++++++++++++++ .../tests/test_mqtt_json_fb.cpp | 28 ++++++++++++ 2 files changed, 73 insertions(+) diff --git a/mqtt_streaming_client_module/tests/test_data.h b/mqtt_streaming_client_module/tests/test_data.h index 0615dab5..c1ae83af 100644 --- a/mqtt_streaming_client_module/tests/test_data.h +++ b/mqtt_streaming_client_module/tests/test_data.h @@ -315,6 +315,51 @@ inline const std::string MISSING_FIELD_JSON_DATA_2 = R"json({ } )json"; +inline const std::string VALID_JSON_CONFIG_3 = R"json({ + :[ + { + "temperature":{ + "Value":"value", + "Unit":[ + "rpm" + ] + } + } + ] +} +)json"; + +inline const std::string VALID_JSON_CONFIG_4 = R"json({ + :[ + { + "temperature":{ + "Value":"value", + "Unit":[ + "rpm", + "rotations per minute" + ] + } + } + ] +} +)json"; + +inline const std::string VALID_JSON_CONFIG_5 = R"json({ + :[ + { + "temperature":{ + "Value":"value", + "Unit":[ + "rpm", + "rotations per minute", + "rotational speed" + ] + } + } + ] +} +)json"; + inline const std::vector> DATA_DOUBLE_INT_0 = {{23.50000001, 1761567115}, {-0.00000005583, 1761567116}, {19.84916651651, 1761567117}, diff --git a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp index c9ac4dc5..022a678f 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp @@ -243,6 +243,11 @@ class MqttJsonFbStringTsPTest : public ::testing::TestWithParam>>, + public DaqTestHelper, + public MqttJsonFbHelper +{ +}; } // namespace daq::modules::mqtt_streaming_client_module TEST_F(MqttJsonFbTest, DefaultConfig) @@ -618,3 +623,26 @@ TEST_F(MqttJsonFbTest, FullDataTransfer) ASSERT_EQ(DATA_DOUBLE_INT_0.size(), dataToReceive.size()); ASSERT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive)); } + +TEST_P(MqttJsonFbUnitPTest, SignalUnit) +{ + const auto [config, unitDetails] = GetParam(); + ASSERT_EQ(unitDetails.size(), 3u); + const auto topic = buildTopicName(); + const auto jsonConfig = replacePlaceholder(config, "", topic); + CreateJsonFB(jsonConfig); + + auto signalList = getSignals(); + ASSERT_EQ(signalList.getCount(), 1u); + ASSERT_TRUE(signalList[0].getDescriptor().assigned()); + ASSERT_TRUE(signalList[0].getDescriptor().getUnit().assigned()); + EXPECT_EQ(signalList[0].getDescriptor().getUnit().getSymbol(), unitDetails[0]); + EXPECT_EQ(signalList[0].getDescriptor().getUnit().getName(), unitDetails[1]); + EXPECT_EQ(signalList[0].getDescriptor().getUnit().getQuantity(), unitDetails[2]); +} + +INSTANTIATE_TEST_SUITE_P(SignalUnit, + MqttJsonFbUnitPTest, + ::testing::Values(std::pair>{VALID_JSON_CONFIG_3, {"rpm", "", ""}}, + std::pair>{VALID_JSON_CONFIG_4, {"rpm", "rotations per minute", ""}}, + std::pair>{VALID_JSON_CONFIG_5, {"rpm", "rotations per minute", "rotational speed"}})); From b1607979cdaf83457a7964ff03edfe4b977bf02e Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 17:35:05 +0100 Subject: [PATCH 30/55] mqtt: renaming --- .../include/mqtt_streaming_client_module/constants.h | 4 ++-- .../src/mqtt_streaming_client_module_impl.cpp | 4 ++-- mqtt_streaming_client_module/tests/test_mqtt_device.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h index 50e7cc53..d7547e25 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h @@ -19,8 +19,8 @@ static constexpr const char* DEFAULT_BROKER_ADDRESS = "127.0.0.1"; static constexpr uint16_t DEFAULT_PORT = 1883; static constexpr const char* DEFAULT_USERNAME = ""; static constexpr const char* DEFAULT_PASSWORD = ""; -static constexpr uint32_t DEFAULT_INIT_DELAY = 3000; // ms -static constexpr uint32_t DEFAULT_DISCOVERY_DELAY = 3000; // ms +static constexpr uint32_t DEFAULT_INIT_TIMEOUT = 3000; // ms +static constexpr uint32_t DEFAULT_DISCOVERY_TIMEOUT = 3000; // ms static constexpr const char* PROPERTY_NAME_MQTT_BROKER_ADDRESS = "MqttBrokerAddress"; static constexpr const char* PROPERTY_NAME_MQTT_BROKER_PORT = "MqttBrokerPort"; diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp index a6dfbdc5..c5419cac 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp @@ -248,8 +248,8 @@ PropertyObjectPtr MqttStreamingClientModule::createDefaultConfig() config.addProperty(StringProperty(PROPERTY_NAME_MQTT_USERNAME, DEFAULT_USERNAME)); config.addProperty(StringProperty(PROPERTY_NAME_MQTT_PASSWORD, DEFAULT_PASSWORD)); config.addProperty(IntProperty(PROPERTY_NAME_MQTT_BROKER_PORT, DEFAULT_PORT)); - config.addProperty(IntProperty(PROPERTY_NAME_CONNECT_TIMEOUT, DEFAULT_INIT_DELAY)); - config.addProperty(IntProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT, DEFAULT_DISCOVERY_DELAY)); + config.addProperty(IntProperty(PROPERTY_NAME_CONNECT_TIMEOUT, DEFAULT_INIT_TIMEOUT)); + config.addProperty(IntProperty(PROPERTY_NAME_DISCOVERY_TIMEOUT, DEFAULT_DISCOVERY_TIMEOUT)); return config; } diff --git a/mqtt_streaming_client_module/tests/test_mqtt_device.cpp b/mqtt_streaming_client_module/tests/test_mqtt_device.cpp index 6bea4ebd..5b2f7894 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_device.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_device.cpp @@ -49,8 +49,8 @@ TEST_F(MqttDeviceTest, DefaultDeviceConfig) ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT), DEFAULT_PORT); ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME), DEFAULT_USERNAME); ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD), DEFAULT_PASSWORD); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CONNECT_TIMEOUT), DEFAULT_INIT_DELAY); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_DISCOVERY_TIMEOUT), DEFAULT_DISCOVERY_DELAY); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CONNECT_TIMEOUT), DEFAULT_INIT_TIMEOUT); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_DISCOVERY_TIMEOUT), DEFAULT_DISCOVERY_TIMEOUT); } TEST_F(MqttDeviceTest, CreatingDeviceWithDefaultConfig) From feebdc0a5d60943dcd0d0fd1a10721970e65bbe3 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 28 Oct 2025 19:13:40 +0100 Subject: [PATCH 31/55] mqtt: helper for the module; filling in missing FBs properties with default values --- .../mqtt_streaming_client_module/helper.h | 27 +++++++++++++++ .../mqtt_raw_receiver_fb_impl.h | 3 -- .../mqtt_receiver_fb_impl.h | 3 -- .../mqtt_streaming_client_module_impl.h | 1 + .../src/CMakeLists.txt | 4 +++ mqtt_streaming_client_module/src/helper.cpp | 34 +++++++++++++++++++ .../src/mqtt_raw_receiver_fb_impl.cpp | 16 ++++----- .../src/mqtt_receiver_fb_impl.cpp | 22 +++--------- .../src/mqtt_streaming_client_module_impl.cpp | 11 ++---- .../tests/test_mqtt_json_fb.cpp | 5 +-- .../test_mqtt_streaming_client_module.cpp | 29 ++++++++++++++++ 11 files changed, 112 insertions(+), 43 deletions(-) create mode 100644 mqtt_streaming_client_module/include/mqtt_streaming_client_module/helper.h create mode 100644 mqtt_streaming_client_module/src/helper.cpp diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/helper.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/helper.h new file mode 100644 index 00000000..912c78da --- /dev/null +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/helper.h @@ -0,0 +1,27 @@ +/* + * Copyright 2022-2025 openDAQ d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +std::string buildSignalNameFromTopic(std::string topic, const std::string& signalName); +std::string buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName); +PropertyObjectPtr populateDefaultConfig(const PropertyObjectPtr& defaultConfig, const PropertyObjectPtr& config); + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h index b4cd537b..0e9a9dc4 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h @@ -17,7 +17,6 @@ #pragma once #include #include - #include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE @@ -34,8 +33,6 @@ class MqttRawReceiverFbImpl final : public FunctionBlock const PropertyObjectPtr& config = nullptr); ~MqttRawReceiverFbImpl() override; - static std::string buildSignalNameFromTopic(std::string topic); - private: std::unordered_map outputSignals; diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h index 5b0b609b..7a2beab5 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -36,9 +36,6 @@ class MqttReceiverFbImpl final : public FunctionBlock const PropertyObjectPtr& config = nullptr); ~MqttReceiverFbImpl() override; - static std::string buildSignalNameFromTopic(std::string topic, const std::string& signalName); - static std::string buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName); - private: mqtt::MqttDataWrapper jsonDataWorker; std::unordered_map outputSignals; diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h index afa6453a..815ebee3 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h @@ -23,6 +23,7 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE class MqttStreamingClientModule final : public Module { + friend class MqttStreamingClientModuleTest; public: MqttStreamingClientModule(ContextPtr context); diff --git a/mqtt_streaming_client_module/src/CMakeLists.txt b/mqtt_streaming_client_module/src/CMakeLists.txt index 6f8958aa..6b136dd7 100644 --- a/mqtt_streaming_client_module/src/CMakeLists.txt +++ b/mqtt_streaming_client_module/src/CMakeLists.txt @@ -8,6 +8,7 @@ set(SRC_Include common.h mqtt_receiver_fb_impl.h mqtt_raw_receiver_fb_impl.h constants.h + helper.h ) set(SRC_Srcs module_dll.cpp @@ -15,12 +16,15 @@ set(SRC_Srcs module_dll.cpp mqtt_streaming_device_impl.cpp mqtt_receiver_fb_impl.cpp mqtt_raw_receiver_fb_impl.cpp + helper.cpp ) prepend_include(${TARGET_FOLDER_NAME} SRC_Include) source_group("common" FILES ${MODULE_HEADERS_DIR}/common.h ${MODULE_HEADERS_DIR}/constants.h + ${MODULE_HEADERS_DIR}/helper.h + helper.cpp ) source_group("module" FILES ${MODULE_HEADERS_DIR}/mqtt_streaming_client_module_impl.h diff --git a/mqtt_streaming_client_module/src/helper.cpp b/mqtt_streaming_client_module/src/helper.cpp new file mode 100644 index 00000000..f827c2da --- /dev/null +++ b/mqtt_streaming_client_module/src/helper.cpp @@ -0,0 +1,34 @@ +#include "mqtt_streaming_client_module/helper.h" +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +std::string buildSignalNameFromTopic(std::string topic, const std::string& signalName) +{ + boost::replace_all(topic, "/", "_"); + topic += "_Mqtt" + signalName; + return topic; +} + +std::string buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName) +{ + boost::replace_all(topic, "/", "_"); + topic += std::string("_Mqtt") + "_domain" + signalName; + return topic; +} + +PropertyObjectPtr populateDefaultConfig(const PropertyObjectPtr& defaultConfig, const PropertyObjectPtr& config) +{ + auto newConfig = PropertyObject(); + for (const auto& prop : defaultConfig.getAllProperties()) + { + newConfig.addProperty(prop.asPtr(true).clone()); + const auto propName = prop.getName(); + newConfig.setPropertyValue(propName, config.hasProperty(propName) ? config.getPropertyValue(propName) : prop.getValue()); + } + return newConfig; +} + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index 4000169d..53cd9175 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -1,4 +1,5 @@ #include "mqtt_streaming_client_module/constants.h" +#include "mqtt_streaming_client_module/helper.h" #include "MqttDataWrapper.h" #include #include @@ -15,7 +16,11 @@ MqttRawReceiverFbImpl::MqttRawReceiverFbImpl(const ContextPtr& ctx, : FunctionBlock(type, ctx, parent, localId), subscriber(subscriber) { initComponentStatus(); - initProperties(config.assigned() ? config : type.createDefaultConfig()); + if (config.assigned()) + initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); + else + initProperties(type.createDefaultConfig()); + createSignals(); subscribeToTopics(); @@ -110,17 +115,10 @@ void MqttRawReceiverFbImpl::createSignals() const auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::Binary).build(); outputSignals.emplace( - std::make_pair(topic, createAndAddSignal(buildSignalNameFromTopic(topic), signalDsc))); + std::make_pair(topic, createAndAddSignal(buildSignalNameFromTopic(topic, ""), signalDsc))); } } -std::string MqttRawReceiverFbImpl::buildSignalNameFromTopic(std::string topic) -{ - boost::replace_all(topic, "/", "_"); - topic += "_Mqtt"; - return topic; -} - void MqttRawReceiverFbImpl::subscribeToTopics() { if (!subscriber) diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index 0718020e..ba8cee72 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -1,6 +1,7 @@ -#include -#include #include "mqtt_streaming_client_module/constants.h" +#include +#include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE @@ -17,9 +18,10 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, initComponentStatus(); if (config.assigned()) - initProperties(config); + initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); else initProperties(type.createDefaultConfig()); + createSignals(); if (subscriber) @@ -138,20 +140,6 @@ void MqttReceiverFbImpl::createSignals() jsonDataWorker.setOutputSignals(&outputSignals); } -std::string MqttReceiverFbImpl::buildSignalNameFromTopic(std::string topic, const std::string& signalName) -{ - boost::replace_all(topic, "/", "_"); - topic += "_Mqtt_" + signalName; - return topic; -} - -std::string MqttReceiverFbImpl::buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName) -{ - boost::replace_all(topic, "/", "_"); - topic += std::string("_Mqtt") + "_domain" + signalName; - return topic; -} - std::set MqttReceiverFbImpl::getSubscribedTopics() const { auto lock = std::lock_guard(sync); diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp index c5419cac..7f3cfe68 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -103,15 +104,7 @@ MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionString, con PropertyObjectPtr MqttStreamingClientModule::populateDefaultConfig(const PropertyObjectPtr& config) { - const auto defConfig = createDefaultConfig(); - for (const auto& prop : defConfig.getAllProperties()) - { - const auto name = prop.getName(); - if (config.hasProperty(name)) - defConfig.setPropertyValue(name, config.getPropertyValue(name)); - } - - return defConfig; + return ::daq::modules::mqtt_streaming_client_module::populateDefaultConfig(createDefaultConfig(), config); } void MqttStreamingClientModule::extractConnectionParams(const StringPtr& connectionString, diff --git a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp index 022a678f..adfc88fc 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp @@ -1,4 +1,5 @@ #include "MqttAsyncClientWrapper.h" +#include "mqtt_streaming_client_module/helper.h" #include "mqtt_streaming_client_module/mqtt_receiver_fb_impl.h" #include "test_daq_test_helper.h" #include "test_data.h" @@ -481,7 +482,7 @@ TEST_F(MqttJsonFbTest, DataTransferSeveralSignals) const std::vector originalNames{"temperature", "humi", "pressure"}; std::vector names; for (const auto& name : originalNames) - names.emplace_back(MqttReceiverFbImpl::buildSignalNameFromTopic(topic, name)); + names.emplace_back(buildSignalNameFromTopic(topic, name)); for (const auto& name : names) { @@ -553,7 +554,7 @@ TEST_F(MqttJsonFbTest, DataTransferMissingFieldSeveralSignals) const std::vector originalNames{"temperature", "humi", "pressure"}; std::vector names; for (const auto& name : originalNames) - names.emplace_back(MqttReceiverFbImpl::buildSignalNameFromTopic(topic, name)); + names.emplace_back(buildSignalNameFromTopic(topic, name)); for (const auto& name : names) { diff --git a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp index 6a7fbf03..05a2c58f 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -13,6 +15,11 @@ namespace daq::modules::mqtt_streaming_client_module { class MqttStreamingClientModuleTest : public testing::Test, public DaqTestHelper { +public: + static PropertyObjectPtr createDefaultConfig() + { + return MqttStreamingClientModule::createDefaultConfig(); + } }; } // namespace daq::modules::mqtt_streaming_client_module @@ -114,3 +121,25 @@ TEST_F(MqttStreamingClientModuleTest, GetAvailableComponentTypes) ASSERT_EQ(versionInfoDeviceType.getPatch(), MQTT_STREAM_CLI_MODULE_PATCH_VERSION); } } + +TEST_F(MqttStreamingClientModuleTest, ConfigFilling) +{ + const auto defConfig = createDefaultConfig(); + auto customConfig = PropertyObject(); + customConfig.addProperty(StringProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS, "testBrokerAddress.com")); + customConfig.addProperty(IntProperty(PROPERTY_NAME_CONNECT_TIMEOUT, 123456)); + auto newConfig = ::daq::modules::mqtt_streaming_client_module::populateDefaultConfig(defConfig, customConfig); + + ASSERT_EQ(defConfig.getAllProperties().getCount(), newConfig.getAllProperties().getCount()); + for (const auto& prop : defConfig.getAllProperties()) + { + if (customConfig.hasProperty(prop.getName())) + { + EXPECT_EQ(newConfig.getPropertyValue(prop.getName()), customConfig.getPropertyValue(prop.getName())); + } + else + { + EXPECT_EQ(newConfig.getPropertyValue(prop.getName()), prop.getValue()); + } + } +} From 40e5008fd294176baec86d4c7d316e355b53d547 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 29 Oct 2025 13:56:14 +0100 Subject: [PATCH 32/55] mqtt: implementing the use of a port for broker connection --- .../src/mqtt_streaming_device_impl.cpp | 5 +++-- .../src/mqtt_streaming_server_impl.cpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index c2168756..c1392bd5 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -97,7 +97,8 @@ void MqttStreamingDeviceImpl::initBaseFunctionalBlocks() void MqttStreamingDeviceImpl::initMqttSubscriber() { - subscriber->setServerURL(connectionSettings.mqttUrl); + const auto serverUrl = connectionSettings.mqttUrl + ((connectionSettings.port > 0) ? ":" + std::to_string(connectionSettings.port) : ""); + subscriber->setServerURL(serverUrl); subscriber->setClientId(connectionSettings.clientId); subscriber->setUsernamePasswrod(connectionSettings.username, connectionSettings.password); @@ -115,7 +116,7 @@ void MqttStreamingDeviceImpl::initMqttSubscriber() } }); - LOG_I("MQTT: Trying to connect to MQTT broker ({})", connectionSettings.mqttUrl); + LOG_I("MQTT: Trying to connect to MQTT broker ({})", serverUrl); subscriber->connect(); } diff --git a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp index b48ba788..8496d8af 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -65,12 +65,13 @@ MqttStreamingServerImpl::~MqttStreamingServerImpl() void MqttStreamingServerImpl::setupMqttPublisher() { publisher.disconnect(); - publisher.setServerURL(connectionSettings.mqttUrl); + const auto serverUrl = connectionSettings.mqttUrl + ((connectionSettings.port > 0) ? ":" + std::to_string(connectionSettings.port) : ""); + publisher.setServerURL(serverUrl); publisher.setClientId(connectionSettings.clientId); publisher.setUsernamePasswrod(connectionSettings.username, connectionSettings.password); publisher.setConnectedCb([this]() { LOG_I("MQTT: Connection established"); }); - LOG_I("MQTT: Trying to connect to MQTT broker ({})", connectionSettings.mqttUrl); + LOG_I("MQTT: Trying to connect to MQTT broker ({})", serverUrl); publisher.connect(); } From cb42634c2f0afaff69eab824398dd46ba4710b8d Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 29 Oct 2025 14:14:58 +0100 Subject: [PATCH 33/55] mqtt: ref-dev-mqtt-sub fixes --- examples/ref-dev-mqtt-sub/src/CMakeLists.txt | 4 +++- examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/ref-dev-mqtt-sub/src/CMakeLists.txt b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt index c7621cc2..1ed54195 100644 --- a/examples/ref-dev-mqtt-sub/src/CMakeLists.txt +++ b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt @@ -3,5 +3,7 @@ cmake_minimum_required(VERSION 3.16) add_compile_definitions(MODULE_PATH="${OPENDAQ_MODULES_DIR}") add_executable(${EXAMPLE_PROJECT_NAME} ref-dev-mqtt-sub.cpp) -target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq) +target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq + mqtt_stream_cli_module +) target_include_directories(${EXAMPLE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../) diff --git a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp index 77a06654..191fbb7c 100644 --- a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp +++ b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp @@ -1,9 +1,11 @@ #include #include "../../InputArgs.h" +#include #include using namespace daq; +using namespace daq::modules::mqtt_streaming_client_module; int main(int argc, char* argv[]) { @@ -31,9 +33,11 @@ int main(int argc, char* argv[]) std::vector fbList; for (const auto& [key, value] : availableDeviceNodes) { std::cout << "Available function block: " << key << std::endl; + if (key == RAW_FB_NAME || key == JSON_FB_NAME) + continue; fbList.push_back(brokerDevice.addFunctionBlock(key)); } - std::cout << "Try to connect the first one (" << fbList[0].getLocalId() << ")" << std::endl; + std::cout << "Try to connect the " << fbList[0].getLocalId() << std::endl; auto signals = fbList[0].getSignals(); std::vector readers; From 4b5ba32cdd2606bc4493298091d402ffa2afd2b9 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 29 Oct 2025 15:44:57 +0100 Subject: [PATCH 34/55] mqtt: multidevice support; tests; --- .../mqtt_streaming_client_module_impl.h | 1 - .../src/mqtt_streaming_client_module_impl.cpp | 6 +- .../tests/test_mqtt_device.cpp | 6 +- .../tests/test_mqtt_json_fb.cpp | 101 +++++++++++++----- 4 files changed, 83 insertions(+), 31 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h index 815ebee3..be961879 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h @@ -42,7 +42,6 @@ class MqttStreamingClientModule final : public Module static PropertyObjectPtr populateDefaultConfig(const PropertyObjectPtr& config); std::mutex sync; - DevicePtr device; }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp index 7f3cfe68..e68129b3 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp @@ -51,8 +51,6 @@ DictPtr MqttStreamingClientModule::onGetAvailableDeviceTyp DevicePtr MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionString, const ComponentPtr& parent, const PropertyObjectPtr& config) { - if (device.assigned()) - DAQ_THROW_EXCEPTION(AlreadyExistsException, "Only one MQTT streaming device can be created per module instance."); if (!connectionString.assigned()) DAQ_THROW_EXCEPTION(ArgumentNullException); @@ -76,7 +74,9 @@ MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionString, con std::scoped_lock lock(sync); - device = createWithImplementation(context, parent, configPtr); + DevicePtr device = createWithImplementation(context, parent, configPtr); + + LOG_I("MQTT device (GlobalId: {}) created with connection string: {}", device.getGlobalId(), connectionString.toStdString()); // Set the connection info for the device ServerCapabilityConfigPtr connectionInfo = device.getInfo().getConfigurationConnectionInfo(); diff --git a/mqtt_streaming_client_module/tests/test_mqtt_device.cpp b/mqtt_streaming_client_module/tests/test_mqtt_device.cpp index 5b2f7894..832c2b6e 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_device.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_device.cpp @@ -120,7 +120,11 @@ TEST_F(MqttDeviceTest, CreatingSeveralDevices) Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); ASSERT_EQ(device.getInfo().getName(), MQTT_DEVICE_NAME); daq::GenericDevicePtr anotherDevice; - ASSERT_THROW(anotherDevice = instance.addDevice("daq.mqtt://127.0.0.1", config), AlreadyExistsException); + ASSERT_NO_THROW(anotherDevice = instance.addDevice("daq.mqtt://127.0.0.1:1884", config)); + ASSERT_EQ(anotherDevice.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); + ASSERT_EQ(anotherDevice.getInfo().getName(), MQTT_DEVICE_NAME); + ASSERT_EQ(instance.getDevices().getCount(), 2u); } TEST_F(MqttDeviceTest, RemovingDevice) diff --git a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp index adfc88fc..6ed6ac26 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp @@ -221,6 +221,59 @@ class MqttJsonFbHelper class MqttJsonFbTest : public testing::Test, public DaqTestHelper, public MqttJsonFbHelper { }; + +class MqttJsonFbCommunicationTest : public testing::Test, public DaqTestHelper, public MqttJsonFbHelper +{ +public: + using data_set_t = std::vector>; + + struct Result + { + bool deviceProblem = false; + bool publishingProblem = false; + data_set_t dataReceived; + }; + + Result processTransfer(const InstancePtr& instance, const std::string& url, const std::string& topic_postfix, const data_set_t& dataSet) + { + Result result; + const std::string topic = buildTopicName() + topic_postfix; + const auto jsonConfig = replacePlaceholder(VALID_JSON_CONFIG_0, "", topic); + DevicePtr device; + try + { + device = instance.addDevice("daq.mqtt://" + url, DaqMqttDeviceConfig(100)); + } + catch (...) + { + result.deviceProblem = true; + return result; + } + + auto config = device.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); + auto singal = device.addFunctionBlock(JSON_FB_NAME, config).getSignals()[0]; + auto reader = daq::StreamReader(singal); + + MqttAsyncClientWrapper publisher(std::make_shared(), buildClientId()); + result.deviceProblem = !publisher.connect(url); + if (result.deviceProblem) + return result; + + for (const auto& [value, ts] : dataSet) + { + auto str = VALID_JSON_DATA_0; + str = replacePlaceholder(str, "", ts); + str = replacePlaceholder(str, "", value); + result.publishingProblem = !publisher.publishMsg({topic, std::vector(str.begin(), str.end()), 1, 0}); + if (result.publishingProblem) + return result; + } + result.dataReceived = read(reader, true); + return result; + }; +}; + class MqttJsonFbRightJsonConfigPTest : public ::testing::TestWithParam>, public DaqTestHelper, public MqttJsonFbHelper @@ -593,36 +646,32 @@ TEST_F(MqttJsonFbTest, DataTransferMissingFieldSeveralSignals) EXPECT_TRUE(compareData(DATA_DOUBLE_INT_2, dataToReceive[2], false)); } -TEST_F(MqttJsonFbTest, FullDataTransfer) +TEST_F(MqttJsonFbCommunicationTest, FullDataTransfer) { - const std::string topic = buildTopicName(); - const auto jsonConfig = replacePlaceholder(VALID_JSON_CONFIG_0, "", topic); - const auto instance = Instance(); - auto device = instance.addDevice("daq.mqtt://127.0.0.1", DaqMqttDeviceConfig(100)); - - auto config = device.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); - auto singal = device.addFunctionBlock(JSON_FB_NAME, config).getSignals()[0]; + const auto result = processTransfer(instance, "127.0.0.1", "", DATA_DOUBLE_INT_0); - auto reader = daq::StreamReader(singal); - - MqttAsyncClientWrapper publisher(std::make_shared(), buildClientId()); - ASSERT_TRUE(publisher.connect("127.0.0.1")); - - for (const auto& [value, ts] : DATA_DOUBLE_INT_0) - { - auto str = VALID_JSON_DATA_0; - str = replacePlaceholder(str, "", ts); - str = replacePlaceholder(str, "", value); - ASSERT_TRUE(publisher.publishMsg({topic, std::vector(str.begin(), str.end()), 1, 0})); - } - - const std::vector> dataToReceive = read(reader, true); + ASSERT_FALSE(result.deviceProblem); + ASSERT_FALSE(result.publishingProblem); + EXPECT_EQ(DATA_DOUBLE_INT_0.size(), result.dataReceived.size()); + ASSERT_TRUE(compareData(DATA_DOUBLE_INT_0, result.dataReceived)); +} - ASSERT_EQ(DATA_DOUBLE_INT_0.size(), dataToReceive.size()); - ASSERT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive)); +TEST_F(MqttJsonFbCommunicationTest, FullDataTransferFor2Devices) +{ + const auto instance = Instance(); + const auto result0 = processTransfer(instance, "127.0.0.1:1883", "0", DATA_DOUBLE_INT_0); + const auto result1 = processTransfer(instance, "127.0.0.1:1884", "1", DATA_DOUBLE_INT_1); + + ASSERT_FALSE(result0.deviceProblem); + ASSERT_FALSE(result0.publishingProblem); + EXPECT_EQ(DATA_DOUBLE_INT_0.size(), result0.dataReceived.size()); + EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, result0.dataReceived)); + + ASSERT_FALSE(result1.deviceProblem); + ASSERT_FALSE(result1.publishingProblem); + EXPECT_EQ(DATA_DOUBLE_INT_1.size(), result1.dataReceived.size()); + EXPECT_TRUE(compareData(DATA_DOUBLE_INT_1, result1.dataReceived)); } TEST_P(MqttJsonFbUnitPTest, SignalUnit) From 76a3bc57cb7a0b81f7f4a34373d175795a6acbd8 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 30 Oct 2025 17:18:18 +0100 Subject: [PATCH 35/55] mqtt: new returned value for MQTT commands --- .../src/mqtt_raw_receiver_fb_impl.cpp | 12 +- .../src/mqtt_receiver_fb_impl.cpp | 6 +- .../include/MqttAsyncClient.h | 55 +++++--- .../src/MqttAsyncClient.cpp | 132 +++++++----------- .../tests/MqttAsyncClientWrapper.cpp | 17 +-- .../tests/test_mqtt_streaming_protocol.cpp | 31 ++-- .../src/mqtt_streaming_server_impl.cpp | 13 +- 7 files changed, 123 insertions(+), 143 deletions(-) diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index 53cd9175..52fdc848 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -133,9 +133,9 @@ void MqttRawReceiverFbImpl::subscribeToTopics() this, std::placeholders::_1, std::placeholders::_2)); - auto ok = subscriber->subscribe(topic, 1); - if (!ok) - LOG_W("Failed to subscribe to the topic: {}", topic); + auto result = subscriber->subscribe(topic, 1); + if (!result.success) + LOG_W("Failed to subscribe to the topic: {}; reason: {}", topic, result.msg); } } @@ -149,9 +149,9 @@ void MqttRawReceiverFbImpl::unsubscribeFromTopics() for (const auto& topic : topicsForSubscribing) { subscriber->setMessageArrivedCb(topic, nullptr); - auto ok = subscriber->unsubscribe(topic); - if (!ok) - LOG_W("Failed to unsubscribe from the topic: {}", topic); + auto result = subscriber->unsubscribe(topic); + if (!result.success) + LOG_W("Failed to unsubscribe from the topic: {}; reason: {}", topic, result.msg); } } END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index ba8cee72..257cf229 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -31,9 +31,9 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, subscriber ->setMessageArrivedCb(topic, std::bind(&MqttReceiverFbImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); - auto ok = subscriber->subscribe(topic, 1); - if (!ok) - LOG_W("Failed to subscribe to the topic: {}", topic); + auto result = subscriber->subscribe(topic, 1); + if (!result.success) + LOG_W("Failed to subscribe to the topic: {}; reason: {}", topic, result.msg); } setComponentStatus(ComponentStatus::Ok); } diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h index d2c32a0c..4516e867 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncClient.h +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -16,14 +16,38 @@ enum class MqttConnectionStatus { pending, }; -struct MqttSubscription { - std::string topic; - int qos; - - MqttSubscription(std::string t, int q) - : topic(t) - , qos(q) - {} +struct CmdResult +{ + bool success = false; + std::string msg; + int token = 0; + + CmdResult() + : success(false), + msg(""), + token(0) + { + } + CmdResult(bool success) + : success(success), + msg(""), + token(0) + { + } + + CmdResult(bool success, const std::string& msg) + : success(success), + msg(msg), + token(0) + { + } + + CmdResult(bool success, const std::string& msg, int token) + : success(success), + msg(msg), + token(token) + { + } }; class MqttAsyncClient final { @@ -38,21 +62,18 @@ class MqttAsyncClient final { MqttAsyncClient &operator=(MqttAsyncClient &&) = delete; ~MqttAsyncClient(); - bool connect(); - bool disconnect(); + CmdResult connect(); + CmdResult disconnect(); bool syncDisconnect(int timeoutMs); - bool publish(const std::string &topic, + CmdResult publish(const std::string &topic, void *data, size_t dataLen, - std::string *err = nullptr, int qos = 1, - int *token = nullptr, bool retained = false); - bool subscribe(std::string topic, int qos); - bool unsubscribe(std::string topic); - bool unsubscribeAll(); + CmdResult subscribe(std::string topic, int qos); + CmdResult unsubscribe(std::string topic); void setConnectedCb(std::function cb); void setMessageArrivedCb(std::string topic, std::function cb); @@ -90,8 +111,6 @@ class MqttAsyncClient final { std::function onMsgArrivedCmnCb; std::unordered_map> onMsgArrivedCbs; - std::vector subscriptions; - std::lock_guard getCbLock(); static void onDeliveryCompleted(void *context, MQTTAsync_token token); diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index ca52a250..a6e6ad24 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -44,7 +44,7 @@ MqttAsyncClient::~MqttAsyncClient() } } -bool MqttAsyncClient::connect() +CmdResult MqttAsyncClient::connect() { if (client != nullptr) { @@ -53,14 +53,14 @@ bool MqttAsyncClient::connect() if (serverUrl.empty() || clientId.empty()) { - return false; + return CmdResult(false, "serverUrl or clientId is empty"); } int rc = MQTTAsync_createWithOptions(&client, serverUrl.c_str(), clientId.c_str(), MQTTCLIENT_PERSISTENCE_NONE, NULL, &createOpts); if (rc != MQTTASYNC_SUCCESS) { - return false; + return CmdResult(false, MQTTAsync_strerror(rc)); } rc = MQTTAsync_setCallbacks(client, @@ -71,33 +71,33 @@ bool MqttAsyncClient::connect() if (rc != MQTTASYNC_SUCCESS) { - return false; + return CmdResult(false, MQTTAsync_strerror(rc)); } rc = MQTTAsync_setConnected(client, this, &MqttAsyncClient::onConnected); if (rc != MQTTASYNC_SUCCESS) { - return false; + return CmdResult(false, MQTTAsync_strerror(rc)); } rc = MQTTAsync_connect(client, &connOpts); if (rc != MQTTASYNC_SUCCESS) { - return false; + return CmdResult(false, MQTTAsync_strerror(rc)); } pendingConnect = true; - return true; + return CmdResult(true); } -bool MqttAsyncClient::disconnect() +CmdResult MqttAsyncClient::disconnect() { if (client == nullptr) - return true; + return CmdResult(true, "The client is null"); // It is only the result of the request to disconnect (queuing) auto status = MQTTAsync_disconnect(client, &disconnOpts); bool result = (status == MQTTASYNC_SUCCESS || status == MQTTASYNC_DISCONNECTED); - return result; + return CmdResult(result, MQTTAsync_strerror(status)); } bool MqttAsyncClient::syncDisconnect(int timeoutMs) @@ -105,33 +105,38 @@ bool MqttAsyncClient::syncDisconnect(int timeoutMs) if (client == nullptr) return true; - bool result = disconnect(); - if (result) + if (isConnected() != MqttConnectionStatus::not_connected) { - std::atomic done{false}; - std::promise disconnectedPromise; - auto disconnectedFuture = disconnectedPromise.get_future(); + if (disconnect().success) { - auto lock = getCbLock(); - onInternalDisconnectCb = [promise = &disconnectedPromise, &done](bool result) { - bool expected = false; - if (done.compare_exchange_strong(expected, true)) { - promise->set_value(result); - } - }; - } - auto status = disconnectedFuture.wait_for(std::chrono::milliseconds(timeoutMs)); - { - auto lock = getCbLock(); - onInternalDisconnectCb = nullptr; - } - if (status == std::future_status::ready && disconnectedFuture.get() == true) - { - MQTTAsync_destroy(&client); - return true; + std::atomic done{false}; + std::promise disconnectedPromise; + auto disconnectedFuture = disconnectedPromise.get_future(); + { + auto lock = getCbLock(); + onInternalDisconnectCb = [promise = &disconnectedPromise, &done](bool result) + { + bool expected = false; + if (done.compare_exchange_strong(expected, true)) + promise->set_value(result); + }; + } + auto status = disconnectedFuture.wait_for(std::chrono::milliseconds(timeoutMs)); + { + auto lock = getCbLock(); + onInternalDisconnectCb = nullptr; + } } } - return false; + if (isConnected() == MqttConnectionStatus::not_connected) + { + MQTTAsync_destroy(&client); + return true; + } + else + { + return false; + } } MqttConnectionStatus MqttAsyncClient::isConnected() const @@ -156,36 +161,26 @@ void MqttAsyncClient::setUsernamePasswrod(std::string username, std::string pass connOpts.password = !password.empty() ? password.c_str() : NULL; } -bool MqttAsyncClient::publish(const std::string& topic, void* data, size_t dataLen, std::string* err, int qos, int* token, bool retained) +CmdResult MqttAsyncClient::publish(const std::string& topic, void* data, size_t dataLen, int qos, bool retained) { std::string tmpErr; if (client == nullptr) { - tmpErr = "MQTTAsync is nullptr"; + return CmdResult(false, "MQTTAsync is nullptr"); } if (topic.empty()) { - tmpErr = "topic is empty"; + return CmdResult(false, "topic is empty"); } if (qos > 2 || qos < 0) { - tmpErr = "QoS is wrong"; - } - - if (!tmpErr.empty()) - { - if (err != nullptr) - { - *err = std::move(tmpErr); - } - return false; + return CmdResult(false, "QoS is wrong"); } MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; MQTTAsync_message pubmsg = MQTTAsync_message_initializer; - int rc; opts.onSuccess = (MQTTAsync_onSuccess*)&MqttAsyncClient::onSendSuccess; opts.onFailure = (MQTTAsync_onFailure*)&MqttAsyncClient::onSendFailure; opts.context = this; @@ -193,57 +188,28 @@ bool MqttAsyncClient::publish(const std::string& topic, void* data, size_t dataL pubmsg.payloadlen = (int)dataLen; pubmsg.qos = qos; pubmsg.retained = retained ? 1 : 0; - if ((rc = MQTTAsync_sendMessage(client, topic.c_str(), &pubmsg, &opts)) != MQTTASYNC_SUCCESS) - { - if (err != nullptr) - { - *err = MQTTAsync_strerror(rc); - } - } - if (token != nullptr) - { - *token = opts.token; - } - return rc == MQTTASYNC_SUCCESS; + int rc = MQTTAsync_sendMessage(client, topic.c_str(), &pubmsg, &opts); + return CmdResult(rc == MQTTASYNC_SUCCESS, MQTTAsync_strerror(rc), opts.token); } -bool MqttAsyncClient::subscribe(std::string topic, int qos) +CmdResult MqttAsyncClient::subscribe(std::string topic, int qos) { MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - int rc; opts.onSuccess = MqttAsyncClient::onSubscribeSuccess; opts.onFailure = MqttAsyncClient::onSubscribeFailure; opts.context = this; - if ((rc = MQTTAsync_subscribe(client, topic.c_str(), qos, &opts)) == MQTTASYNC_SUCCESS) - { - subscriptions.emplace_back(topic, qos); - } - return rc == MQTTASYNC_SUCCESS; + int rc = MQTTAsync_subscribe(client, topic.c_str(), qos, &opts); + return CmdResult(rc == MQTTASYNC_SUCCESS, MQTTAsync_strerror(rc), opts.token); } -bool MqttAsyncClient::unsubscribe(std::string topic) +CmdResult MqttAsyncClient::unsubscribe(std::string topic) { MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; opts.onSuccess = (MQTTAsync_onSuccess*)&MqttAsyncClient::onUnsubscribeSuccess; opts.onFailure = (MQTTAsync_onFailure*)&MqttAsyncClient::onUnsubscribeFailure; opts.context = this; int rc = MQTTAsync_unsubscribe(client, topic.c_str(), &opts); - auto it = - std::find_if(subscriptions.begin(), subscriptions.end(), [&topic](const MqttSubscription& sub) { return sub.topic == topic; }); - if (it != subscriptions.end()) - subscriptions.erase(it); - return rc == MQTTASYNC_SUCCESS; -} - -bool MqttAsyncClient::unsubscribeAll() -{ - for (auto& sub : subscriptions) - { - unsubscribe(sub.topic); - } - subscriptions.clear(); - - return true; + return CmdResult(rc == MQTTASYNC_SUCCESS, MQTTAsync_strerror(rc), opts.token); } void MqttAsyncClient::setServerURL(std::string serverUrl) diff --git a/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp b/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp index b3a97674..3d933e94 100644 --- a/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp +++ b/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp @@ -36,7 +36,8 @@ bool MqttAsyncClientWrapper::createConnection(const std::string& url, const std: connectedPromise.set_value(true); } }); - return instance->connect(); + auto result = instance->connect(); + return result.success; } bool MqttAsyncClientWrapper::connect(const std::string& url) @@ -75,8 +76,8 @@ bool MqttAsyncClientWrapper::disconnect() } }); - auto disconnectionOk = instance->disconnect(); - if (!disconnectionOk) + auto result = instance->disconnect(); + if (!result.success) { return false; } @@ -99,21 +100,17 @@ bool MqttAsyncClientWrapper::publishMsg(const std::string& topic, const std::str bool MqttAsyncClientWrapper::publishMsg(const mqtt::MqttMessage& msg) { - int token = 0; - std::promise deliveryPromise; auto deliveryFuture = deliveryPromise.get_future(); instance->setDeliveryCompletedCb([promise = &deliveryPromise](int deliveredToken) { promise->set_value(deliveredToken); }); Timer sendTimer(successTimeout); - auto ok = instance->publish(msg.getTopic(), + auto result = instance->publish(msg.getTopic(), (void*)(msg.getData().data()), msg.getData().size(), - nullptr, msg.getQos(), - &token, msg.getRetained()); - if (!ok || token == 0) + if (!result.success || result.token == 0) { instance->setDeliveryCompletedCb(nullptr); return false; @@ -121,5 +118,5 @@ bool MqttAsyncClientWrapper::publishMsg(const mqtt::MqttMessage& msg) auto status = deliveryFuture.wait_for(sendTimer.remain()); instance->setDeliveryCompletedCb(nullptr); - return (status == std::future_status::ready && deliveryFuture.get() == token); + return (status == std::future_status::ready && deliveryFuture.get() == result.token); } diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp index 0cbe6184..e3b19ef0 100644 --- a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -109,8 +109,8 @@ TEST_F(MqttStreamingProtocolTest, Disconnection) } }); - auto disconnectionOk = instance->disconnect(); - ASSERT_TRUE(disconnectionOk); + auto result = instance->disconnect(); + ASSERT_TRUE(result.success); status = disconnectedFuture.wait_for(timer.remain()); instance->setDisconnectCb(nullptr); @@ -129,11 +129,11 @@ TEST_F(MqttStreamingProtocolTest, PublishingWithoutDataControl) auto ok = connect("127.0.0.1", clientId); ASSERT_TRUE(ok); - int token = 0; + CmdResult result; std::atomic sendDone{false}; std::promise sendPromise; auto sendFuture = sendPromise.get_future(); - instance->setSentCb([promise = &sendPromise, token = &token, &sendDone](int receivedToken, bool result) { + instance->setSentCb([promise = &sendPromise, token = &result.token, &sendDone](int receivedToken, bool result) { bool expected = false; if (receivedToken == *token) { if (sendDone.compare_exchange_strong(expected, true)) { @@ -146,7 +146,7 @@ TEST_F(MqttStreamingProtocolTest, PublishingWithoutDataControl) std::promise deliveryPromise; auto deliveryFuture = deliveryPromise.get_future(); instance->setDeliveryCompletedCb( - [promise = &deliveryPromise, token = &token, &deliveryDone](int receivedToken) { + [promise = &deliveryPromise, token = &result.token, &deliveryDone](int receivedToken) { bool expected = false; if (receivedToken == *token) { if (deliveryDone.compare_exchange_strong(expected, true)) { @@ -158,9 +158,9 @@ TEST_F(MqttStreamingProtocolTest, PublishingWithoutDataControl) const std::string topic = buildTopicName(); const std::string data = "test data"; - ok = instance->publish(topic, (void *)(data.c_str()), data.size(), nullptr, 1, &token, false); - ASSERT_TRUE(ok); - ASSERT_TRUE(token != 0); + result = instance->publish(topic, (void *)(data.c_str()), data.size(), 1, false); + ASSERT_TRUE(result.success); + ASSERT_TRUE(result.token != 0); Timer timer(successTimeout); @@ -199,7 +199,7 @@ TEST_F(MqttStreamingProtocolTest, PublishingRetainedWithReceivingControl) const MqttMessage msg(topic, std::vector(text.begin(), text.end()), 1, true); ASSERT_TRUE(publishMsg(msg)); - ASSERT_TRUE(instance->disconnect()); + ASSERT_TRUE(instance->disconnect().success); std::this_thread::sleep_for(milliseconds(500)); // Give some time to the broker to store the retained message) @@ -226,8 +226,8 @@ TEST_F(MqttStreamingProtocolTest, PublishingRetainedWithReceivingControl) }); Timer receiveTimer(successTimeout); - auto ok = subscriber.instance->subscribe(msg.getTopic(), msg.getQos()); - ASSERT_TRUE(ok); + auto result = subscriber.instance->subscribe(msg.getTopic(), msg.getQos()); + ASSERT_TRUE(result.success); auto status = receivedFuture.wait_for(receiveTimer.remain()); instance->setMessageArrivedCb(nullptr); ASSERT_TRUE(status == std::future_status::ready); @@ -269,8 +269,8 @@ TEST_F(MqttStreamingProtocolTest, PublishingWithReceivingControl) }); Timer receiveTimer(successTimeout); - auto ok = subscriber.instance->subscribe(msg.getTopic(), msg.getQos()); - ASSERT_TRUE(ok); + auto result = subscriber.instance->subscribe(msg.getTopic(), msg.getQos()); + ASSERT_TRUE(result.success); ASSERT_TRUE(publishMsg(msg)); auto status = receivedFuture.wait_for(receiveTimer.remain()); @@ -285,9 +285,8 @@ TEST_F(MqttStreamingProtocolTest, PublishingWithoutConnection) { const std::string topic = buildTopicName(); const std::string data = "test data"; - int token = 0; - auto ok = instance->publish(topic, (void *)(data.c_str()), data.size(), nullptr, 1, &token, false); - ASSERT_FALSE(ok); + auto result = instance->publish(topic, (void *)(data.c_str()), data.size(), 1, false); + ASSERT_FALSE(result.success); } TEST_P(JsonTimestampParsingIntPTest, IntTimestampParsing) diff --git a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp index 8496d8af..94065463 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -85,11 +85,10 @@ void MqttStreamingServerImpl::sendData(const std::string& topic, const ChannelDa { for (const auto& jsonMessage : jsonMessages) { - std::string err; - auto status = publisher.publish(topic, (void*)jsonMessage.c_str(), jsonMessage.length(), &err); - if (!status) + auto status = publisher.publish(topic, (void*)jsonMessage.c_str(), jsonMessage.length()); + if (!status.success) { - LOG_W("Failed to publish data to {}; reason - {}", topic, err); + LOG_W("Failed to publish data to {}; reason - {}", topic, status.msg); } } } @@ -118,10 +117,10 @@ void MqttStreamingServerImpl::sendTopicList() auto topicsMessage = prepareJsonTopics(); if (publisher.isConnected() == mqtt::MqttConnectionStatus::connected) { - bool status = publisher.publish(topic, (void*)topicsMessage.c_str(), topicsMessage.length(), nullptr, 1, nullptr, true); - if (!status) + auto status = publisher.publish(topic, (void*)topicsMessage.c_str(), topicsMessage.length(), 1, true); + if (!status.success) { - LOG_W("Failed to publish topics list to {}", topic); + LOG_W("Failed to publish topics list to {}; reason: {}", topic, status.msg); } else { From 2398cff58b0297a8febd62034c2dbceed6bddc05 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 30 Oct 2025 17:21:18 +0100 Subject: [PATCH 36/55] mqtt: unsubscribing when a FB is removed or deleted --- .../mqtt_raw_receiver_fb_impl.h | 4 +- .../mqtt_receiver_fb_impl.h | 5 +- .../src/mqtt_raw_receiver_fb_impl.cpp | 31 +++++++--- .../src/mqtt_receiver_fb_impl.cpp | 51 ++++++++++++---- .../src/mqtt_streaming_device_impl.cpp | 2 +- .../include/MqttAsyncClient.h | 5 ++ .../src/MqttAsyncClient.cpp | 58 ++++++++++++++++++- 7 files changed, 133 insertions(+), 23 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h index 0e9a9dc4..db8210ea 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h @@ -37,7 +37,7 @@ class MqttRawReceiverFbImpl final : public FunctionBlock std::unordered_map outputSignals; std::shared_ptr subscriber; - ListObjectPtr topicsForSubscribing; + std::vector topicsForSubscribing; std::mutex sync; @@ -52,6 +52,8 @@ class MqttRawReceiverFbImpl final : public FunctionBlock void subscribeToTopics(); void unsubscribeFromTopics(); + + void removed() override; }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h index 7a2beab5..0021bb4b 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -55,7 +55,10 @@ class MqttReceiverFbImpl final : public FunctionBlock void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg); - std::set getSubscribedTopics() const; + std::vector getSubscribedTopics() const; + void unsubscribeFromTopics(); + + void removed() override; }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index 52fdc848..3c11a1ad 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -7,6 +7,8 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE +constexpr int MQTT_RAW_FB_UNSUBSCRIBE_TOUT = 3000; + MqttRawReceiverFbImpl::MqttRawReceiverFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, const FunctionBlockTypePtr& type, @@ -32,6 +34,12 @@ MqttRawReceiverFbImpl::~MqttRawReceiverFbImpl() unsubscribeFromTopics(); } +void MqttRawReceiverFbImpl::removed() +{ + FunctionBlock::removed(); + unsubscribeFromTopics(); +} + void MqttRawReceiverFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg) { @@ -59,7 +67,7 @@ void MqttRawReceiverFbImpl::initProperties(const PropertyObjectPtr& config) void MqttRawReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); - topicsForSubscribing = List(); + topicsForSubscribing.clear(); bool isPresent = false; if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) { @@ -73,7 +81,7 @@ void MqttRawReceiverFbImpl::readProperties() if (mqtt::MqttDataWrapper::validateTopic(topicStr, loggerComponent)) { LOG_I("Topic in list: {}", topicStr.toStdString()); - topicsForSubscribing.pushBack(topicStr); + topicsForSubscribing.emplace_back(topicStr.toStdString()); } } } @@ -146,12 +154,21 @@ void MqttRawReceiverFbImpl::unsubscribeFromTopics() LOG_E("The subscriber is null"); return; } - for (const auto& topic : topicsForSubscribing) + if (topicsForSubscribing.empty()) + return; + subscriber->setMessageArrivedCb(topicsForSubscribing, nullptr); + auto result = subscriber->unsubscribe(topicsForSubscribing); + if (result.success) + result = subscriber->waitForCompletion(result.token, MQTT_RAW_FB_UNSUBSCRIBE_TOUT); + + if (result.success) { - subscriber->setMessageArrivedCb(topic, nullptr); - auto result = subscriber->unsubscribe(topic); - if (!result.success) - LOG_W("Failed to unsubscribe from the topic: {}; reason: {}", topic, result.msg); + topicsForSubscribing.clear(); + LOG_I("All topics have been unsubscribed successfully"); + } + else + { + LOG_W("Failed to unsubscribe from all topics; reason: {}", result.msg); } } END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp index 257cf229..3aae0946 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -5,6 +5,8 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE +constexpr int MQTT_JSON_FB_UNSUBSCRIBE_TOUT = 3000; + MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, const FunctionBlockTypePtr& type, @@ -45,14 +47,13 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, MqttReceiverFbImpl::~MqttReceiverFbImpl() { - if (subscriber) - { - for (const auto& topic : getSubscribedTopics()) - { - subscriber->setMessageArrivedCb(topic, nullptr); - subscriber->unsubscribe(topic); - } - } + unsubscribeFromTopics(); +} + +void MqttReceiverFbImpl::removed() +{ + FunctionBlock::removed(); + unsubscribeFromTopics(); } void MqttReceiverFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg) @@ -140,15 +141,41 @@ void MqttReceiverFbImpl::createSignals() jsonDataWorker.setOutputSignals(&outputSignals); } -std::set MqttReceiverFbImpl::getSubscribedTopics() const +std::vector MqttReceiverFbImpl::getSubscribedTopics() const { auto lock = std::lock_guard(sync); - std::set topics; + std::set topicsSet; for (const auto& [signalId, _] : subscribedSignals) { - topics.emplace(signalId.topic); + topicsSet.emplace(signalId.topic); + } + return std::vector(topicsSet.cbegin(), topicsSet.cend()); +} + +void MqttReceiverFbImpl::unsubscribeFromTopics() +{ + if (!subscriber) + { + LOG_E("The subscriber is null"); + return; + } + const auto topics = getSubscribedTopics(); + if (topics.empty()) + return; + subscriber->setMessageArrivedCb(topics, nullptr); + auto result = subscriber->unsubscribe(topics); + if (result.success) + result = subscriber->waitForCompletion(result.token, MQTT_JSON_FB_UNSUBSCRIBE_TOUT); + + if (result.success) + { + subscribedSignals.clear(); + LOG_I("All topics have been unsubscribed successfully"); + } + else + { + LOG_W("Failed to unsubscribe from all topics; reason: {}", result.msg); } - return topics; } END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index c1392bd5..0c009ab8 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -56,10 +56,10 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const Co void MqttStreamingDeviceImpl::removed() { + Device::removed(); bool disRes = subscriber->syncDisconnect(MQTT_CLIENT_SYNC_DISCONNECT_TOUT); if (!disRes) LOG_E("MQTT: disconnection was unsuccessful"); - Device::removed(); } DeviceInfoPtr MqttStreamingDeviceImpl::onGetInfo() diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h index 4516e867..a62ce0ca 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncClient.h +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -74,12 +74,16 @@ class MqttAsyncClient final { CmdResult subscribe(std::string topic, int qos); CmdResult unsubscribe(std::string topic); + CmdResult unsubscribe(const std::vector& topics); + CmdResult waitForCompletion(int token, unsigned long toutMs); void setConnectedCb(std::function cb); void setMessageArrivedCb(std::string topic, std::function cb); + void setMessageArrivedCb(std::vector topics, std::function cb); void setMessageArrivedCb(std::function cb); void setDisconnectCb(std::function cb); void setSentCb(std::function cb); + void setUnsubscribeCb(std::function cb); void setDeliveryCompletedCb(std::function cb); void setServerURL(std::string serverUrl); @@ -105,6 +109,7 @@ class MqttAsyncClient final { std::function onConnectedCb; std::function onSentCb; + std::function onUnsubscribeCb; std::function onDisconnectCb; std::function onInternalDisconnectCb; std::function onDeliveryCompletedCb; diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index a6e6ad24..598aa48d 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -212,6 +212,27 @@ CmdResult MqttAsyncClient::unsubscribe(std::string topic) return CmdResult(rc == MQTTASYNC_SUCCESS, MQTTAsync_strerror(rc), opts.token); } +CmdResult MqttAsyncClient::unsubscribe(const std::vector& topics) +{ + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + opts.onSuccess = (MQTTAsync_onSuccess*)&MqttAsyncClient::onUnsubscribeSuccess; + opts.onFailure = (MQTTAsync_onFailure*)&MqttAsyncClient::onUnsubscribeFailure; + opts.context = this; + const char* topicArray[topics.size()]; + for (size_t i = 0; i < topics.size(); ++i) + { + topicArray[i] = topics[i].c_str(); + } + int rc = MQTTAsync_unsubscribeMany(client, topics.size(), (char* const*)topicArray, &opts); + return CmdResult(rc == MQTTASYNC_SUCCESS, MQTTAsync_strerror(rc), opts.token); +} + +CmdResult MqttAsyncClient::waitForCompletion(int token, unsigned long toutMs) +{ + int rc = MQTTAsync_waitForCompletion(client, token, toutMs); + return CmdResult(rc == MQTTASYNC_SUCCESS, MQTTAsync_strerror(rc)); +} + void MqttAsyncClient::setServerURL(std::string serverUrl) { if (serverUrl != "") @@ -373,10 +394,24 @@ void MqttAsyncClient::onSubscribeFailure(void* context, MQTTAsync_failureData* r void MqttAsyncClient::onUnsubscribeSuccess(void* context, MQTTAsync_successData* response) { + if (context != nullptr) + { + auto clienttInst = (MqttAsyncClient*)context; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onUnsubscribeCb) + clienttInst->onUnsubscribeCb(response->token, true); + } } void MqttAsyncClient::onUnsubscribeFailure(void* context, MQTTAsync_failureData* response) { + if (context != nullptr) + { + auto clienttInst = (MqttAsyncClient*)context; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onUnsubscribeCb) + clienttInst->onUnsubscribeCb(response->token, false); + } } void MqttAsyncClient::setConnectedCb(std::function cb) @@ -388,7 +423,22 @@ void MqttAsyncClient::setConnectedCb(std::function cb) void MqttAsyncClient::setMessageArrivedCb(std::string topic, std::function cb) { auto lock = getCbLock(); - onMsgArrivedCbs.insert({topic, cb}); + if (!cb) + onMsgArrivedCbs.erase(topic); + else + onMsgArrivedCbs.insert({topic, cb}); +} + +void MqttAsyncClient::setMessageArrivedCb(std::vector topics, std::function cb) +{ + auto lock = getCbLock(); + for (const auto& topic : topics) + { + if (!cb) + onMsgArrivedCbs.erase(topic); + else + onMsgArrivedCbs.insert({topic, cb}); + } } void MqttAsyncClient::setMessageArrivedCb(std::function cb) @@ -409,6 +459,12 @@ void MqttAsyncClient::setSentCb(std::function cb) onSentCb = cb; } +void MqttAsyncClient::setUnsubscribeCb(std::function cb) +{ + auto lock = getCbLock(); + onUnsubscribeCb = cb; +} + void MqttAsyncClient::setDeliveryCompletedCb(std::function cb) { auto lock = getCbLock(); From 9799242ed28e53e22a95ba9cc64bb77f364e711b Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 31 Oct 2025 13:11:29 +0100 Subject: [PATCH 37/55] mqtt: mqtt class refactoring --- .../mqtt_base_fb.h | 55 ++++++ ...fb_impl.h => mqtt_json_receiver_fb_impl.h} | 38 ++-- .../mqtt_raw_receiver_fb_impl.h | 41 ++-- .../src/CMakeLists.txt | 12 +- .../src/mqtt_base_fb.cpp | 97 ++++++++++ .../src/mqtt_json_receiver_fb_impl.cpp | 121 ++++++++++++ .../src/mqtt_raw_receiver_fb_impl.cpp | 89 +-------- .../src/mqtt_receiver_fb_impl.cpp | 181 ------------------ .../src/mqtt_streaming_client_module_impl.cpp | 2 +- .../src/mqtt_streaming_device_impl.cpp | 4 +- .../tests/test_mqtt_json_fb.cpp | 6 +- 11 files changed, 327 insertions(+), 319 deletions(-) create mode 100644 mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_base_fb.h rename mqtt_streaming_client_module/include/mqtt_streaming_client_module/{mqtt_receiver_fb_impl.h => mqtt_json_receiver_fb_impl.h} (72%) create mode 100644 mqtt_streaming_client_module/src/mqtt_base_fb.cpp create mode 100644 mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp delete mode 100644 mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_base_fb.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_base_fb.h new file mode 100644 index 00000000..e37daddf --- /dev/null +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_base_fb.h @@ -0,0 +1,55 @@ +/* + * Copyright 2022-2025 openDAQ d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +#include "MqttAsyncClient.h" + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +class MqttBaseFb : public FunctionBlock +{ +public: + explicit MqttBaseFb(const ContextPtr& ctx, + const ComponentPtr& parent, + const FunctionBlockTypePtr& type, + const StringPtr& localId, + std::shared_ptr subscriber, + const PropertyObjectPtr& config = nullptr); + ~MqttBaseFb() = default; + +protected: + std::shared_ptr subscriber; + + virtual void createSignals() = 0; + virtual void processMessage(const mqtt::MqttMessage& msg) = 0; + + virtual void initProperties(const PropertyObjectPtr& config); + virtual void readProperties() = 0; + + void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg); + + virtual std::vector getSubscribedTopics() const = 0; + virtual void clearSubscribedTopics() = 0; + virtual void subscribeToTopics(); + virtual void unsubscribeFromTopics(); + + void removed() override; +}; + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h similarity index 72% rename from mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h rename to mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h index 0021bb4b..dd4af679 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h @@ -15,50 +15,40 @@ */ #pragma once -#include -#include -#include - #include "MqttAsyncClient.h" #include "MqttDataWrapper.h" +#include +#include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE - -class MqttReceiverFbImpl final : public FunctionBlock + +class MqttJsonReceiverFbImpl final : public MqttBaseFb { friend class MqttJsonFbHelper; + public: - explicit MqttReceiverFbImpl(const ContextPtr& ctx, + explicit MqttJsonReceiverFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, const FunctionBlockTypePtr& type, const StringPtr& localId, std::shared_ptr subscriber, const PropertyObjectPtr& config = nullptr); - ~MqttReceiverFbImpl() override; + ~MqttJsonReceiverFbImpl() override; private: + mutable std::mutex sync; mqtt::MqttDataWrapper jsonDataWorker; std::unordered_map outputSignals; - - std::shared_ptr subscriber; std::unordered_map subscribedSignals; - mutable std::mutex sync; - - void createSignals(); + void createSignals() override; + void clearSubscribedTopics() override; + std::vector getSubscribedTopics() const override; + void processMessage(const mqtt::MqttMessage& msg) override; + void readProperties() override; - void parseMessage(const mqtt::MqttMessage& msg); void createDataPacket(const std::string& topic, const std::string& json); - - void initProperties(const PropertyObjectPtr& config); - void readProperties(); - - void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg); - - std::vector getSubscribedTopics() const; - void unsubscribeFromTopics(); - - void removed() override; }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h index db8210ea..59cb80ee 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h @@ -15,45 +15,36 @@ */ #pragma once +#include #include +#include #include -#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE - -class MqttRawReceiverFbImpl final : public FunctionBlock + +class MqttRawReceiverFbImpl final : public MqttBaseFb { friend class MqttRawFbTest; + public: explicit MqttRawReceiverFbImpl(const ContextPtr& ctx, - const ComponentPtr& parent, - const FunctionBlockTypePtr& type, - const StringPtr& localId, - std::shared_ptr subscriber, - const PropertyObjectPtr& config = nullptr); + const ComponentPtr& parent, + const FunctionBlockTypePtr& type, + const StringPtr& localId, + std::shared_ptr subscriber, + const PropertyObjectPtr& config = nullptr); ~MqttRawReceiverFbImpl() override; private: + std::mutex sync; std::unordered_map outputSignals; - - std::shared_ptr subscriber; std::vector topicsForSubscribing; - std::mutex sync; - - void createSignals(); - - void createAndSendDataPacket(mqtt::MqttMessage& msg); - - void initProperties(const PropertyObjectPtr& config); - void readProperties(); - - void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); - - void subscribeToTopics(); - void unsubscribeFromTopics(); - - void removed() override; + void createSignals() override; + void clearSubscribedTopics() override; + std::vector getSubscribedTopics() const override; + void processMessage(const mqtt::MqttMessage& msg) override; + void readProperties() override; }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/CMakeLists.txt b/mqtt_streaming_client_module/src/CMakeLists.txt index 6b136dd7..4e7878f6 100644 --- a/mqtt_streaming_client_module/src/CMakeLists.txt +++ b/mqtt_streaming_client_module/src/CMakeLists.txt @@ -5,7 +5,8 @@ set(SRC_Include common.h module_dll.h mqtt_streaming_client_module_impl.h mqtt_streaming_device_impl.h - mqtt_receiver_fb_impl.h + mqtt_base_fb.h + mqtt_json_receiver_fb_impl.h mqtt_raw_receiver_fb_impl.h constants.h helper.h @@ -14,7 +15,8 @@ set(SRC_Include common.h set(SRC_Srcs module_dll.cpp mqtt_streaming_client_module_impl.cpp mqtt_streaming_device_impl.cpp - mqtt_receiver_fb_impl.cpp + mqtt_base_fb.cpp + mqtt_json_receiver_fb_impl.cpp mqtt_raw_receiver_fb_impl.cpp helper.cpp ) @@ -37,10 +39,12 @@ source_group("device" FILES ${MODULE_HEADERS_DIR}/mqtt_streaming_device_impl.h mqtt_streaming_device_impl.cpp ) -source_group("functionalBlock" FILES ${MODULE_HEADERS_DIR}/mqtt_receiver_fb_impl.h - mqtt_receiver_fb_impl.cpp +source_group("functionalBlock" FILES ${MODULE_HEADERS_DIR}/mqtt_json_receiver_fb_impl.h + mqtt_json_receiver_fb_impl.cpp ${MODULE_HEADERS_DIR}/mqtt_raw_receiver_fb_impl.h mqtt_raw_receiver_fb_impl.cpp + ${MODULE_HEADERS_DIR}/mqtt_base_fb.h + mqtt_base_fb.cpp ) find_package(Boost REQUIRED COMPONENTS algorithm) diff --git a/mqtt_streaming_client_module/src/mqtt_base_fb.cpp b/mqtt_streaming_client_module/src/mqtt_base_fb.cpp new file mode 100644 index 00000000..548c03b0 --- /dev/null +++ b/mqtt_streaming_client_module/src/mqtt_base_fb.cpp @@ -0,0 +1,97 @@ +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +constexpr int MQTT_FB_UNSUBSCRIBE_TOUT = 3000; + +MqttBaseFb::MqttBaseFb(const ContextPtr& ctx, + const ComponentPtr& parent, + const FunctionBlockTypePtr& type, + const StringPtr& localId, + std::shared_ptr subscriber, + const PropertyObjectPtr& config) + : FunctionBlock(type, ctx, parent, localId) + , subscriber(subscriber) +{ + initComponentStatus(); +} + + +void MqttBaseFb::removed() +{ + FunctionBlock::removed(); + unsubscribeFromTopics(); +} + +void MqttBaseFb::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg) +{ + processMessage(msg); +} + +void MqttBaseFb::initProperties(const PropertyObjectPtr& config) +{ + for (const auto& prop : config.getAllProperties()) + { + const auto propName = prop.getName(); + if (!objPtr.hasProperty(propName)) + { + if (const auto internalProp = prop.asPtrOrNull(true); internalProp.assigned()) + { + objPtr.addProperty(internalProp.clone()); + } + } + objPtr.setPropertyValue(propName, prop.getValue()); + } + readProperties(); +} + +void MqttBaseFb::subscribeToTopics() +{ + if (subscriber) + { + bool success = true; + auto lambda = [this](const mqtt::MqttAsyncClient &client, mqtt::MqttMessage &msg){this->onSignalsMessage(client, msg);}; + for (const auto& topic : getSubscribedTopics()) + { + subscriber->setMessageArrivedCb(topic, lambda); + auto result = subscriber->subscribe(topic, 1); + success &= result.success; + if (!result.success) + LOG_W("Failed to subscribe to the topic: {}; reason: {}", topic, result.msg); + } + if (!success) + setComponentStatusWithMessage(ComponentStatus::Warning, "Some topics failed to subscribe!"); + } + else + { + setComponentStatusWithMessage(ComponentStatus::Error, "MQTT subscriber client is not set!"); + } +} + +void MqttBaseFb::unsubscribeFromTopics() +{ + if (!subscriber) + { + LOG_E("The subscriber is null"); + return; + } + const auto topics = getSubscribedTopics(); + if (topics.empty()) + return; + subscriber->setMessageArrivedCb(topics, nullptr); + auto result = subscriber->unsubscribe(topics); + if (result.success) + result = subscriber->waitForCompletion(result.token, MQTT_FB_UNSUBSCRIBE_TOUT); + + if (result.success) + { + clearSubscribedTopics(); + LOG_I("All topics have been unsubscribed successfully"); + } + else + { + LOG_W("Failed to unsubscribe from all topics; reason: {}", result.msg); + } +} + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp new file mode 100644 index 00000000..3299f7ad --- /dev/null +++ b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp @@ -0,0 +1,121 @@ +#include "mqtt_streaming_client_module/constants.h" +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +constexpr int MQTT_JSON_FB_UNSUBSCRIBE_TOUT = 3000; + +MqttJsonReceiverFbImpl::MqttJsonReceiverFbImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const FunctionBlockTypePtr& type, + const StringPtr& localId, + std::shared_ptr subscriber, + const PropertyObjectPtr& config) + : MqttBaseFb(ctx, parent, type, localId, subscriber, config), + jsonDataWorker(loggerComponent) +{ + if (config.assigned()) + initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); + else + initProperties(type.createDefaultConfig()); + + createSignals(); + subscribeToTopics(); +} + +MqttJsonReceiverFbImpl::~MqttJsonReceiverFbImpl() +{ + unsubscribeFromTopics(); +} + +void MqttJsonReceiverFbImpl::readProperties() +{ + auto lock = std::lock_guard(sync); + subscribedSignals.clear(); + if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) + { + auto signalConfig = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtrOrNull(); + if (signalConfig.assigned()) + { + jsonDataWorker.setConfig(signalConfig.toStdString()); + subscribedSignals = jsonDataWorker.extractDescription(); + LOG_I("Signal in list:"); + for (const auto& [signalId, descriptor] : subscribedSignals) + { + LOG_I("{} | {}", signalId.topic, signalId.signalName); + } + } + } +} + +void MqttJsonReceiverFbImpl::createDataPacket(const std::string& topic, const std::string& json) +{ + auto lock = std::lock_guard(sync); + jsonDataWorker.createAndSendDataPacket(topic, json); +} + +void MqttJsonReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) +{ + std::string topic(msg.getTopic()); + std::string jsonObjStr(msg.getData().begin(), msg.getData().end()); + createDataPacket(topic, jsonObjStr); +} + +void MqttJsonReceiverFbImpl::createSignals() +{ + auto lock = std::lock_guard(sync); + for (const auto& [signalId, descriptor] : subscribedSignals) + { + LOG_I("Creating signal \"{}\" for topic \"{}\"", signalId.signalName, signalId.topic); + const std::string& topic = signalId.topic; + + auto signalDsc = descriptor; + + auto refS = + outputSignals + .emplace(std::make_pair(signalId, createAndAddSignal(buildSignalNameFromTopic(topic, signalId.signalName), signalDsc))) + .first; + if (jsonDataWorker.hasDomainSignal(signalId)) + { + auto getEpoch = []() -> std::string + { + const std::time_t epochTime = std::chrono::system_clock::to_time_t(std::chrono::time_point{}); + char buf[48]; + strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&epochTime)); + return {buf}; + }; + + const auto domainSignalDsc = DataDescriptorBuilder() + .setSampleType(SampleType::UInt64) + .setUnit(Unit("s", -1, "seconds", "time")) + .setTickResolution(Ratio(1, 1'000'000)) + .setOrigin(getEpoch()) + .setName("Time") + .build(); + refS->second->setDomainSignal( + createAndAddSignal(buildDomainSignalNameFromTopic(topic, signalId.signalName), domainSignalDsc, false)); + } + } + jsonDataWorker.setOutputSignals(&outputSignals); +} + +std::vector MqttJsonReceiverFbImpl::getSubscribedTopics() const +{ + auto lock = std::lock_guard(sync); + std::set topicsSet; + for (const auto& [signalId, _] : subscribedSignals) + { + topicsSet.emplace(signalId.topic); + } + return std::vector(topicsSet.cbegin(), topicsSet.cend()); +} + +void MqttJsonReceiverFbImpl::clearSubscribedTopics() +{ + subscribedSignals.clear(); +} + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index 3c11a1ad..ca8c4337 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -1,6 +1,6 @@ +#include "MqttDataWrapper.h" #include "mqtt_streaming_client_module/constants.h" #include "mqtt_streaming_client_module/helper.h" -#include "MqttDataWrapper.h" #include #include #include @@ -15,9 +15,8 @@ MqttRawReceiverFbImpl::MqttRawReceiverFbImpl(const ContextPtr& ctx, const StringPtr& localId, std::shared_ptr subscriber, const PropertyObjectPtr& config) - : FunctionBlock(type, ctx, parent, localId), subscriber(subscriber) + : MqttBaseFb(ctx, parent, type, localId, subscriber, config) { - initComponentStatus(); if (config.assigned()) initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); else @@ -25,8 +24,6 @@ MqttRawReceiverFbImpl::MqttRawReceiverFbImpl(const ContextPtr& ctx, createSignals(); subscribeToTopics(); - - setComponentStatus(ComponentStatus::Ok); } MqttRawReceiverFbImpl::~MqttRawReceiverFbImpl() @@ -34,36 +31,6 @@ MqttRawReceiverFbImpl::~MqttRawReceiverFbImpl() unsubscribeFromTopics(); } -void MqttRawReceiverFbImpl::removed() -{ - FunctionBlock::removed(); - unsubscribeFromTopics(); -} - -void MqttRawReceiverFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, - mqtt::MqttMessage& msg) -{ - createAndSendDataPacket(msg); -} - -void MqttRawReceiverFbImpl::initProperties(const PropertyObjectPtr& config) -{ - for (const auto& prop : config.getAllProperties()) - { - const auto propName = prop.getName(); - if (!objPtr.hasProperty(propName)) - { - if (const auto internalProp = prop.asPtrOrNull(true); - internalProp.assigned()) - { - objPtr.addProperty(internalProp.clone()); - } - } - objPtr.setPropertyValue(propName, prop.getValue()); - } - readProperties(); -} - void MqttRawReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); @@ -96,7 +63,7 @@ void MqttRawReceiverFbImpl::readProperties() } } -void MqttRawReceiverFbImpl::createAndSendDataPacket(mqtt::MqttMessage& msg) +void MqttRawReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) { std::string topic(msg.getTopic()); @@ -108,8 +75,7 @@ void MqttRawReceiverFbImpl::createAndSendDataPacket(mqtt::MqttMessage& msg) } const auto& signal = signalIter->second; - const auto outputPacket = - BinaryDataPacket(nullptr, signal.getDescriptor(), msg.getData().size()); + const auto outputPacket = BinaryDataPacket(nullptr, signal.getDescriptor(), msg.getData().size()); memcpy(outputPacket.getData(), msg.getData().data(), msg.getData().size()); signal.sendPacket(outputPacket); } @@ -122,53 +88,18 @@ void MqttRawReceiverFbImpl::createSignals() LOG_I("Subscribing to topic: {}", topic); const auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::Binary).build(); - outputSignals.emplace( - std::make_pair(topic, createAndAddSignal(buildSignalNameFromTopic(topic, ""), signalDsc))); + outputSignals.emplace(std::make_pair(topic, createAndAddSignal(buildSignalNameFromTopic(topic, ""), signalDsc))); } } -void MqttRawReceiverFbImpl::subscribeToTopics() +std::vector MqttRawReceiverFbImpl::getSubscribedTopics() const { - if (!subscriber) - { - LOG_E("The subscriber is null"); - return; - } - for (const auto& topic : topicsForSubscribing) - { - subscriber->setMessageArrivedCb(topic, - std::bind(&MqttRawReceiverFbImpl::onSignalsMessage, - this, - std::placeholders::_1, - std::placeholders::_2)); - auto result = subscriber->subscribe(topic, 1); - if (!result.success) - LOG_W("Failed to subscribe to the topic: {}; reason: {}", topic, result.msg); - } + return topicsForSubscribing; } -void MqttRawReceiverFbImpl::unsubscribeFromTopics() +void MqttRawReceiverFbImpl::clearSubscribedTopics() { - if (!subscriber) - { - LOG_E("The subscriber is null"); - return; - } - if (topicsForSubscribing.empty()) - return; - subscriber->setMessageArrivedCb(topicsForSubscribing, nullptr); - auto result = subscriber->unsubscribe(topicsForSubscribing); - if (result.success) - result = subscriber->waitForCompletion(result.token, MQTT_RAW_FB_UNSUBSCRIBE_TOUT); - - if (result.success) - { - topicsForSubscribing.clear(); - LOG_I("All topics have been unsubscribed successfully"); - } - else - { - LOG_W("Failed to unsubscribe from all topics; reason: {}", result.msg); - } + topicsForSubscribing.clear(); } + END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp deleted file mode 100644 index 3aae0946..00000000 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include "mqtt_streaming_client_module/constants.h" -#include -#include -#include - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE - -constexpr int MQTT_JSON_FB_UNSUBSCRIBE_TOUT = 3000; - -MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, - const ComponentPtr& parent, - const FunctionBlockTypePtr& type, - const StringPtr& localId, - std::shared_ptr subscriber, - const PropertyObjectPtr& config) - : FunctionBlock(type, ctx, parent, localId) - , jsonDataWorker(loggerComponent) - , subscriber(subscriber) -{ - initComponentStatus(); - - if (config.assigned()) - initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); - else - initProperties(type.createDefaultConfig()); - - createSignals(); - - if (subscriber) - { - for (const auto& topic : getSubscribedTopics()) - { - subscriber - ->setMessageArrivedCb(topic, - std::bind(&MqttReceiverFbImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); - auto result = subscriber->subscribe(topic, 1); - if (!result.success) - LOG_W("Failed to subscribe to the topic: {}; reason: {}", topic, result.msg); - } - setComponentStatus(ComponentStatus::Ok); - } - else - { - setComponentStatusWithMessage(ComponentStatus::Error, "MQTT subscriber client is not set."); - } -} - -MqttReceiverFbImpl::~MqttReceiverFbImpl() -{ - unsubscribeFromTopics(); -} - -void MqttReceiverFbImpl::removed() -{ - FunctionBlock::removed(); - unsubscribeFromTopics(); -} - -void MqttReceiverFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg) -{ - parseMessage(msg); -} - -void MqttReceiverFbImpl::initProperties(const PropertyObjectPtr& config) -{ - for (const auto& prop : config.getAllProperties()) - { - const auto propName = prop.getName(); - if (!objPtr.hasProperty(propName)) - { - if (const auto internalProp = prop.asPtrOrNull(true); internalProp.assigned()) - { - objPtr.addProperty(internalProp.clone()); - } - } - objPtr.setPropertyValue(propName, prop.getValue()); - } - readProperties(); -} - -void MqttReceiverFbImpl::readProperties() -{ - auto lock = std::lock_guard(sync); - subscribedSignals.clear(); - if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) { - auto signalConfig = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtrOrNull(); - if (signalConfig.assigned()) { - jsonDataWorker.setConfig(signalConfig.toStdString()); - subscribedSignals = jsonDataWorker.extractDescription(); - LOG_I("Signal in list:"); - for (const auto& [signalId, descriptor] : subscribedSignals) { - LOG_I("{} | {}", signalId.topic, signalId.signalName); - } - } - } -} - -void MqttReceiverFbImpl::createDataPacket(const std::string& topic, const std::string& json) -{ - auto lock = std::lock_guard(sync); - jsonDataWorker.createAndSendDataPacket(topic, json); -} - -void MqttReceiverFbImpl::parseMessage(const mqtt::MqttMessage& msg) -{ - std::string topic(msg.getTopic()); - std::string jsonObjStr(msg.getData().begin(), msg.getData().end()); - createDataPacket(topic, jsonObjStr); -} - -void MqttReceiverFbImpl::createSignals() -{ - auto lock = std::lock_guard(sync); - for (const auto& [signalId, descriptor] : subscribedSignals) - { - LOG_I("Creating signal \"{}\" for topic \"{}\"", signalId.signalName, signalId.topic); - const std::string& topic = signalId.topic; - - auto signalDsc = descriptor; - - auto refS = outputSignals.emplace(std::make_pair(signalId, createAndAddSignal(buildSignalNameFromTopic(topic, signalId.signalName), signalDsc))).first; - if (jsonDataWorker.hasDomainSignal(signalId)) - { - auto getEpoch = []() ->std::string { - const std::time_t epochTime = std::chrono::system_clock::to_time_t(std::chrono::time_point{}); - char buf[48]; - strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&epochTime)); - return { buf }; - }; - - const auto domainSignalDsc = - DataDescriptorBuilder() - .setSampleType(SampleType::UInt64) - .setUnit(Unit("s", -1, "seconds", "time")) - .setTickResolution(Ratio(1, 1'000'000)) - .setOrigin(getEpoch()) - .setName("Time").build(); - refS->second->setDomainSignal(createAndAddSignal(buildDomainSignalNameFromTopic(topic, signalId.signalName), domainSignalDsc, false)); - } - } - jsonDataWorker.setOutputSignals(&outputSignals); -} - -std::vector MqttReceiverFbImpl::getSubscribedTopics() const -{ - auto lock = std::lock_guard(sync); - std::set topicsSet; - for (const auto& [signalId, _] : subscribedSignals) - { - topicsSet.emplace(signalId.topic); - } - return std::vector(topicsSet.cbegin(), topicsSet.cend()); -} - -void MqttReceiverFbImpl::unsubscribeFromTopics() -{ - if (!subscriber) - { - LOG_E("The subscriber is null"); - return; - } - const auto topics = getSubscribedTopics(); - if (topics.empty()) - return; - subscriber->setMessageArrivedCb(topics, nullptr); - auto result = subscriber->unsubscribe(topics); - if (result.success) - result = subscriber->waitForCompletion(result.token, MQTT_JSON_FB_UNSUBSCRIBE_TOUT); - - if (result.success) - { - subscribedSignals.clear(); - LOG_I("All topics have been unsubscribed successfully"); - } - else - { - LOG_W("Failed to unsubscribe from all topics; reason: {}", result.msg); - } -} - -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp index e68129b3..ee545c88 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index 0c009ab8..9fd0e270 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -1,5 +1,5 @@ #include "mqtt_streaming_client_module/constants.h" -#include "mqtt_streaming_client_module/mqtt_receiver_fb_impl.h" +#include "mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h" #include "mqtt_streaming_client_module/mqtt_raw_receiver_fb_impl.h" #include @@ -194,7 +194,7 @@ FunctionBlockPtr MqttStreamingDeviceImpl::onAddFunctionBlock(const StringPtr& ty } else { - nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, typeId, subscriber, config); + nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, typeId, subscriber, config); } addNestedFunctionBlock(nestedFunctionBlock); diff --git a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp index 6ed6ac26..9258c4f3 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp @@ -1,6 +1,6 @@ #include "MqttAsyncClientWrapper.h" #include "mqtt_streaming_client_module/helper.h" -#include "mqtt_streaming_client_module/mqtt_receiver_fb_impl.h" +#include "mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h" #include "test_daq_test_helper.h" #include "test_data.h" #include "timestampConverter.h" @@ -33,7 +33,7 @@ namespace daq::modules::mqtt_streaming_client_module class MqttJsonFbHelper { public: - std::unique_ptr obj; + std::unique_ptr obj; void onSignalsMessage(const mqtt::MqttMessage& msg) { @@ -47,7 +47,7 @@ class MqttJsonFbHelper config.addProperty(StringProperty(PROPERTY_NAME_SIGNAL_LIST, String(""))); const auto fbType = FunctionBlockType(JSON_FB_NAME, JSON_FB_NAME, "", config); config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); - obj = std::make_unique(NullContext(), nullptr, fbType, "localId", nullptr, config); + obj = std::make_unique(NullContext(), nullptr, fbType, "localId", nullptr, config); } auto getSignals() From 774834c524d046602dc17b625156c9ec9415d05f Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 31 Oct 2025 15:56:50 +0100 Subject: [PATCH 38/55] mqtt: logs --- .../src/mqtt_base_fb.cpp | 9 +++++++ .../src/mqtt_json_receiver_fb_impl.cpp | 24 +++++++++++++++--- .../src/mqtt_raw_receiver_fb_impl.cpp | 8 ++++-- .../src/mqtt_streaming_device_impl.cpp | 25 +++++++++++++++++-- .../src/MqttDataWrapper.cpp | 6 +++++ 5 files changed, 65 insertions(+), 7 deletions(-) diff --git a/mqtt_streaming_client_module/src/mqtt_base_fb.cpp b/mqtt_streaming_client_module/src/mqtt_base_fb.cpp index 548c03b0..f6bbf6d1 100644 --- a/mqtt_streaming_client_module/src/mqtt_base_fb.cpp +++ b/mqtt_streaming_client_module/src/mqtt_base_fb.cpp @@ -51,13 +51,22 @@ void MqttBaseFb::subscribeToTopics() { bool success = true; auto lambda = [this](const mqtt::MqttAsyncClient &client, mqtt::MqttMessage &msg){this->onSignalsMessage(client, msg);}; + if (!getSubscribedTopics().empty()) + LOG_I("Trying to subscribe to the topics"); for (const auto& topic : getSubscribedTopics()) { subscriber->setMessageArrivedCb(topic, lambda); auto result = subscriber->subscribe(topic, 1); success &= result.success; if (!result.success) + { LOG_W("Failed to subscribe to the topic: {}; reason: {}", topic, result.msg); + } + else + { + // subscriber->subscribe(...) is asynchronous. It puts command in queue and returns immediately. + LOG_D("Trying to subscribe to the topic: {}", topic); + } } if (!success) setComponentStatusWithMessage(ComponentStatus::Warning, "Some topics failed to subscribe!"); diff --git a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp index 3299f7ad..00b6ae0e 100644 --- a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp @@ -35,20 +35,30 @@ void MqttJsonReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); subscribedSignals.clear(); + bool isPresent = false; if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) { auto signalConfig = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtrOrNull(); if (signalConfig.assigned()) { + isPresent = true; jsonDataWorker.setConfig(signalConfig.toStdString()); subscribedSignals = jsonDataWorker.extractDescription(); - LOG_I("Signal in list:"); + LOG_I("Signal in the list (topic | signal name):"); for (const auto& [signalId, descriptor] : subscribedSignals) { - LOG_I("{} | {}", signalId.topic, signalId.signalName); + LOG_I("\t\"{}\" | \"{}\"", signalId.topic, signalId.signalName); } } } + if (!isPresent) + { + LOG_W("{} property is missing!", PROPERTY_NAME_SIGNAL_LIST); + } + if (subscribedSignals.empty()) + { + LOG_W("No signals in the list!"); + } } void MqttJsonReceiverFbImpl::createDataPacket(const std::string& topic, const std::string& json) @@ -67,9 +77,12 @@ void MqttJsonReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) void MqttJsonReceiverFbImpl::createSignals() { auto lock = std::lock_guard(sync); + if (!subscribedSignals.empty()) + LOG_I("Creating signals..."); + for (const auto& [signalId, descriptor] : subscribedSignals) { - LOG_I("Creating signal \"{}\" for topic \"{}\"", signalId.signalName, signalId.topic); + LOG_D("\tfor the topic \"{}\"", signalId.signalName, signalId.topic); const std::string& topic = signalId.topic; auto signalDsc = descriptor; @@ -80,6 +93,7 @@ void MqttJsonReceiverFbImpl::createSignals() .first; if (jsonDataWorker.hasDomainSignal(signalId)) { + LOG_D("\tThe signal has a domain signal"); auto getEpoch = []() -> std::string { const std::time_t epochTime = std::chrono::system_clock::to_time_t(std::chrono::time_point{}); @@ -98,6 +112,10 @@ void MqttJsonReceiverFbImpl::createSignals() refS->second->setDomainSignal( createAndAddSignal(buildDomainSignalNameFromTopic(topic, signalId.signalName), domainSignalDsc, false)); } + else + { + LOG_D("\tThe signal doesn't have a domain signal"); + } } jsonDataWorker.setOutputSignals(&outputSignals); } diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index ca8c4337..f1335117 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -42,12 +42,14 @@ void MqttRawReceiverFbImpl::readProperties() if (prop.assigned()) { isPresent = true; + if (prop.getCount() != 0) + LOG_I("Topics in the list:"); for (const auto& topic : prop) { auto topicStr = topic.asPtr(); if (mqtt::MqttDataWrapper::validateTopic(topicStr, loggerComponent)) { - LOG_I("Topic in list: {}", topicStr.toStdString()); + LOG_I("\t{}", topicStr.toStdString()); topicsForSubscribing.emplace_back(topicStr.toStdString()); } } @@ -83,9 +85,11 @@ void MqttRawReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) void MqttRawReceiverFbImpl::createSignals() { auto lock = std::lock_guard(sync); + if (!topicsForSubscribing.empty()) + LOG_I("Creating signals..."); for (const auto& topic : topicsForSubscribing) { - LOG_I("Subscribing to topic: {}", topic); + LOG_D("\tfor the topic: {}", topic); const auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::Binary).build(); outputSignals.emplace(std::make_pair(topic, createAndAddSignal(buildSignalNameFromTopic(topic, ""), signalDsc))); diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index 9fd0e270..7c379e89 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -42,6 +42,7 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const Co initComponentStatus(); initBaseFunctionalBlocks(); initMqttSubscriber(); + if (!waitForConnection(connectTimeout)) { LOG_E("MQTT: could not connect to MQTT broker within {} ms", connectTimeout); @@ -57,9 +58,16 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const Co void MqttStreamingDeviceImpl::removed() { Device::removed(); + LOG_I("MQTT: disconnecting from the MQTT broker...", connectionSettings.mqttUrl + ":" + std::to_string(connectionSettings.port)); bool disRes = subscriber->syncDisconnect(MQTT_CLIENT_SYNC_DISCONNECT_TOUT); if (!disRes) + { LOG_E("MQTT: disconnection was unsuccessful"); + } + else + { + LOG_I("MQTT: disconnection was successful"); + } } DeviceInfoPtr MqttStreamingDeviceImpl::onGetInfo() @@ -116,7 +124,7 @@ void MqttStreamingDeviceImpl::initMqttSubscriber() } }); - LOG_I("MQTT: Trying to connect to MQTT broker ({})", serverUrl); + LOG_I("MQTT: Trying to connect to the MQTT broker ({})", serverUrl); subscriber->connect(); } @@ -143,7 +151,7 @@ void MqttStreamingDeviceImpl::receiveSignalTopics(const int timeoutMs) } else { - LOG_I("Signal discovering step was skipped"); + LOG_W("Signal discovering step was skipped"); } } @@ -174,6 +182,19 @@ void MqttStreamingDeviceImpl::buildFunctionBlockTypes() fbTypes.set(fbType.getId(), fbType); } + if (fbTypes.getCount() != 0) + { + LOG_I("Function block types available:"); + } + else + { + LOG_I("No function block types available"); + } + + for (const auto& [fbName, _] : fbTypes) + { + LOG_I("\t{}", fbName.toStdString()); + } } DictPtr MqttStreamingDeviceImpl::onGetAvailableFunctionBlockTypes() diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 92415f35..3ae78b94 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -158,6 +158,12 @@ std::unordered_map MqttDataWrapper::extr rapidjson::Document doc; topicDescriptors.clear(); + if (config.empty()) + { + LOG_E("The JSON config is empty"); + return result; + } + if (doc.Parse(config.c_str()).HasParseError()) { LOG_E("The JSON config has wrong format"); From 1b8c0127bc3334fdf25d0e05aec3b9400318ca05 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 4 Nov 2025 15:21:55 +0100 Subject: [PATCH 39/55] mqtt: supporting for different value types: int64_t, double, string --- .../custom-mqtt-sub/src/custom-mqtt-sub.cpp | 180 +++++++++++++----- .../include/MqttDataWrapper.h | 16 +- .../src/MqttDataWrapper.cpp | 154 ++++++++++++--- 3 files changed, 276 insertions(+), 74 deletions(-) diff --git a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp index 9df2c53c..f4da4841 100644 --- a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp +++ b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp @@ -1,14 +1,107 @@ -#include #include "../../InputArgs.h" +#include #include +#include -#include #include +#include #include using namespace daq; using namespace daq::modules::mqtt_streaming_client_module; +std::string to_string(SampleType sampleType) +{ + switch (sampleType) + { + case SampleType::Float32: + return "Float32"; + case SampleType::Float64: + return "Float64"; + case SampleType::UInt8: + return "UInt8"; + case SampleType::Int8: + return "Int8"; + case SampleType::UInt16: + return "UInt16"; + case SampleType::Int16: + return "Int16"; + case SampleType::UInt32: + return "UInt32"; + case SampleType::Int32: + return "Int32"; + case SampleType::UInt64: + return "UInt64"; + case SampleType::Int64: + return "Int64"; + case SampleType::RangeInt64: + return "RangeInt64"; + case SampleType::ComplexFloat32: + return "ComplexFloat32"; + case SampleType::ComplexFloat64: + return "ComplexFloat64"; + case SampleType::Struct: + return "Struct"; + case SampleType::Undefined: + return "Undefined"; + case SampleType::Binary: + return "Binary"; + case SampleType::String: + return "String"; + case SampleType::Null: + return "Null"; + case SampleType::_count: + return "Count"; + } + return "Unknown"; +} + +std::string to_string(uint64_t ts) +{ + using namespace std::chrono; + + system_clock::time_point tp = system_clock::time_point(microseconds(ts)); + + auto tt = system_clock::to_time_t(tp); + std::tm tm = *std::localtime(&tt); + + auto us = duration_cast(tp.time_since_epoch()) % 1000; + + std::ostringstream oss; + oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") << '.' << std::setfill('0') << std::setw(3) << us.count(); + return oss.str(); +} + +std::string to_string(daq::DataPacketPtr packet) +{ + std::string result; + std::string data; + switch (packet.getDataDescriptor().getSampleType()) + { + case SampleType::Float64: + data = std::to_string(*(static_cast(packet.getData()))); + break; + case SampleType::UInt64: + data = std::to_string(*(static_cast(packet.getData()))); + break; + case SampleType::Int64: + data = std::to_string(*(static_cast(packet.getData()))); + break; + case SampleType::Binary: + data = '\"' + std::string(static_cast(packet.getData()), packet.getDataSize()) + '\"'; + break; + default: + break; + } + result = fmt::format("SampleType : {}; Data: {};", to_string(packet.getDataDescriptor().getSampleType()), data); + if (packet.getDomainPacket().assigned()) + { + uint64_t ts = *(static_cast(packet.getDomainPacket().getData())); + result += fmt::format(" Time : {};", to_string(ts)); + } + return result; +} + std::string readFileToString(const std::string& filePath) { std::ifstream file(filePath); @@ -16,7 +109,7 @@ std::string readFileToString(const std::string& filePath) throw std::runtime_error("Failed to open file: " + filePath); std::ostringstream buffer; - buffer << file.rdbuf(); // Read the entire file buffer + buffer << file.rdbuf(); // Read the entire file buffer return buffer.str(); } @@ -28,14 +121,16 @@ int main(int argc, char* argv[]) args.setUsageHelp(APP_NAME " [options] "); args.parse(argc, argv); - if (args.hasArg("--help") || args.hasUnknownArgs()) { + if (args.hasArg("--help") || args.hasUnknownArgs()) + { args.printHelp(); return 0; } std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); auto configFilePath = args.getPositionalArgs(); - if (configFilePath.size() != 1) { + if (configFilePath.size() != 1) + { std::cout << "Configuration file path is required." << std::endl; return -1; } @@ -44,12 +139,14 @@ int main(int argc, char* argv[]) auto brokerDevice = instance.addDevice("daq.mqtt://" + brokerAddress); auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); - if (availableDeviceNodes.getCount() == 0) { + if (availableDeviceNodes.getCount() == 0) + { std::cout << "No function block available from the device." << std::endl; return -1; } - for (const auto& [key, value] : availableDeviceNodes) { + for (const auto& [key, value] : availableDeviceNodes) + { std::cout << "Available function block: " << key << std::endl; } const std::string fbName = JSON_FB_NAME; @@ -61,7 +158,6 @@ int main(int argc, char* argv[]) config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); daq::FunctionBlockPtr jsonFb = brokerDevice.addFunctionBlock(fbName, config); - auto signals = jsonFb.getSignals(); using ReaderContainerEntryType = std::pair, StreamReaderPtr>; @@ -69,51 +165,41 @@ int main(int argc, char* argv[]) ReaderContainerType readers; for (const auto& s : signals) { - readers.emplace_back(ReaderContainerEntryType( - std::pair(s, daq::StreamReader(s, ReadTimeoutType::Any)))); + readers.emplace_back(ReaderContainerEntryType(std::pair(s, daq::StreamReader(s, ReadTimeoutType::Any)))); } - std::thread readerThread([readers]() { - constexpr int size = 1000; - - std::vector samplesVec(size); - std::vector timestampsVec(size); - auto samples = samplesVec.data(); - auto timestamps = timestampsVec.data(); + std::map packetReaders; + for (const auto& s : signals) + { + packetReaders.emplace(std::pair(s.getName().toStdString(), daq::PacketReader(s))); + } - auto readWithDomain = [samples, timestamps](const daq::GenericSignalPtr<> signal, const daq::StreamReaderPtr reader) + std::thread readerThread( + [packetReaders]() { - daq::SizeT count = size; - reader.readWithDomain(samples, timestamps, &count); - const std::string sampleUnit = (signal.getDescriptor().assigned() && signal.getDescriptor().getUnit().assigned()) ? - " " + signal.getDescriptor().getUnit().getSymbol().toStdString() : ""; - for (daq::SizeT i = 0; i < count; ++i) - std::cout << signal.getName() << " - Sample: " << samples[i] << sampleUnit << " Timestamp: " << timestamps[i] << std::endl; - }; - - auto read = [samples](const daq::GenericSignalPtr<> signal, const daq::StreamReaderPtr reader) - { - daq::SizeT count = size; - reader.read(samples, &count); - const std::string sampleUnit = (signal.getDescriptor().assigned() && signal.getDescriptor().getUnit().assigned()) ? - " " + signal.getDescriptor().getUnit().getSymbol().toStdString() : ""; - for (daq::SizeT i = 0; i < count; ++i) - std::cout << signal.getName() << " - Sample: " << samples[i] << sampleUnit << std::endl; - }; - - while (true) { - for (const auto& [signal, reader] : readers) { - while (!reader.getEmpty()) { - if (signal.getDomainSignal().assigned()) - readWithDomain(signal, reader); - else - read(signal, reader); - + while (true) + { + for (const auto& [signal, reader] : packetReaders) + { + while (!reader.getEmpty()) + { + auto packet = reader.read(); + const auto eventPacket = packet.asPtrOrNull(); + if (eventPacket.assigned()) + { + std::cout << "Event packet is skipped!" << std::endl; + continue; + } + const auto dataPacket = packet.asPtrOrNull(); + if (dataPacket.assigned()) + { + std::cout << signal << " - " << to_string(dataPacket) << std::endl; + } + } } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); } - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } - }); + }); readerThread.detach(); std::cout << "Press \"enter\" to exit the application..." << std::endl; diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/mqtt_streaming_protocol/include/MqttDataWrapper.h index b5afbb1a..943c03ea 100644 --- a/mqtt_streaming_protocol/include/MqttDataWrapper.h +++ b/mqtt_streaming_protocol/include/MqttDataWrapper.h @@ -78,16 +78,26 @@ class MqttDataWrapper final std::vector> extractDataSamples( const std::string& topic, const MqttMsgDescriptor& msgDescriptor, const std::string& json); void sendDataSamples(const SignalId& signalId, const DataPackets& dataPackets); - DataPackets buildDataPackets(const SignalId& signalId, double value, uint64_t timestamp); - DataPackets buildDataPackets(const SignalId& signalId, double value); + template + DataPackets buildDataPackets(const SignalId& signalId, T value, uint64_t timestamp); + template + DataPackets buildDataPackets(const SignalId& signalId, T value); daq::DataPacketPtr buildDomainDataPacket(daq::GenericSignalConfigPtr<> signalConfig, uint64_t timestamp); + template daq::DataPacketPtr buildDataPacket(daq::GenericSignalConfigPtr<> signalConfig, - double value, + T value, const daq::DataPacketPtr domainPacket); + template + daq::DataPacketPtr createEmptyDataPacket(const daq::GenericSignalConfigPtr<> signalConfig, + const daq::DataPacketPtr domainPacket, T value); + template void copyDataIntoPacket(daq::DataPacketPtr dataPacket, T value); daq::UnitPtr extractSignalUnit(const rapidjson::Value& signalObj); std::string extractValueFieldName(const rapidjson::Value& signalObj); std::string extractTimestampFieldName(const rapidjson::Value& signalObj); std::string extractFieldName(const rapidjson::Value& signalObj, const std::string& field); + + template + static bool isTypeTheSame(daq::SampleType sampleType); }; } // namespace mqtt diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 3ae78b94..4ce1ad93 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -3,14 +3,16 @@ #include "rapidjson/writer.h" #include #include +#include +#include #include #include +#include #include #include -#include - #include +#include namespace mqtt { @@ -261,8 +263,9 @@ bool MqttDataWrapper::hasDomainSignal(const SignalId& signalId) const std::vector> MqttDataWrapper::extractDataSamples( const std::string& topic, const MqttMsgDescriptor& msgDescriptor, const std::string& json) { + using ValueVariant = std::variant; + ValueVariant value{}; std::vector> res; - double value = 0.0; uint64_t ts = 0; bool hasTS = false; bool hasValue = false; @@ -283,15 +286,20 @@ std::vector> MqttDataWrapper::extractDataSample const std::string name = it->name.GetString(); if (!msgDescriptor.valueFieldName.empty() && name == msgDescriptor.valueFieldName) { - if (jsonDocument[name].IsDouble() || jsonDocument[name].IsInt() || - jsonDocument[name].IsFloat()) - { - value = jsonDocument[name].GetDouble(); - hasValue = true; - } + const auto& v = jsonDocument[name]; + hasValue = true; + if (v.IsInt64()) + value = v.GetInt64(); + else if (v.IsUint64()) + value = static_cast(v.GetUint64()); + else if (v.IsDouble()) + value = v.GetDouble(); + else if (v.IsString()) + value = std::string(v.GetString()); else { - LOG_W("Value is not supported."); + hasValue = false; + LOG_W("Unsupported value type for '{}'.", name); } } else if (!msgDescriptor.tsFieldName.empty() && name == msgDescriptor.tsFieldName) @@ -337,11 +345,18 @@ std::vector> MqttDataWrapper::extractDataSample // TODO : value [1, 2, 3, ...] support SignalId signalId{topic, msgDescriptor.signalName}; DataPackets dataPackets; - if (hasTS) - dataPackets = buildDataPackets(signalId, value, ts); - else - dataPackets = buildDataPackets(signalId, value); - res.emplace_back(std::move(signalId), std::move(dataPackets)); + std::visit( + [&](auto&& val) + { + using T = std::decay_t; + if (hasTS) + dataPackets = buildDataPackets(signalId, val, ts); + else + dataPackets = buildDataPackets(signalId, val); + }, + value); + if (dataPackets.dataPacket.assigned()) + res.emplace_back(std::move(signalId), std::move(dataPackets)); } return res; } @@ -362,9 +377,9 @@ void MqttDataWrapper::sendDataSamples(const SignalId& signalId, const DataPacket signal.getDomainSignal().asPtr().sendPacket( dataPackets.domainDataPacket); } - +template DataPackets -MqttDataWrapper::buildDataPackets(const SignalId& signalId, double value, uint64_t timestamp) +MqttDataWrapper::buildDataPackets(const SignalId& signalId, T value, uint64_t timestamp) { DataPackets dataPackets; const auto signalIter = outputSignals->find(signalId); @@ -381,8 +396,9 @@ MqttDataWrapper::buildDataPackets(const SignalId& signalId, double value, uint64 return dataPackets; } +template DataPackets -MqttDataWrapper::buildDataPackets(const SignalId& signalId, double value) +MqttDataWrapper::buildDataPackets(const SignalId& signalId, T value) { DataPackets dataPackets; const auto signalIter = outputSignals->find(signalId); @@ -397,22 +413,112 @@ MqttDataWrapper::buildDataPackets(const SignalId& signalId, double value) return dataPackets; } -daq::DataPacketPtr MqttDataWrapper::buildDataPacket(daq::GenericSignalConfigPtr<> signalConfig, double value, const daq::DataPacketPtr domainPacket) +template +bool MqttDataWrapper::isTypeTheSame(daq::SampleType sampleType) +{ + using daq::SampleTypeToType; + using daq::SampleTypeFromType; + using daq::SampleType; + switch (sampleType) + { + case daq::SampleType::Float32: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::Float64: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::UInt8: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::Int8: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::Int16: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::UInt16: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::Int32: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::UInt32: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::Int64: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::UInt64: + return std::is_same_v::Type, TReadType>; + case daq::SampleType::Binary: + case daq::SampleType::String: + return std::is_same_v::Type, typename SampleTypeFromType::Type>; + case daq::SampleType::RangeInt64: + case daq::SampleType::ComplexFloat32: + case daq::SampleType::ComplexFloat64: + case daq::SampleType::Struct: + case daq::SampleType::Invalid: + case daq::SampleType::Null: + case daq::SampleType::_count: + break; + } + + return false; +} + +template +daq::DataPacketPtr MqttDataWrapper::buildDataPacket(daq::GenericSignalConfigPtr<> signalConfig, T value, const daq::DataPacketPtr domainPacket) +{ + const auto curType = signalConfig.getDescriptor().getSampleType(); + if (isTypeTheSame(curType) == false) + { + if constexpr (std::is_same_v) + { + // because daq::SampleType::String is not implemented properly, we use Binary type for string data + // daq::SampleType::BinaryData != daq::SampleType::String + auto descriptor = DataDescriptorBuilderCopy(signalConfig.getDescriptor()).setSampleType(daq::SampleType::Binary).build(); + signalConfig.setDescriptor(descriptor); + } + else + { + auto descriptor = DataDescriptorBuilderCopy(signalConfig.getDescriptor()).setSampleType(daq::SampleTypeFromType::SampleType).build(); + signalConfig.setDescriptor(descriptor); + } + } + daq::DataPacketPtr dataPacket = createEmptyDataPacket(signalConfig, domainPacket, value); + copyDataIntoPacket(dataPacket, value); + return dataPacket; +} + +template +daq::DataPacketPtr MqttDataWrapper::createEmptyDataPacket(const daq::GenericSignalConfigPtr<> signalConfig, const daq::DataPacketPtr domainPacket, T value) { daq::DataPacketPtr dataPacket; - if (signalConfig.getDomainSignal().assigned() && domainPacket.assigned()) + if constexpr (std::is_same_v) { - dataPacket = DataPacketWithDomain(domainPacket, signalConfig.getDescriptor(), 1); + dataPacket = daq::BinaryDataPacket(domainPacket, signalConfig.getDescriptor(), value.size()); } else { - dataPacket = DataPacket(signalConfig.getDescriptor(), 1); + if (signalConfig.getDomainSignal().assigned() && domainPacket.assigned()) + { + dataPacket = DataPacketWithDomain(domainPacket, signalConfig.getDescriptor(), 1); + } + else + { + dataPacket = DataPacket(signalConfig.getDescriptor(), 1); + } } - auto outputData = reinterpret_cast(dataPacket.getRawData()); - *outputData = value; return dataPacket; } +template +void MqttDataWrapper::copyDataIntoPacket(daq::DataPacketPtr dataPacket, T value) +{ + if (!dataPacket.assigned()) + return; + if constexpr (std::is_same_v) + { + memcpy(dataPacket.getData(), value.c_str(), value.size()); + } + else + { + auto outputData = reinterpret_cast(dataPacket.getRawData()); + *outputData = value; + } +} + daq::DataPacketPtr MqttDataWrapper::buildDomainDataPacket(daq::GenericSignalConfigPtr<> signalConfig, uint64_t timestamp) { daq::DataPacketPtr dataPacket; From a975fcd4ec8e49f9cc7705fc7a5e8afbbe41bd70 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 4 Nov 2025 16:04:40 +0100 Subject: [PATCH 40/55] mqtt: jsonFb - default value sample type SampleType::Float64 -> SampleType::Undefined; changes in tests --- .../tests/test_data.h | 30 ++--- .../tests/test_mqtt_json_fb.cpp | 114 ++++++++++++------ .../src/MqttDataWrapper.cpp | 2 +- 3 files changed, 91 insertions(+), 55 deletions(-) diff --git a/mqtt_streaming_client_module/tests/test_data.h b/mqtt_streaming_client_module/tests/test_data.h index c1ae83af..b4846d97 100644 --- a/mqtt_streaming_client_module/tests/test_data.h +++ b/mqtt_streaming_client_module/tests/test_data.h @@ -376,21 +376,21 @@ inline const std::vector> DATA_DOUBLE_INT_2 = {{223. {380.2, 1761567118000000}, {570.1, 1761567119000000}}; -inline const std::vector> DATA_INT_INT_0 = {{2307, 1761567115}, - {4500, 1761567116}, - {198, 1761567117}, - {380, 1761567118}, - {500, 1761567119}}; -inline const std::vector> DATA_INT_INT_1 = {{3, 1761567115000}, - {55358, 1761567116000}, - {9525, 1761567117000}, - {-454, 1761567118000}, - {454490, 1761567119000}}; -inline const std::vector> DATA_INT_INT_2 = {{-7223, 1761567115000000}, - {-3475, 1761567116000000}, - {5719, 1761567117000000}, - {380, 1761567118000000}, - {5, 1761567119000000}}; +inline const std::vector> DATA_INT_INT_0 = {{2307, 1761567115}, + {4500, 1761567116}, + {198, 1761567117}, + {380, 1761567118}, + {500, 1761567119}}; +inline const std::vector> DATA_INT_INT_1 = {{3, 1761567115000}, + {55358, 1761567116000}, + {9525, 1761567117000}, + {-454, 1761567118000}, + {454490, 1761567119000}}; +inline const std::vector> DATA_INT_INT_2 = {{-7223, 1761567115000000}, + {-3475, 1761567116000000}, + {5719, 1761567117000000}, + {380, 1761567118000000}, + {5, 1761567119000000}}; inline const std::vector> DATA_DOUBLE_STR_0 = {{23070.008, "2025-10-27T12:45:15Z"}, {4500.4883, " 2025-10-27T12:45:16Z"}, {198.0000052, "2025-10-27T12:45:17Z "}, diff --git a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp index 9258c4f3..526e27e0 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,13 @@ using namespace daq; using namespace daq::modules::mqtt_streaming_client_module; +template +struct is_pair : std::false_type {}; + +template +struct is_pair> : std::true_type {}; + + bool almostEqual(double a, double b, double relEpsilon = 1e-9, double absEpsilon = 1e-12) { return std::fabs(a - b) <= std::max(absEpsilon, relEpsilon * std::max(std::fabs(a), std::fabs(b))); @@ -104,22 +112,22 @@ class MqttJsonFbHelper } template - std::vector> + std::vector> transferData(const std::vector>& data, const std::string& jsonConfigTemplate, const std::string& jsonDataTemplate) { - return transferData>(data, jsonConfigTemplate, jsonDataTemplate); + return transferData>(data, jsonConfigTemplate, jsonDataTemplate); } template - std::vector transferDataWithoutDomain(const std::vector>& data, + std::vector transferDataWithoutDomain(const std::vector>& data, const std::string& jsonConfigTemplate, const std::string& jsonDataTemplate) { - return transferData(data, jsonConfigTemplate, jsonDataTemplate); + return transferData(data, jsonConfigTemplate, jsonDataTemplate); } template - bool compareData(const std::vector>& data0, const std::vector>& data1, bool compareTs = true) + bool compareData(const std::vector>& data0, const std::vector>& data1, bool compareTs = true) { if (data0.size() != data1.size()) return false; @@ -127,8 +135,16 @@ class MqttJsonFbHelper { const auto& [value0, ts0] = data0[i]; const auto& [value1, ts1] = data1[i]; - if (!almostEqual(static_cast(value0), static_cast(value1))) - return false; + if constexpr (std::is_same_v) + { + if (!almostEqual(static_cast(value0), static_cast(value1))) + return false; + } + else + { + if (value0 != value1) + return false; + } if (compareTs) { if constexpr (std::is_same_v) @@ -151,7 +167,7 @@ class MqttJsonFbHelper } template - bool compareData(const std::vector>& data0, const std::vector& data1) + bool compareData(const std::vector>& data0, const std::vector& data1) { if (data0.size() != data1.size()) return false; @@ -159,23 +175,58 @@ class MqttJsonFbHelper { const auto& [value0, ts0] = data0[i]; const auto& value1 = data1[i]; - if (!almostEqual(static_cast(value0), static_cast(value1))) - return false; + if constexpr (std::is_same_v) + { + if (!almostEqual(static_cast(value0), static_cast(value1))) + return false; + } + else + { + if (value0 != value1) + return false; + } } return true; } - std::vector> read(const StreamReaderPtr& reader, bool withDomain = true) + template + std::vector read(StreamReaderPtr& reader, bool withDomain = true) { - std::vector> result; + std::vector result; while (!reader.getEmpty()) { daq::SizeT cnt = 1; - std::pair dataToReceiveEntry; - if (withDomain) - reader.readWithDomain(&dataToReceiveEntry.first, &dataToReceiveEntry.second, &cnt); + T dataToReceiveEntry; + daq::ReaderStatusPtr status; + + if constexpr (is_pair::value) + { + const auto dataType = reader.getValueReadType(); + if (dataType != SampleType::Invalid && getSampleSize(dataType) != sizeof(dataToReceiveEntry.first)) + break; + if (withDomain) + status = reader.readWithDomain(&dataToReceiveEntry.first, &dataToReceiveEntry.second, &cnt); + else + status = reader.read(&dataToReceiveEntry.first, &cnt); + } else - reader.read(&dataToReceiveEntry.first, &cnt); + { + const auto dataType = reader.getValueReadType(); + if (dataType != SampleType::Invalid && getSampleSize(dataType) != sizeof(dataToReceiveEntry)) + break; + status = reader.read(&dataToReceiveEntry, &cnt); + } + + if (status.assigned() && status.getReadStatus() == ReadStatus::Event) + { + const auto [valueDescriptorChanged, domainDescriptorChanged, valueSignalDescriptor, domainSignalDescriptor] = + parseDataDescriptorEventPacket(status.getEventPacket()); + if (valueDescriptorChanged) + reader = StreamReaderFromExisting(reader, valueSignalDescriptor.getSampleType(), SampleType::UInt64); + continue; + } + if (status.assigned() && status.getReadStatus() == ReadStatus::Fail) + break; if (cnt == 1) result.push_back(dataToReceiveEntry); } @@ -190,7 +241,7 @@ class MqttJsonFbHelper CreateJsonFB(jsonConfig); auto signalList = getSignals(); - auto reader = daq::StreamReader(signalList[0]); + auto reader = daq::StreamReader(signalList[0], SampleType::Undefined, SampleType::UInt64); auto msgs = replacePlaceholders(data, jsonDataTemplate); for (const auto& str : msgs) @@ -198,22 +249,7 @@ class MqttJsonFbHelper onSignalsMessage({topic, std::vector(str.begin(), str.end()), 1, 0}); } - std::vector dataToReceive; - while (!reader.getEmpty()) - { - daq::SizeT cnt = 1; - returnT dataToReceiveEntry; - if constexpr (std::is_same_v) - { - reader.read(&dataToReceiveEntry, &cnt); - } - else if constexpr (std::is_same_v>) - { - reader.readWithDomain(&dataToReceiveEntry.first, &dataToReceiveEntry.second, &cnt); - } - if (cnt == 1) - dataToReceive.push_back(dataToReceiveEntry); - } + std::vector dataToReceive = read(reader); return dataToReceive; } }; @@ -269,7 +305,7 @@ class MqttJsonFbCommunicationTest : public testing::Test, public DaqTestHelper, if (result.publishingProblem) return result; } - result.dataReceived = read(reader, true); + result.dataReceived = read>(reader, true); return result; }; }; @@ -287,7 +323,7 @@ class MqttJsonFbDoubleDataPTest : public ::testing::TestWithParam>>, +class MqttJsonFbIntDataPTest : public ::testing::TestWithParam>>, public DaqTestHelper, public MqttJsonFbHelper { @@ -543,7 +579,7 @@ TEST_F(MqttJsonFbTest, DataTransferSeveralSignals) { if (signal.getName().toStdString() == name) { - readers.emplace_back(std::pair(daq::StreamReader(signal), signal)); + readers.emplace_back(std::pair(daq::StreamReader(signal, SampleType::Undefined, SampleType::UInt64), signal)); break; } } @@ -564,7 +600,7 @@ TEST_F(MqttJsonFbTest, DataTransferSeveralSignals) for (int i = 0; i < readers.size(); ++i) { auto& [reader, signal] = readers[i]; - dataToReceive[i] = read(reader, signal.getDomainSignal().assigned()); + dataToReceive[i] = read>(reader, signal.getDomainSignal().assigned()); } EXPECT_EQ(DATA_DOUBLE_INT_0.size(), dataToReceive[0].size()); EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive[0])); @@ -590,7 +626,7 @@ TEST_F(MqttJsonFbTest, DataTransferMissingFieldOneSignal) str = replacePlaceholder(str, "", DATA_DOUBLE_INT_0[cnt].first); onSignalsMessage({topic, std::vector(str.begin(), str.end()), 1, 0}); } - std::vector> dataToReceive = read(reader, false); + std::vector> dataToReceive = read>(reader, false); ASSERT_EQ(dataToReceive.size(), 0); } @@ -635,7 +671,7 @@ TEST_F(MqttJsonFbTest, DataTransferMissingFieldSeveralSignals) for (int i = 0; i < readers.size(); ++i) { auto& [reader, signal] = readers[i]; - dataToReceive[i] = read(reader, signal.getDomainSignal().assigned()); + dataToReceive[i] = read>(reader, signal.getDomainSignal().assigned()); } EXPECT_EQ(DATA_DOUBLE_INT_0.size(), dataToReceive[0].size()); EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive[0])); diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 4ce1ad93..d932dfb5 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -209,7 +209,7 @@ std::unordered_map MqttDataWrapper::extr std::move(tsFieldName)}); auto dataDescBdr = - daq::DataDescriptorBuilder().setSampleType(daq::SampleType::Float64); + daq::DataDescriptorBuilder().setSampleType(daq::SampleType::Undefined); if (unit.assigned()) dataDescBdr.setUnit(unit); From ca07804076ac7ebfafb463353b5a96636269c4ad Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 4 Nov 2025 16:07:09 +0100 Subject: [PATCH 41/55] mqtt: jsonFb signal list was aligned with json config --- .../mqtt_json_receiver_fb_impl.h | 1 + .../src/mqtt_json_receiver_fb_impl.cpp | 19 +++++++++++++++---- .../tests/test_mqtt_json_fb.cpp | 16 +++------------- .../include/MqttDataWrapper.h | 2 +- .../src/MqttDataWrapper.cpp | 6 +++--- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h index dd4af679..5effd865 100644 --- a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h @@ -40,6 +40,7 @@ class MqttJsonReceiverFbImpl final : public MqttBaseFb mutable std::mutex sync; mqtt::MqttDataWrapper jsonDataWorker; std::unordered_map outputSignals; + std::vector signalIdList; std::unordered_map subscribedSignals; void createSignals() override; diff --git a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp index 00b6ae0e..e773ba35 100644 --- a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp @@ -35,6 +35,7 @@ void MqttJsonReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); subscribedSignals.clear(); + signalIdList.clear(); bool isPresent = false; if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) { @@ -43,10 +44,12 @@ void MqttJsonReceiverFbImpl::readProperties() { isPresent = true; jsonDataWorker.setConfig(signalConfig.toStdString()); - subscribedSignals = jsonDataWorker.extractDescription(); + auto listSubscribedSignals = jsonDataWorker.extractDescription(); LOG_I("Signal in the list (topic | signal name):"); - for (const auto& [signalId, descriptor] : subscribedSignals) + for (const auto& [signalId, descriptor] : listSubscribedSignals) { + subscribedSignals.emplace(signalId, descriptor); + signalIdList.push_back(signalId); LOG_I("\t\"{}\" | \"{}\"", signalId.topic, signalId.signalName); } } @@ -80,12 +83,18 @@ void MqttJsonReceiverFbImpl::createSignals() if (!subscribedSignals.empty()) LOG_I("Creating signals..."); - for (const auto& [signalId, descriptor] : subscribedSignals) + for (const auto& signalId : signalIdList) { + auto iter = subscribedSignals.find(signalId); + if (iter == subscribedSignals.end()) + { + LOG_W("\tSignal \"{}\" on topic \"{}\" is not in the subscribed signal list!", signalId.signalName, signalId.topic); + continue; + } LOG_D("\tfor the topic \"{}\"", signalId.signalName, signalId.topic); const std::string& topic = signalId.topic; - auto signalDsc = descriptor; + auto signalDsc = iter->second; auto refS = outputSignals @@ -133,7 +142,9 @@ std::vector MqttJsonReceiverFbImpl::getSubscribedTopics() const void MqttJsonReceiverFbImpl::clearSubscribedTopics() { + auto lock = std::lock_guard(sync); subscribedSignals.clear(); + signalIdList.clear(); } END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp index 526e27e0..590d528b 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp @@ -568,22 +568,12 @@ TEST_F(MqttJsonFbTest, DataTransferSeveralSignals) ASSERT_EQ(signalList.getCount(), 3u); std::vector> readers; - const std::vector originalNames{"temperature", "humi", "pressure"}; - std::vector names; - for (const auto& name : originalNames) - names.emplace_back(buildSignalNameFromTopic(topic, name)); - for (const auto& name : names) + for (const auto& signal : signalList) { - for (const auto& signal : signalList) - { - if (signal.getName().toStdString() == name) - { - readers.emplace_back(std::pair(daq::StreamReader(signal, SampleType::Undefined, SampleType::UInt64), signal)); - break; - } - } + readers.emplace_back(std::pair(daq::StreamReader(signal, SampleType::Undefined, SampleType::UInt64), signal)); } + ASSERT_EQ(readers.size(), 3u); for (int cnt = 0; cnt < DATA_DOUBLE_INT_0.size(); ++cnt) diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/mqtt_streaming_protocol/include/MqttDataWrapper.h index 943c03ea..23c12ed4 100644 --- a/mqtt_streaming_protocol/include/MqttDataWrapper.h +++ b/mqtt_streaming_protocol/include/MqttDataWrapper.h @@ -60,7 +60,7 @@ class MqttDataWrapper final static bool validateTopic(const daq::StringPtr topic, const daq::LoggerComponentPtr loggerComponent = nullptr); void setConfig(const std::string& config); - std::unordered_map extractDescription(); + std::vector> extractDescription(); void setOutputSignals(std::unordered_map* const outputSignals); void createAndSendDataPacket(const std::string& topic, const std::string& json); bool hasDomainSignal(const SignalId& signalId) const; diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index d932dfb5..17bfcb27 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -154,9 +154,9 @@ void MqttDataWrapper::setConfig(const std::string& config) this->config = config; } -std::unordered_map MqttDataWrapper::extractDescription() +std::vector> MqttDataWrapper::extractDescription() { - std::unordered_map result; + std::vector> result; rapidjson::Document doc; topicDescriptors.clear(); @@ -213,7 +213,7 @@ std::unordered_map MqttDataWrapper::extr if (unit.assigned()) dataDescBdr.setUnit(unit); - result.emplace(std::pair(std::move(SignalId), dataDescBdr.build())); + result.emplace_back(std::pair(std::move(SignalId), dataDescBdr.build())); } } topicDescriptors.emplace(std::pair(topic, std::move(msgDescriptors))); From 7f9d16629593dafc8419ee0e9041721392afe2a6 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 4 Nov 2025 18:29:29 +0100 Subject: [PATCH 42/55] mqtt: tests for a string value in a JSON message --- .../tests/test_data.h | 10 + .../tests/test_mqtt_json_fb.cpp | 180 ++++++++++++------ 2 files changed, 131 insertions(+), 59 deletions(-) diff --git a/mqtt_streaming_client_module/tests/test_data.h b/mqtt_streaming_client_module/tests/test_data.h index b4846d97..4b2e947a 100644 --- a/mqtt_streaming_client_module/tests/test_data.h +++ b/mqtt_streaming_client_module/tests/test_data.h @@ -391,6 +391,16 @@ inline const std::vector> DATA_INT_INT_2 = {{-7223, {5719, 1761567117000000}, {380, 1761567118000000}, {5, 1761567119000000}}; +inline const std::vector> DATA_STR_INT_0 = {{"very cold", 1761567115}, + {"cold", 1761567116}, + {"normal", 1761567117}, + {"hot", 1761567118}, + {"very hot", 1761567119}}; +inline const std::vector> DATA_STR_INT_1 = {{u8"очень холодно", 1761567115000}, + {u8"холодно", 1761567116000}, + {u8"нормально", 1761567117000}, + {u8"жарко", 1761567118000}, + {u8"очень жарко", 1761567119000}}; inline const std::vector> DATA_DOUBLE_STR_0 = {{23070.008, "2025-10-27T12:45:15Z"}, {4500.4883, " 2025-10-27T12:45:16Z"}, {198.0000052, "2025-10-27T12:45:17Z "}, diff --git a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp index 590d528b..08e5f354 100644 --- a/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp +++ b/mqtt_streaming_client_module/tests/test_mqtt_json_fb.cpp @@ -1,5 +1,5 @@ #include "MqttAsyncClientWrapper.h" -#include "mqtt_streaming_client_module/helper.h" +#include "Timer.h" #include "mqtt_streaming_client_module/mqtt_json_receiver_fb_impl.h" #include "test_daq_test_helper.h" #include "test_data.h" @@ -23,7 +23,6 @@ struct is_pair : std::false_type {}; template struct is_pair> : std::true_type {}; - bool almostEqual(double a, double b, double relEpsilon = 1e-9, double absEpsilon = 1e-12) { return std::fabs(a - b) <= std::max(absEpsilon, relEpsilon * std::max(std::fabs(a), std::fabs(b))); @@ -190,46 +189,97 @@ class MqttJsonFbHelper } template - std::vector read(StreamReaderPtr& reader, bool withDomain = true) + bool copyData(T& destination, const DataPacketPtr source) { - std::vector result; - while (!reader.getEmpty()) + auto checkType = [](SampleType type) -> bool + { + switch (type) + { + case SampleType::Float32: + case SampleType::Float64: + case SampleType::UInt8: + case SampleType::Int8: + case SampleType::UInt16: + case SampleType::Int16: + case SampleType::UInt32: + case SampleType::Int32: + case SampleType::UInt64: + case SampleType::Int64: + case SampleType::RangeInt64: + case SampleType::ComplexFloat32: + case SampleType::ComplexFloat64: + return true; + case SampleType::String: + case SampleType::Binary: + case SampleType::Struct: + case SampleType::Invalid: + case SampleType::Null: + case SampleType::_count: + return false; + } + return true; + }; + + const auto dataType = source.getDataDescriptor().getSampleType(); + if (checkType(dataType) && getSampleSize(dataType) != sizeof(destination)) + return false; + if constexpr (std::is_same_v) { - daq::SizeT cnt = 1; - T dataToReceiveEntry; - daq::ReaderStatusPtr status; + destination = std::string(static_cast(source.getData()), source.getDataSize()); + } + else + { + memcpy(&destination, source.getData(), sizeof(destination)); + } + return true; + } - if constexpr (is_pair::value) + template + std::vector read(PacketReaderPtr reader, const SignalPtr signal, int timeoutMs = 1000) + { + std::vector result; + + auto timer = Timer(timeoutMs); + while (!reader.getEmpty() || !timer.expired()) + { + if (reader.getEmpty()) { - const auto dataType = reader.getValueReadType(); - if (dataType != SampleType::Invalid && getSampleSize(dataType) != sizeof(dataToReceiveEntry.first)) - break; - if (withDomain) - status = reader.readWithDomain(&dataToReceiveEntry.first, &dataToReceiveEntry.second, &cnt); - else - status = reader.read(&dataToReceiveEntry.first, &cnt); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + continue; } - else + auto packet = reader.read(); + if (packet.getType() == PacketType::Event) { - const auto dataType = reader.getValueReadType(); - if (dataType != SampleType::Invalid && getSampleSize(dataType) != sizeof(dataToReceiveEntry)) - break; - status = reader.read(&dataToReceiveEntry, &cnt); + continue; } - if (status.assigned() && status.getReadStatus() == ReadStatus::Event) + if (packet.getType() == PacketType::Data) { - const auto [valueDescriptorChanged, domainDescriptorChanged, valueSignalDescriptor, domainSignalDescriptor] = - parseDataDescriptorEventPacket(status.getEventPacket()); - if (valueDescriptorChanged) - reader = StreamReaderFromExisting(reader, valueSignalDescriptor.getSampleType(), SampleType::UInt64); - continue; + const auto dataPacket = packet.asPtr(); + const auto dataType = dataPacket.getDataDescriptor().getSampleType(); + if constexpr (is_pair::value) + { + T dataToReceiveEntry; + bool ok = true; + ok &= copyData(dataToReceiveEntry.first, dataPacket); + if (signal.getDomainSignal().assigned()) + ok &= copyData(dataToReceiveEntry.second, dataPacket.getDomainPacket()); + if (!ok) + break; + result.push_back(dataToReceiveEntry); + + } + else + { + T dataToReceiveEntry; + bool ok = copyData(dataToReceiveEntry, dataPacket); + if (!ok) + break; + result.push_back(dataToReceiveEntry); + } } - if (status.assigned() && status.getReadStatus() == ReadStatus::Fail) - break; - if (cnt == 1) - result.push_back(dataToReceiveEntry); } + return result; } @@ -240,8 +290,8 @@ class MqttJsonFbHelper const auto jsonConfig = replacePlaceholder(jsonConfigTemplate, "", topic); CreateJsonFB(jsonConfig); - auto signalList = getSignals(); - auto reader = daq::StreamReader(signalList[0], SampleType::Undefined, SampleType::UInt64); + auto signal = getSignals()[0]; + auto reader = daq::PacketReader(signal); auto msgs = replacePlaceholders(data, jsonDataTemplate); for (const auto& str : msgs) @@ -249,7 +299,7 @@ class MqttJsonFbHelper onSignalsMessage({topic, std::vector(str.begin(), str.end()), 1, 0}); } - std::vector dataToReceive = read(reader); + std::vector dataToReceive = read(reader, signal, 0); return dataToReceive; } }; @@ -289,7 +339,7 @@ class MqttJsonFbCommunicationTest : public testing::Test, public DaqTestHelper, auto config = device.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); auto singal = device.addFunctionBlock(JSON_FB_NAME, config).getSignals()[0]; - auto reader = daq::StreamReader(singal); + auto reader = daq::PacketReader(singal); MqttAsyncClientWrapper publisher(std::make_shared(), buildClientId()); result.deviceProblem = !publisher.connect(url); @@ -305,7 +355,7 @@ class MqttJsonFbCommunicationTest : public testing::Test, public DaqTestHelper, if (result.publishingProblem) return result; } - result.dataReceived = read>(reader, true); + result.dataReceived = read>(reader, singal, 2000); return result; }; }; @@ -328,6 +378,11 @@ class MqttJsonFbIntDataPTest : public ::testing::TestWithParam>>, + public DaqTestHelper, + public MqttJsonFbHelper +{ +}; class MqttJsonFbStringTsPTest : public ::testing::TestWithParam>>, public DaqTestHelper, public MqttJsonFbHelper @@ -546,6 +601,26 @@ INSTANTIATE_TEST_SUITE_P(DataTransferOneSignalInt, MqttJsonFbIntDataPTest, ::testing::Values(DATA_INT_INT_0, DATA_INT_INT_1, DATA_INT_INT_2)); +TEST_P(MqttJsonFbStringDataPTest, DataTransferOneSignalString) +{ + const auto dataToSend = GetParam(); + auto dataToReceive = transferData(dataToSend, VALID_JSON_CONFIG_0, VALID_JSON_DATA_0); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); +} + +TEST_P(MqttJsonFbStringDataPTest, DataTransferOneSignalStringWithoutDomain) +{ + const auto dataToSend = GetParam(); + auto dataToReceive = transferDataWithoutDomain(dataToSend, VALID_JSON_CONFIG_1, VALID_JSON_DATA_1); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); +} + +INSTANTIATE_TEST_SUITE_P(DataTransferOneSignalString, + MqttJsonFbStringDataPTest, + ::testing::Values(DATA_STR_INT_0, DATA_STR_INT_1)); + TEST_P(MqttJsonFbStringTsPTest, DataTransferOneSignalIntDomainString) { const auto dataToSend = GetParam(); @@ -567,15 +642,13 @@ TEST_F(MqttJsonFbTest, DataTransferSeveralSignals) auto signalList = getSignals(); ASSERT_EQ(signalList.getCount(), 3u); - std::vector> readers; + std::vector> readers; for (const auto& signal : signalList) { - readers.emplace_back(std::pair(daq::StreamReader(signal, SampleType::Undefined, SampleType::UInt64), signal)); + readers.emplace_back(std::pair(daq::PacketReader(signal), signal)); } - ASSERT_EQ(readers.size(), 3u); - for (int cnt = 0; cnt < DATA_DOUBLE_INT_0.size(); ++cnt) { auto str = VALID_JSON_DATA_2; @@ -590,7 +663,7 @@ TEST_F(MqttJsonFbTest, DataTransferSeveralSignals) for (int i = 0; i < readers.size(); ++i) { auto& [reader, signal] = readers[i]; - dataToReceive[i] = read>(reader, signal.getDomainSignal().assigned()); + dataToReceive[i] = read>(reader, signal, 0); } EXPECT_EQ(DATA_DOUBLE_INT_0.size(), dataToReceive[0].size()); EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive[0])); @@ -608,7 +681,8 @@ TEST_F(MqttJsonFbTest, DataTransferMissingFieldOneSignal) const auto jsonConfig = replacePlaceholder(VALID_JSON_CONFIG_0, "", topic); CreateJsonFB(jsonConfig); - auto reader = daq::StreamReader(getSignals()[0]); + auto signal = getSignals()[0]; + auto reader = daq::PacketReader(signal); for (int cnt = 0; cnt < DATA_DOUBLE_INT_0.size(); ++cnt) { @@ -616,7 +690,7 @@ TEST_F(MqttJsonFbTest, DataTransferMissingFieldOneSignal) str = replacePlaceholder(str, "", DATA_DOUBLE_INT_0[cnt].first); onSignalsMessage({topic, std::vector(str.begin(), str.end()), 1, 0}); } - std::vector> dataToReceive = read>(reader, false); + std::vector> dataToReceive = read>(reader, signal, 0); ASSERT_EQ(dataToReceive.size(), 0); } @@ -629,24 +703,12 @@ TEST_F(MqttJsonFbTest, DataTransferMissingFieldSeveralSignals) auto signalList = getSignals(); ASSERT_EQ(signalList.getCount(), 3u); - std::vector> readers; - const std::vector originalNames{"temperature", "humi", "pressure"}; - std::vector names; - for (const auto& name : originalNames) - names.emplace_back(buildSignalNameFromTopic(topic, name)); + std::vector> readers; - for (const auto& name : names) + for (const auto& signal : signalList) { - for (const auto& signal : signalList) - { - if (signal.getName().toStdString() == name) - { - readers.emplace_back(std::pair(daq::StreamReader(signal), signal)); - break; - } - } + readers.emplace_back(std::pair(daq::PacketReader(signal), signal)); } - ASSERT_EQ(readers.size(), 3u); for (int cnt = 0; cnt < DATA_DOUBLE_INT_0.size(); ++cnt) { @@ -661,7 +723,7 @@ TEST_F(MqttJsonFbTest, DataTransferMissingFieldSeveralSignals) for (int i = 0; i < readers.size(); ++i) { auto& [reader, signal] = readers[i]; - dataToReceive[i] = read>(reader, signal.getDomainSignal().assigned()); + dataToReceive[i] = read>(reader, signal, 0); } EXPECT_EQ(DATA_DOUBLE_INT_0.size(), dataToReceive[0].size()); EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive[0])); From e41aa3ae158c2e8c48b9dd1ca2fd6e1c53b5b030 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 5 Nov 2025 10:51:45 +0100 Subject: [PATCH 43/55] mqtt: naming --- mqtt_streaming_client_module/src/helper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mqtt_streaming_client_module/src/helper.cpp b/mqtt_streaming_client_module/src/helper.cpp index f827c2da..33c04004 100644 --- a/mqtt_streaming_client_module/src/helper.cpp +++ b/mqtt_streaming_client_module/src/helper.cpp @@ -8,14 +8,14 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE std::string buildSignalNameFromTopic(std::string topic, const std::string& signalName) { boost::replace_all(topic, "/", "_"); - topic += "_Mqtt" + signalName; + topic += "_Mqtt_" + signalName; return topic; } std::string buildDomainSignalNameFromTopic(std::string topic, const std::string& signalName) { boost::replace_all(topic, "/", "_"); - topic += std::string("_Mqtt") + "_domain" + signalName; + topic += std::string("_Mqtt") + "_domain_" + signalName; return topic; } From 16f023509555b89f0d3a0bb496d69c8298ffbe96 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 5 Nov 2025 10:52:01 +0100 Subject: [PATCH 44/55] mqtt: tests --- mqtt_streaming_client_module/tests/test_data.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mqtt_streaming_client_module/tests/test_data.h b/mqtt_streaming_client_module/tests/test_data.h index 4b2e947a..3f182edc 100644 --- a/mqtt_streaming_client_module/tests/test_data.h +++ b/mqtt_streaming_client_module/tests/test_data.h @@ -415,4 +415,7 @@ inline const std::vector> DATA_DOUBLE_STR_2 = {{- {-3475.5, "2025-10-27 12:45:15.002 "}, {5719.9, " 2025-10-27 12:45:15.003 "}, {380.1, " 2025-10-27 12:45:15.004 "}, - {5, " 2025-10-27 12:45:15.005"}}; + {5, " 2025-10-27 12:45:15.005"}, + {50, " 2025-10-27T12:45:15.005Z"}, + {50.5, " 2025-10-27 12:45:15.005Z"}, + {51, "2025-10-27T12:45:15.005"}}; From 8bcaba9d579a3165d0c3bf604b266557dd960223 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 5 Nov 2025 11:17:46 +0100 Subject: [PATCH 45/55] mqtt: fixes for the ref-dev-mqtt-sub example --- .../ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp index 191fbb7c..51b5e026 100644 --- a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp +++ b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp @@ -7,6 +7,36 @@ using namespace daq; using namespace daq::modules::mqtt_streaming_client_module; +std::string to_string(daq::DataPacketPtr packet) +{ + std::string result; + std::string data; + switch (packet.getDataDescriptor().getSampleType()) + { + case SampleType::Float64: + data = std::to_string(*(static_cast(packet.getData()))); + break; + case SampleType::UInt64: + data = std::to_string(*(static_cast(packet.getData()))); + break; + case SampleType::Int64: + data = std::to_string(*(static_cast(packet.getData()))); + break; + case SampleType::Binary: + data = '\"' + std::string(static_cast(packet.getData()), packet.getDataSize()) + '\"'; + break; + default: + break; + } + result = fmt::format("Data: {};", data); + if (packet.getDomainPacket().assigned()) + { + uint64_t ts = *(static_cast(packet.getDomainPacket().getData())); + result += fmt::format(" Timestamp : {};", ts); + } + return result; +} + int main(int argc, char* argv[]) { InputArgs args; @@ -40,29 +70,34 @@ int main(int argc, char* argv[]) std::cout << "Try to connect the " << fbList[0].getLocalId() << std::endl; auto signals = fbList[0].getSignals(); - std::vector readers; + std::vector readers; for (const auto& s : signals) { - readers.emplace_back(daq::StreamReader(s, ReadTimeoutType::Any)); + readers.emplace_back(daq::PacketReader(s)); } - std::thread t1([readers]() { - constexpr int size = 1000; - double samples[size]; - uint64_t timestamps[size]; + std::thread readerThread([readers]() { while (true) { for (int iRdr = 0; iRdr < readers.size(); ++iRdr) { const auto& reader = readers[iRdr]; while (!reader.getEmpty()) { - daq::SizeT count = size; - reader.readWithDomain(samples, timestamps, &count); - for (daq::SizeT i = 0; i < count; ++i) - std::cout << "READER #" << iRdr << " - Sample: " << samples[i] << " Timestamp: " << timestamps[i] << std::endl; + auto packet = reader.read(); + if (packet.getType() == PacketType::Event) + { + std::cout << "Event packet is skipped!" << std::endl; + continue; + } + + if (packet.getType() == PacketType::Data) + { + const auto dataPacket = packet.asPtrOrNull(); + std::cout << "READER #" << iRdr << " - " << to_string(dataPacket) << std::endl; + } } } std::this_thread::sleep_for(std::chrono::milliseconds(20)); } }); - t1.detach(); + readerThread.detach(); std::cout << "Press \"enter\" to exit the application..." << std::endl; std::cin.get(); From 92f0e8c4fa46882d39607a58e7e3248607456506 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 5 Nov 2025 11:30:55 +0100 Subject: [PATCH 46/55] mqtt: correct termination of the example applications; formatting --- .../custom-mqtt-sub/src/custom-mqtt-sub.cpp | 117 +++++++++--------- .../ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp | 9 +- .../src/ref-dev-mqtt-raw-sub.cpp | 70 ++++++----- .../ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp | 60 +++++---- 4 files changed, 144 insertions(+), 112 deletions(-) diff --git a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp index f4da4841..64a011a9 100644 --- a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp +++ b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp @@ -14,44 +14,44 @@ std::string to_string(SampleType sampleType) { switch (sampleType) { - case SampleType::Float32: - return "Float32"; - case SampleType::Float64: - return "Float64"; - case SampleType::UInt8: - return "UInt8"; - case SampleType::Int8: - return "Int8"; - case SampleType::UInt16: - return "UInt16"; - case SampleType::Int16: - return "Int16"; - case SampleType::UInt32: - return "UInt32"; - case SampleType::Int32: - return "Int32"; - case SampleType::UInt64: - return "UInt64"; - case SampleType::Int64: - return "Int64"; - case SampleType::RangeInt64: - return "RangeInt64"; - case SampleType::ComplexFloat32: - return "ComplexFloat32"; - case SampleType::ComplexFloat64: - return "ComplexFloat64"; - case SampleType::Struct: - return "Struct"; - case SampleType::Undefined: - return "Undefined"; - case SampleType::Binary: - return "Binary"; - case SampleType::String: - return "String"; - case SampleType::Null: - return "Null"; - case SampleType::_count: - return "Count"; + case SampleType::Float32: + return "Float32"; + case SampleType::Float64: + return "Float64"; + case SampleType::UInt8: + return "UInt8"; + case SampleType::Int8: + return "Int8"; + case SampleType::UInt16: + return "UInt16"; + case SampleType::Int16: + return "Int16"; + case SampleType::UInt32: + return "UInt32"; + case SampleType::Int32: + return "Int32"; + case SampleType::UInt64: + return "UInt64"; + case SampleType::Int64: + return "Int64"; + case SampleType::RangeInt64: + return "RangeInt64"; + case SampleType::ComplexFloat32: + return "ComplexFloat32"; + case SampleType::ComplexFloat64: + return "ComplexFloat64"; + case SampleType::Struct: + return "Struct"; + case SampleType::Undefined: + return "Undefined"; + case SampleType::Binary: + return "Binary"; + case SampleType::String: + return "String"; + case SampleType::Null: + return "Null"; + case SampleType::_count: + return "Count"; } return "Unknown"; } @@ -78,20 +78,20 @@ std::string to_string(daq::DataPacketPtr packet) std::string data; switch (packet.getDataDescriptor().getSampleType()) { - case SampleType::Float64: - data = std::to_string(*(static_cast(packet.getData()))); - break; - case SampleType::UInt64: - data = std::to_string(*(static_cast(packet.getData()))); - break; - case SampleType::Int64: - data = std::to_string(*(static_cast(packet.getData()))); - break; - case SampleType::Binary: - data = '\"' + std::string(static_cast(packet.getData()), packet.getDataSize()) + '\"'; - break; - default: - break; + case SampleType::Float64: + data = std::to_string(*(static_cast(packet.getData()))); + break; + case SampleType::UInt64: + data = std::to_string(*(static_cast(packet.getData()))); + break; + case SampleType::Int64: + data = std::to_string(*(static_cast(packet.getData()))); + break; + case SampleType::Binary: + data = '\"' + std::string(static_cast(packet.getData()), packet.getDataSize()) + '\"'; + break; + default: + break; } result = fmt::format("SampleType : {}; Data: {};", to_string(packet.getDataDescriptor().getSampleType()), data); if (packet.getDomainPacket().assigned()) @@ -174,14 +174,15 @@ int main(int argc, char* argv[]) packetReaders.emplace(std::pair(s.getName().toStdString(), daq::PacketReader(s))); } + std::atomic running = true; std::thread readerThread( - [packetReaders]() + [&packetReaders, &running]() { - while (true) + while (running) { for (const auto& [signal, reader] : packetReaders) { - while (!reader.getEmpty()) + while (!reader.getEmpty() && running) { auto packet = reader.read(); const auto eventPacket = packet.asPtrOrNull(); @@ -200,9 +201,13 @@ int main(int argc, char* argv[]) std::this_thread::sleep_for(std::chrono::milliseconds(20)); } }); - readerThread.detach(); std::cout << "Press \"enter\" to exit the application..." << std::endl; std::cin.get(); + + running = false; + readerThread.join(); + std::cout << "Reader thread finished. Exiting.\n"; + return 0; } diff --git a/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp b/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp index dd6cf021..2cfa1bb5 100644 --- a/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp +++ b/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp @@ -1,5 +1,5 @@ -#include #include "../../InputArgs.h" +#include #include @@ -12,16 +12,15 @@ int main(int argc, char* argv[]) args.addArg("--address", "MQTT broker address", true); args.parse(argc, argv); - if (args.hasArg("--help") || args.hasUnknownArgs()) { + if (args.hasArg("--help") || args.hasUnknownArgs()) + { args.printHelp(); return 0; } std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); - const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH) - .setRootDevice("daqref://device0") - .build(); + const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).setRootDevice("daqref://device0").build(); auto refDevice = instance.getRootDevice(); refDevice.setPropertyValue("NumberOfChannels", 4); diff --git a/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp b/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp index 653921f3..cfafc3cf 100644 --- a/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp +++ b/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp @@ -1,6 +1,6 @@ -#include #include "../../InputArgs.h" #include +#include #include #include @@ -17,7 +17,8 @@ int main(int argc, char* argv[]) args.setUsageHelp(APP_NAME " [options] ... "); args.parse(argc, argv); - if (args.hasArg("--help") || args.hasUnknownArgs()) { + if (args.hasArg("--help") || args.hasUnknownArgs()) + { args.printHelp(); return 0; } @@ -25,7 +26,8 @@ int main(int argc, char* argv[]) std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); auto topics = args.getPositionalArgs(); - if (topics.empty()) { + if (topics.empty()) + { std::cout << "MQTT topics are required." << std::endl; return -1; } @@ -33,13 +35,14 @@ int main(int argc, char* argv[]) auto brokerDevice = instance.addDevice("daq.mqtt://" + brokerAddress); auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); - if (availableDeviceNodes.getCount() == 0) { + if (availableDeviceNodes.getCount() == 0) + { std::cout << "No function block available from the device." << std::endl; return -1; } - - for (const auto& [key, value] : availableDeviceNodes) { + for (const auto& [key, value] : availableDeviceNodes) + { std::cout << "Available function block: " << key << std::endl; } const std::string fbName = RAW_FB_NAME; @@ -47,43 +50,54 @@ int main(int argc, char* argv[]) auto config = availableDeviceNodes.get(fbName).createDefaultConfig(); auto topicList = List(); - for (auto& topic : topics) { + for (auto& topic : topics) + { addToList(topicList, std::move(topic)); } config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); daq::FunctionBlockPtr rawFb = brokerDevice.addFunctionBlock(fbName, config); - auto signals = rawFb.getSignals(); std::map readers; - for (const auto& s : signals) { + for (const auto& s : signals) + { readers.emplace(std::pair(s.getName().toStdString(), daq::PacketReader(s))); } - std::thread readerThread([readers]() { - while (true) { - for (const auto& pair : readers) { - const auto& reader = pair.second; - while (!reader.getEmpty()) { - auto packet = reader.read(); - const auto eventPacket = packet.asPtrOrNull(); - if (eventPacket.assigned()) { - continue; + std::atomic running = true; + std::thread readerThread( + [&readers, &running]() + { + while (running) + { + for (const auto& pair : readers) + { + const auto& reader = pair.second; + while (!reader.getEmpty() && running) + { + auto packet = reader.read(); + const auto eventPacket = packet.asPtrOrNull(); + if (eventPacket.assigned()) + { + continue; + } + const auto dataPacket = packet.asPtrOrNull(); + if (dataPacket.assigned()) + { + std::string tmp(static_cast(dataPacket.getData()), dataPacket.getDataSize()); + std::cout << pair.first << " - " << tmp << std::endl; + } } - const auto dataPacket = packet.asPtrOrNull(); - if (dataPacket.assigned()) { - std::string tmp(static_cast(dataPacket.getData()), dataPacket.getDataSize()); - std::cout << pair.first << " - " << tmp << std::endl; - } - } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); } - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } - }); - readerThread.detach(); + }); std::cout << "Press \"enter\" to exit the application..." << std::endl; std::cin.get(); + + running = false; + readerThread.join(); + std::cout << "Reader thread finished. Exiting.\n"; return 0; } diff --git a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp index 51b5e026..8e4c03cb 100644 --- a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp +++ b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp @@ -1,6 +1,6 @@ -#include #include "../../InputArgs.h" #include +#include #include @@ -44,7 +44,8 @@ int main(int argc, char* argv[]) args.addArg("--address", "MQTT broker address", true); // If you want to support --address for sub args.parse(argc, argv); - if (args.hasArg("--help") || args.hasUnknownArgs()) { + if (args.hasArg("--help") || args.hasUnknownArgs()) + { args.printHelp(); return 0; } @@ -55,13 +56,15 @@ int main(int argc, char* argv[]) auto brokerDevice = instance.addDevice("daq.mqtt://" + brokerAddress); auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); - if (availableDeviceNodes.getCount() == 0) { + if (availableDeviceNodes.getCount() == 0) + { std::cout << "No function block available from the device." << std::endl; return -1; } std::vector fbList; - for (const auto& [key, value] : availableDeviceNodes) { + for (const auto& [key, value] : availableDeviceNodes) + { std::cout << "Available function block: " << key << std::endl; if (key == RAW_FB_NAME || key == JSON_FB_NAME) continue; @@ -71,35 +74,46 @@ int main(int argc, char* argv[]) auto signals = fbList[0].getSignals(); std::vector readers; - for (const auto& s : signals) { + for (const auto& s : signals) + { readers.emplace_back(daq::PacketReader(s)); } - std::thread readerThread([readers]() { - while (true) { - for (int iRdr = 0; iRdr < readers.size(); ++iRdr) { - const auto& reader = readers[iRdr]; - while (!reader.getEmpty()) { - auto packet = reader.read(); - if (packet.getType() == PacketType::Event) + std::atomic running = true; + std::thread readerThread( + [&readers, &running]() + { + while (running) + { + for (int iRdr = 0; iRdr < readers.size(); ++iRdr) + { + const auto& reader = readers[iRdr]; + while (!reader.getEmpty() && running) { - std::cout << "Event packet is skipped!" << std::endl; - continue; - } + auto packet = reader.read(); + if (packet.getType() == PacketType::Event) + { + std::cout << "Event packet is skipped!" << std::endl; + continue; + } - if (packet.getType() == PacketType::Data) - { - const auto dataPacket = packet.asPtrOrNull(); - std::cout << "READER #" << iRdr << " - " << to_string(dataPacket) << std::endl; + if (packet.getType() == PacketType::Data) + { + const auto dataPacket = packet.asPtrOrNull(); + std::cout << "READER #" << iRdr << " - " << to_string(dataPacket) << std::endl; + } } } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); } - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } - }); - readerThread.detach(); + }); std::cout << "Press \"enter\" to exit the application..." << std::endl; std::cin.get(); + + running = false; + readerThread.join(); + std::cout << "Reader thread finished. Exiting.\n"; + return 0; } From b3b0b368e6ce0b6bb843831e0ef9ffd04aab9641 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 5 Nov 2025 13:19:28 +0100 Subject: [PATCH 47/55] mqtt: refactoring of example applications --- .../custom-mqtt-sub/src/custom-mqtt-sub.cpp | 132 +++++++----------- .../ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp | 30 +++- .../src/ref-dev-mqtt-raw-sub.cpp | 70 ++++++---- .../ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp | 56 +++++--- 4 files changed, 162 insertions(+), 126 deletions(-) diff --git a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp index 64a011a9..148171a3 100644 --- a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp +++ b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp @@ -10,51 +10,12 @@ using namespace daq; using namespace daq::modules::mqtt_streaming_client_module; -std::string to_string(SampleType sampleType) -{ - switch (sampleType) - { - case SampleType::Float32: - return "Float32"; - case SampleType::Float64: - return "Float64"; - case SampleType::UInt8: - return "UInt8"; - case SampleType::Int8: - return "Int8"; - case SampleType::UInt16: - return "UInt16"; - case SampleType::Int16: - return "Int16"; - case SampleType::UInt32: - return "UInt32"; - case SampleType::Int32: - return "Int32"; - case SampleType::UInt64: - return "UInt64"; - case SampleType::Int64: - return "Int64"; - case SampleType::RangeInt64: - return "RangeInt64"; - case SampleType::ComplexFloat32: - return "ComplexFloat32"; - case SampleType::ComplexFloat64: - return "ComplexFloat64"; - case SampleType::Struct: - return "Struct"; - case SampleType::Undefined: - return "Undefined"; - case SampleType::Binary: - return "Binary"; - case SampleType::String: - return "String"; - case SampleType::Null: - return "Null"; - case SampleType::_count: - return "Count"; - } - return "Unknown"; -} +struct ConfigStruct { + std::string brokerAddress; + std::string configFilePath; + bool exit = true; + int error = 0; +}; std::string to_string(uint64_t ts) { @@ -93,10 +54,17 @@ std::string to_string(daq::DataPacketPtr packet) default: break; } - result = fmt::format("SampleType : {}; Data: {};", to_string(packet.getDataDescriptor().getSampleType()), data); - if (packet.getDomainPacket().assigned()) + std::string unitStr; + if (auto unit = packet.getDataDescriptor().getUnit(); unit.assigned()) { - uint64_t ts = *(static_cast(packet.getDomainPacket().getData())); + if (auto s = unit.getSymbol(); s.assigned()) + unitStr = " " + s.toStdString(); + } + + result = fmt::format("SampleType : {}; Data: {}{};", convertSampleTypeToString(packet.getDataDescriptor().getSampleType()), data, unitStr); + if (auto domainPacket = packet.getDomainPacket(); domainPacket.assigned()) + { + uint64_t ts = *(static_cast(domainPacket.getData())); result += fmt::format(" Time : {};", to_string(ts)); } return result; @@ -113,67 +81,73 @@ std::string readFileToString(const std::string& filePath) return buffer.str(); } -int main(int argc, char* argv[]) +ConfigStruct StartUp(int argc, char* argv[]) { + ConfigStruct config; InputArgs args; args.addArg("--help", "Show help message"); - args.addArg("--address", "MQTT broker address", true); // If you want to support --address for sub + args.addArg("--address", "MQTT broker address", true); args.setUsageHelp(APP_NAME " [options] "); args.parse(argc, argv); if (args.hasArg("--help") || args.hasUnknownArgs()) { args.printHelp(); - return 0; + config.error = 0; + return config; } - std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); + config.brokerAddress = args.getArgValue("--address", "127.0.0.1"); auto configFilePath = args.getPositionalArgs(); if (configFilePath.size() != 1) { std::cout << "Configuration file path is required." << std::endl; - return -1; + config.error = -1; + return config; } - - const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); - auto brokerDevice = instance.addDevice("daq.mqtt://" + brokerAddress); - auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); - - if (availableDeviceNodes.getCount() == 0) + if (configFilePath.size() > 1) { - std::cout << "No function block available from the device." << std::endl; - return -1; + std::cout << "Only one configuration file path is allowed. The first one will be used - " << configFilePath[0] << std::endl; } + config.configFilePath = std::move(configFilePath[0]); + config.exit = false; + return config; +} - for (const auto& [key, value] : availableDeviceNodes) +int main(int argc, char* argv[]) +{ + // Parse input arguments + auto appConfig = StartUp(argc, argv); + if (appConfig.exit) { - std::cout << "Available function block: " << key << std::endl; + return appConfig.error; } + + // Create OpenDAQ instance and add MQTT broker device + const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); + auto brokerDevice = instance.addDevice("daq.mqtt://" + appConfig.brokerAddress); + auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); + const std::string fbName = JSON_FB_NAME; std::cout << "Try to add the " << fbName << std::endl; + // Read JSON function block configuration from file and fill out the function block config + const std::string jsonConfig = readFileToString(appConfig.configFilePath); auto config = availableDeviceNodes.get(fbName).createDefaultConfig(); - std::string jsonConfig = readFileToString(configFilePath[0]); - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); - daq::FunctionBlockPtr jsonFb = brokerDevice.addFunctionBlock(fbName, config); - auto signals = jsonFb.getSignals(); - - using ReaderContainerEntryType = std::pair, StreamReaderPtr>; - using ReaderContainerType = std::vector; - ReaderContainerType readers; - for (const auto& s : signals) - { - readers.emplace_back(ReaderContainerEntryType(std::pair(s, daq::StreamReader(s, ReadTimeoutType::Any)))); - } + // Add the JSON function block to the broker device + daq::FunctionBlockPtr jsonFb = brokerDevice.addFunctionBlock(fbName, config); + // Create packet readers for all signals + const auto signals = jsonFb.getSignals(); std::map packetReaders; for (const auto& s : signals) { packetReaders.emplace(std::pair(s.getName().toStdString(), daq::PacketReader(s))); } + // Start a thread to read packets from the readers std::atomic running = true; std::thread readerThread( [&packetReaders, &running]() @@ -185,15 +159,13 @@ int main(int argc, char* argv[]) while (!reader.getEmpty() && running) { auto packet = reader.read(); - const auto eventPacket = packet.asPtrOrNull(); - if (eventPacket.assigned()) + if (packet.getType() == PacketType::Event) { std::cout << "Event packet is skipped!" << std::endl; - continue; } - const auto dataPacket = packet.asPtrOrNull(); - if (dataPacket.assigned()) + else if (packet.getType() == PacketType::Data) { + const auto dataPacket = packet.asPtr(); std::cout << signal << " - " << to_string(dataPacket) << std::endl; } } diff --git a/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp b/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp index 2cfa1bb5..40bdbae4 100644 --- a/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp +++ b/examples/ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp @@ -5,8 +5,15 @@ using namespace daq; -int main(int argc, char* argv[]) +struct ConfigStruct { + std::string brokerAddress; + bool exit = true; + int error = 0; +}; + +ConfigStruct StartUp(int argc, char* argv[]) { + ConfigStruct config; InputArgs args; args.addArg("--help", "Show help message"); args.addArg("--address", "MQTT broker address", true); @@ -15,15 +22,29 @@ int main(int argc, char* argv[]) if (args.hasArg("--help") || args.hasUnknownArgs()) { args.printHelp(); - return 0; + config.error = 0; + return config; } - std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); + config.brokerAddress = args.getArgValue("--address", "127.0.0.1"); + config.exit = false; + return config; +} + +int main(int argc, char* argv[]) +{ + // Parse input arguments + auto appConfig = StartUp(argc, argv); + if (appConfig.exit) + { + return appConfig.error; + } const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).setRootDevice("daqref://device0").build(); auto refDevice = instance.getRootDevice(); refDevice.setPropertyValue("NumberOfChannels", 4); + // Configure channels const auto channels = refDevice.getChannelsRecursive(); channels[0].setPropertyValue("UseGlobalSampleRate", false); channels[0].setPropertyValue("SampleRate", 10); @@ -37,8 +58,9 @@ int main(int argc, char* argv[]) channels[2].setPropertyValue("SampleRate", 200); channels[2].setPropertyValue("Frequency", 4); + // Create and configure MQTT server auto serverConfig = instance.getAvailableServerTypes().get("OpenDAQMQTT").createDefaultConfig(); - serverConfig.setPropertyValue("MqttBrokerAddress", brokerAddress); + serverConfig.setPropertyValue("MqttBrokerAddress", appConfig.brokerAddress); const auto mqttServer = instance.addServer("OpenDAQMQTT", serverConfig); std::cout << "Press \"enter\" to exit the application..." << std::endl; diff --git a/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp b/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp index cfafc3cf..e9b331af 100644 --- a/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp +++ b/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp @@ -9,8 +9,16 @@ using namespace daq; using namespace daq::modules::mqtt_streaming_client_module; -int main(int argc, char* argv[]) +struct ConfigStruct { + std::string brokerAddress; + std::vector topics; + bool exit = true; + int error = 0; +}; + +ConfigStruct StartUp(int argc, char* argv[]) { + ConfigStruct config; InputArgs args; args.addArg("--help", "Show help message"); args.addArg("--address", "MQTT broker address", true); @@ -20,72 +28,82 @@ int main(int argc, char* argv[]) if (args.hasArg("--help") || args.hasUnknownArgs()) { args.printHelp(); - return 0; + config.error = 0; + return config; } - std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); - auto topics = args.getPositionalArgs(); + config.brokerAddress = args.getArgValue("--address", "127.0.0.1"); + config.topics = args.getPositionalArgs(); - if (topics.empty()) + if (config.topics.empty()) { std::cout << "MQTT topics are required." << std::endl; - return -1; + config.error = -1; + return config; } - const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); - auto brokerDevice = instance.addDevice("daq.mqtt://" + brokerAddress); - auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); - if (availableDeviceNodes.getCount() == 0) - { - std::cout << "No function block available from the device." << std::endl; - return -1; - } + config.exit = false; + return config; +} - for (const auto& [key, value] : availableDeviceNodes) +int main(int argc, char* argv[]) +{ + // Parse input arguments + auto appConfig = StartUp(argc, argv); + if (appConfig.exit) { - std::cout << "Available function block: " << key << std::endl; + return appConfig.error; } + + // Create OpenDAQ instance and add MQTT broker device + const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); + auto brokerDevice = instance.addDevice("daq.mqtt://" + appConfig.brokerAddress); + auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); + const std::string fbName = RAW_FB_NAME; std::cout << "Try to add the " << fbName << std::endl; + // Create RAW function block configuration auto config = availableDeviceNodes.get(fbName).createDefaultConfig(); auto topicList = List(); - for (auto& topic : topics) + for (auto& topic : appConfig.topics) { addToList(topicList, std::move(topic)); } config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + + // Add the RAW function block to the broker device daq::FunctionBlockPtr rawFb = brokerDevice.addFunctionBlock(fbName, config); - auto signals = rawFb.getSignals(); + // Create packet readers for all signals + const auto signals = rawFb.getSignals(); std::map readers; for (const auto& s : signals) { readers.emplace(std::pair(s.getName().toStdString(), daq::PacketReader(s))); } + // Start a thread to read packets from the readers std::atomic running = true; std::thread readerThread( [&readers, &running]() { while (running) { - for (const auto& pair : readers) + for (const auto& [signalName, reader] : readers) { - const auto& reader = pair.second; while (!reader.getEmpty() && running) { auto packet = reader.read(); - const auto eventPacket = packet.asPtrOrNull(); - if (eventPacket.assigned()) + if (packet.getType() == PacketType::Event) { continue; } - const auto dataPacket = packet.asPtrOrNull(); - if (dataPacket.assigned()) + else if (packet.getType() == PacketType::Data) { - std::string tmp(static_cast(dataPacket.getData()), dataPacket.getDataSize()); - std::cout << pair.first << " - " << tmp << std::endl; + const auto dataPacket = packet.asPtr(); + std::string dataStr(static_cast(dataPacket.getData()), dataPacket.getDataSize()); + std::cout << signalName << " - " << dataStr << std::endl; } } } diff --git a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp index 8e4c03cb..2beb6903 100644 --- a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp +++ b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp @@ -7,6 +7,32 @@ using namespace daq; using namespace daq::modules::mqtt_streaming_client_module; +struct ConfigStruct { + std::string brokerAddress; + bool exit = true; + int error = 0; +}; + +ConfigStruct StartUp(int argc, char* argv[]) +{ + ConfigStruct config; + InputArgs args; + args.addArg("--help", "Show help message"); + args.addArg("--address", "MQTT broker address", true); + args.parse(argc, argv); + + if (args.hasArg("--help") || args.hasUnknownArgs()) + { + args.printHelp(); + config.error = 0; + return config; + } + + config.brokerAddress = args.getArgValue("--address", "127.0.0.1"); + config.exit = false; + return config; +} + std::string to_string(daq::DataPacketPtr packet) { std::string result; @@ -39,39 +65,38 @@ std::string to_string(daq::DataPacketPtr packet) int main(int argc, char* argv[]) { - InputArgs args; - args.addArg("--help", "Show help message"); - args.addArg("--address", "MQTT broker address", true); // If you want to support --address for sub - args.parse(argc, argv); - - if (args.hasArg("--help") || args.hasUnknownArgs()) + // Parse input arguments + auto appConfig = StartUp(argc, argv); + if (appConfig.exit) { - args.printHelp(); - return 0; + return appConfig.error; } - std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); - + // Create OpenDAQ instance and add MQTT broker device const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); - auto brokerDevice = instance.addDevice("daq.mqtt://" + brokerAddress); + auto brokerDevice = instance.addDevice("daq.mqtt://" + appConfig.brokerAddress); auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); - if (availableDeviceNodes.getCount() == 0) + // Check available function blocks, skip RAW and JSON FBs + if (availableDeviceNodes.getCount() <= 2) { std::cout << "No function block available from the device." << std::endl; return -1; } std::vector fbList; + std::cout << "Available function blocks: " << std::endl; for (const auto& [key, value] : availableDeviceNodes) { - std::cout << "Available function block: " << key << std::endl; + std::cout << " - " << key << std::endl; if (key == RAW_FB_NAME || key == JSON_FB_NAME) continue; fbList.push_back(brokerDevice.addFunctionBlock(key)); } + std::cout << "Try to connect the " << fbList[0].getLocalId() << std::endl; + // Create packet readers for all signals auto signals = fbList[0].getSignals(); std::vector readers; for (const auto& s : signals) @@ -79,6 +104,7 @@ int main(int argc, char* argv[]) readers.emplace_back(daq::PacketReader(s)); } + // Start a thread to read packets from the readers std::atomic running = true; std::thread readerThread( [&readers, &running]() @@ -94,10 +120,8 @@ int main(int argc, char* argv[]) if (packet.getType() == PacketType::Event) { std::cout << "Event packet is skipped!" << std::endl; - continue; } - - if (packet.getType() == PacketType::Data) + else if (packet.getType() == PacketType::Data) { const auto dataPacket = packet.asPtrOrNull(); std::cout << "READER #" << iRdr << " - " << to_string(dataPacket) << std::endl; From 0b36e9fb35077031f84d49a64d68effe031339c1 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 7 Nov 2025 15:16:49 +0100 Subject: [PATCH 48/55] mqtt: dependencies --- mqtt_streaming_protocol/src/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mqtt_streaming_protocol/src/CMakeLists.txt b/mqtt_streaming_protocol/src/CMakeLists.txt index 90a28a22..9dab80f3 100644 --- a/mqtt_streaming_protocol/src/CMakeLists.txt +++ b/mqtt_streaming_protocol/src/CMakeLists.txt @@ -33,6 +33,7 @@ endif() target_include_directories(${LIB_NAME} PUBLIC $ $ $ + ${Boost_INCLUDE_DIRS} ) if(PAHO_WITH_SSL) @@ -48,8 +49,8 @@ endif() target_link_libraries(${LIB_NAME} PUBLIC ${PAHO_LIB} daq::opendaq rapidjson - PRIVATE $ - $ + Boost::date_time + PRIVATE Boost::algorithm ) if (WIN32) From 9bdbf6f03293baf2591699ea612d4c8a5ff11419 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 13 Nov 2025 11:33:14 +0100 Subject: [PATCH 49/55] mqtt: updating dependencies --- external/opendaq/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/opendaq/CMakeLists.txt b/external/opendaq/CMakeLists.txt index a889308c..cddbe036 100644 --- a/external/opendaq/CMakeLists.txt +++ b/external/opendaq/CMakeLists.txt @@ -3,7 +3,7 @@ set(OPENDAQ_ENABLE_TESTS false) FetchContent_Declare( opendaq GIT_REPOSITORY https://github.com/openDAQ/openDAQ.git - GIT_TAG origin/release-candidate/3.30 + GIT_TAG origin/main GIT_PROGRESS ON EXCLUDE_FROM_ALL SYSTEM From 76fa5c98b54324fc3041a4e27553af1122c175d6 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 13 Nov 2025 11:42:07 +0100 Subject: [PATCH 50/55] mqtt: removing unnecessary linkage from examples --- examples/custom-mqtt-sub/src/CMakeLists.txt | 4 +--- examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp | 6 ++---- examples/ref-dev-mqtt-raw-sub/src/CMakeLists.txt | 1 - examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp | 6 ++---- examples/ref-dev-mqtt-sub/src/CMakeLists.txt | 4 +--- examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp | 4 +--- 6 files changed, 7 insertions(+), 18 deletions(-) diff --git a/examples/custom-mqtt-sub/src/CMakeLists.txt b/examples/custom-mqtt-sub/src/CMakeLists.txt index 222a08fc..f95d5b52 100644 --- a/examples/custom-mqtt-sub/src/CMakeLists.txt +++ b/examples/custom-mqtt-sub/src/CMakeLists.txt @@ -4,7 +4,5 @@ add_compile_definitions(MODULE_PATH="${OPENDAQ_MODULES_DIR}") add_compile_definitions(APP_NAME="${EXAMPLE_PROJECT_NAME}") add_executable(${EXAMPLE_PROJECT_NAME} custom-mqtt-sub.cpp) -target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq - mqtt_stream_cli_module -) +target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq) target_include_directories(${EXAMPLE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../) diff --git a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp index 148171a3..818c7416 100644 --- a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp +++ b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp @@ -1,6 +1,5 @@ #include "../../InputArgs.h" #include -#include #include #include @@ -8,7 +7,6 @@ #include using namespace daq; -using namespace daq::modules::mqtt_streaming_client_module; struct ConfigStruct { std::string brokerAddress; @@ -128,13 +126,13 @@ int main(int argc, char* argv[]) auto brokerDevice = instance.addDevice("daq.mqtt://" + appConfig.brokerAddress); auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); - const std::string fbName = JSON_FB_NAME; + const std::string fbName = "@jsonMqttFb"; std::cout << "Try to add the " << fbName << std::endl; // Read JSON function block configuration from file and fill out the function block config const std::string jsonConfig = readFileToString(appConfig.configFilePath); auto config = availableDeviceNodes.get(fbName).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); + config.setPropertyValue("SignalList", jsonConfig); // Add the JSON function block to the broker device daq::FunctionBlockPtr jsonFb = brokerDevice.addFunctionBlock(fbName, config); diff --git a/examples/ref-dev-mqtt-raw-sub/src/CMakeLists.txt b/examples/ref-dev-mqtt-raw-sub/src/CMakeLists.txt index c213494f..b7907859 100644 --- a/examples/ref-dev-mqtt-raw-sub/src/CMakeLists.txt +++ b/examples/ref-dev-mqtt-raw-sub/src/CMakeLists.txt @@ -7,7 +7,6 @@ add_executable(${EXAMPLE_PROJECT_NAME} ref-dev-mqtt-raw-sub.cpp) target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq rapidjson - mqtt_stream_cli_module ) target_include_directories(${EXAMPLE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../) diff --git a/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp b/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp index e9b331af..85d9d018 100644 --- a/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp +++ b/examples/ref-dev-mqtt-raw-sub/src/ref-dev-mqtt-raw-sub.cpp @@ -1,5 +1,4 @@ #include "../../InputArgs.h" -#include #include #include #include @@ -7,7 +6,6 @@ #include using namespace daq; -using namespace daq::modules::mqtt_streaming_client_module; struct ConfigStruct { std::string brokerAddress; @@ -60,7 +58,7 @@ int main(int argc, char* argv[]) auto brokerDevice = instance.addDevice("daq.mqtt://" + appConfig.brokerAddress); auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); - const std::string fbName = RAW_FB_NAME; + const std::string fbName = "@rawMqttFb"; std::cout << "Try to add the " << fbName << std::endl; // Create RAW function block configuration @@ -70,7 +68,7 @@ int main(int argc, char* argv[]) { addToList(topicList, std::move(topic)); } - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, topicList); + config.setPropertyValue("SignalList", topicList); // Add the RAW function block to the broker device daq::FunctionBlockPtr rawFb = brokerDevice.addFunctionBlock(fbName, config); diff --git a/examples/ref-dev-mqtt-sub/src/CMakeLists.txt b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt index 1ed54195..c7621cc2 100644 --- a/examples/ref-dev-mqtt-sub/src/CMakeLists.txt +++ b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt @@ -3,7 +3,5 @@ cmake_minimum_required(VERSION 3.16) add_compile_definitions(MODULE_PATH="${OPENDAQ_MODULES_DIR}") add_executable(${EXAMPLE_PROJECT_NAME} ref-dev-mqtt-sub.cpp) -target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq - mqtt_stream_cli_module -) +target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq) target_include_directories(${EXAMPLE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../) diff --git a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp index 2beb6903..24774418 100644 --- a/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp +++ b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp @@ -1,11 +1,9 @@ #include "../../InputArgs.h" -#include #include #include using namespace daq; -using namespace daq::modules::mqtt_streaming_client_module; struct ConfigStruct { std::string brokerAddress; @@ -89,7 +87,7 @@ int main(int argc, char* argv[]) for (const auto& [key, value] : availableDeviceNodes) { std::cout << " - " << key << std::endl; - if (key == RAW_FB_NAME || key == JSON_FB_NAME) + if (key == "@rawMqttFb" || key == "@jsonMqttFb") continue; fbList.push_back(brokerDevice.addFunctionBlock(key)); } From 802cc96c259d5ce3e2d59442edd847735ca8b296 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 13 Nov 2025 11:45:37 +0100 Subject: [PATCH 51/55] mqtt: refactoring --- mqtt_streaming_protocol/include/MqttDataWrapper.h | 3 +-- mqtt_streaming_protocol/src/MqttDataWrapper.cpp | 3 +-- .../mqtt_streaming_server_module/mqtt_streaming_server_impl.h | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/mqtt_streaming_protocol/include/MqttDataWrapper.h index 23c12ed4..f89d5865 100644 --- a/mqtt_streaming_protocol/include/MqttDataWrapper.h +++ b/mqtt_streaming_protocol/include/MqttDataWrapper.h @@ -52,8 +52,7 @@ class MqttDataWrapper final static std::string extractDeviceName(const std::string& topic); static std::string serializeSampleData(const SampleData& data); - static std::string serializeSignalDescriptors( - daq::ListObjectPtr> signals); + static std::string serializeSignalDescriptors(daq::ListPtr signals); static std::string buildTopicFromId(const std::string& globalId); static std::string buildSignalsTopic(const std::string& deviceId); diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 17bfcb27..2102bc78 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -44,8 +44,7 @@ std::string MqttDataWrapper::serializeSampleData(const SampleData& data) return result; } -std::string MqttDataWrapper::serializeSignalDescriptors( - daq::ListObjectPtr> signals) +std::string MqttDataWrapper::serializeSignalDescriptors(daq::ListPtr signals) { std::string result; rapidjson::Document doc; diff --git a/mqtt_streaming_server_module/include/mqtt_streaming_server_module/mqtt_streaming_server_impl.h b/mqtt_streaming_server_module/include/mqtt_streaming_server_module/mqtt_streaming_server_impl.h index 21004054..d391e147 100644 --- a/mqtt_streaming_server_module/include/mqtt_streaming_server_module/mqtt_streaming_server_impl.h +++ b/mqtt_streaming_server_module/include/mqtt_streaming_server_module/mqtt_streaming_server_impl.h @@ -68,7 +68,7 @@ class MqttStreamingServerImpl : public daq::Server static void populateDefaultConfigFromProvider(const ContextPtr& context, const PropertyObjectPtr& config); - daq::ListObjectPtr> signals; + daq::ListPtr signals; std::vector streamReaders; std::string rootDeviceGlobalId; From 1a4e0d6bca9746997a2cde6785a5212e5812ef3c Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 13 Nov 2025 11:58:18 +0100 Subject: [PATCH 52/55] mqtt: sync disconnnection fix --- .../src/MqttAsyncClient.cpp | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index 598aa48d..ad53c847 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -107,25 +107,25 @@ bool MqttAsyncClient::syncDisconnect(int timeoutMs) if (isConnected() != MqttConnectionStatus::not_connected) { - if (disconnect().success) + std::atomic done{false}; + std::promise disconnectedPromise; + auto disconnectedFuture = disconnectedPromise.get_future(); { - std::atomic done{false}; - std::promise disconnectedPromise; - auto disconnectedFuture = disconnectedPromise.get_future(); + auto lock = getCbLock(); + onInternalDisconnectCb = [promise = &disconnectedPromise, &done](bool result) { - auto lock = getCbLock(); - onInternalDisconnectCb = [promise = &disconnectedPromise, &done](bool result) - { - bool expected = false; - if (done.compare_exchange_strong(expected, true)) - promise->set_value(result); - }; - } + bool expected = false; + if (done.compare_exchange_strong(expected, true)) + promise->set_value(result); + }; + } + if (disconnect().success) + { auto status = disconnectedFuture.wait_for(std::chrono::milliseconds(timeoutMs)); - { - auto lock = getCbLock(); - onInternalDisconnectCb = nullptr; - } + } + { + auto lock = getCbLock(); + onInternalDisconnectCb = nullptr; } } if (isConnected() == MqttConnectionStatus::not_connected) From e240597413dbbeb3489f199d92630af17ee475be Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 13 Nov 2025 12:14:00 +0100 Subject: [PATCH 53/55] mqtt: refactoring --- .../src/mqtt_json_receiver_fb_impl.cpp | 10 +++++----- .../src/mqtt_raw_receiver_fb_impl.cpp | 6 +++--- .../src/mqtt_streaming_device_impl.cpp | 2 +- .../tests/test_daq_test_helper.h | 2 +- mqtt_streaming_protocol/include/MqttAsyncClient.h | 2 +- mqtt_streaming_protocol/src/MqttAsyncClient.cpp | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp index e773ba35..53ec2813 100644 --- a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp @@ -33,7 +33,7 @@ MqttJsonReceiverFbImpl::~MqttJsonReceiverFbImpl() void MqttJsonReceiverFbImpl::readProperties() { - auto lock = std::lock_guard(sync); + auto lock = std::scoped_lock(sync); subscribedSignals.clear(); signalIdList.clear(); bool isPresent = false; @@ -66,7 +66,7 @@ void MqttJsonReceiverFbImpl::readProperties() void MqttJsonReceiverFbImpl::createDataPacket(const std::string& topic, const std::string& json) { - auto lock = std::lock_guard(sync); + auto lock = std::scoped_lock(sync); jsonDataWorker.createAndSendDataPacket(topic, json); } @@ -79,7 +79,7 @@ void MqttJsonReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) void MqttJsonReceiverFbImpl::createSignals() { - auto lock = std::lock_guard(sync); + auto lock = std::scoped_lock(sync); if (!subscribedSignals.empty()) LOG_I("Creating signals..."); @@ -131,7 +131,7 @@ void MqttJsonReceiverFbImpl::createSignals() std::vector MqttJsonReceiverFbImpl::getSubscribedTopics() const { - auto lock = std::lock_guard(sync); + auto lock = std::scoped_lock(sync); std::set topicsSet; for (const auto& [signalId, _] : subscribedSignals) { @@ -142,7 +142,7 @@ std::vector MqttJsonReceiverFbImpl::getSubscribedTopics() const void MqttJsonReceiverFbImpl::clearSubscribedTopics() { - auto lock = std::lock_guard(sync); + auto lock = std::scoped_lock(sync); subscribedSignals.clear(); signalIdList.clear(); } diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index f1335117..e9f94cbd 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -33,7 +33,7 @@ MqttRawReceiverFbImpl::~MqttRawReceiverFbImpl() void MqttRawReceiverFbImpl::readProperties() { - auto lock = std::lock_guard(sync); + auto lock = std::scoped_lock(sync); topicsForSubscribing.clear(); bool isPresent = false; if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) @@ -69,7 +69,7 @@ void MqttRawReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) { std::string topic(msg.getTopic()); - auto lock = std::lock_guard(sync); + auto lock = std::scoped_lock(sync); auto signalIter = outputSignals.find(topic); if (signalIter == outputSignals.end()) { @@ -84,7 +84,7 @@ void MqttRawReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) void MqttRawReceiverFbImpl::createSignals() { - auto lock = std::lock_guard(sync); + auto lock = std::scoped_lock(sync); if (!topicsForSubscribing.empty()) LOG_I("Creating signals..."); for (const auto& topic : topicsForSubscribing) diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp index 7c379e89..3809d534 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -223,7 +223,7 @@ FunctionBlockPtr MqttStreamingDeviceImpl::onAddFunctionBlock(const StringPtr& ty } else { - setComponentStatusWithMessage(ComponentStatus::Error, "Function block type is not available: " + typeId.toStdString()); + DAQ_THROW_EXCEPTION(NotFoundException, "Function block type is not available: " + typeId.toStdString()); } } return nestedFunctionBlock; diff --git a/mqtt_streaming_client_module/tests/test_daq_test_helper.h b/mqtt_streaming_client_module/tests/test_daq_test_helper.h index fe7787f7..3de73e88 100644 --- a/mqtt_streaming_client_module/tests/test_daq_test_helper.h +++ b/mqtt_streaming_client_module/tests/test_daq_test_helper.h @@ -7,7 +7,7 @@ class DaqTestHelper { public: daq::InstancePtr daqInstance; - daq::GenericDevicePtr device; + daq::DevicePtr device; void StartUp(std::string connectionStr = "daq.mqtt://127.0.0.1", int discoveryTimeoutMs = 0) { diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h index a62ce0ca..c282a0a6 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncClient.h +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -116,7 +116,7 @@ class MqttAsyncClient final { std::function onMsgArrivedCmnCb; std::unordered_map> onMsgArrivedCbs; - std::lock_guard getCbLock(); + std::scoped_lock getCbLock(); static void onDeliveryCompleted(void *context, MQTTAsync_token token); static void onConnected(void *context, char *cause); diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index ad53c847..273456cc 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -147,9 +147,9 @@ MqttConnectionStatus MqttAsyncClient::isConnected() const return MQTTAsync_isConnected(client) ? MqttConnectionStatus::connected : MqttConnectionStatus::not_connected; } -std::lock_guard MqttAsyncClient::getCbLock() +std::scoped_lock MqttAsyncClient::getCbLock() { - return std::lock_guard(cbMtx); + return std::scoped_lock(cbMtx); } void MqttAsyncClient::setUsernamePasswrod(std::string username, std::string password) From ce2eaca1de2474a168baebcdbd0e46c9af9461dd Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 13 Nov 2025 12:46:46 +0100 Subject: [PATCH 54/55] mqtt: {} for LOG_x --- mqtt_streaming_client_module/src/mqtt_base_fb.cpp | 2 ++ .../src/mqtt_json_receiver_fb_impl.cpp | 2 ++ .../src/mqtt_raw_receiver_fb_impl.cpp | 4 ++++ mqtt_streaming_protocol/src/MqttDataWrapper.cpp | 4 ++++ 4 files changed, 12 insertions(+) diff --git a/mqtt_streaming_client_module/src/mqtt_base_fb.cpp b/mqtt_streaming_client_module/src/mqtt_base_fb.cpp index f6bbf6d1..3ec6ca5a 100644 --- a/mqtt_streaming_client_module/src/mqtt_base_fb.cpp +++ b/mqtt_streaming_client_module/src/mqtt_base_fb.cpp @@ -52,7 +52,9 @@ void MqttBaseFb::subscribeToTopics() bool success = true; auto lambda = [this](const mqtt::MqttAsyncClient &client, mqtt::MqttMessage &msg){this->onSignalsMessage(client, msg);}; if (!getSubscribedTopics().empty()) + { LOG_I("Trying to subscribe to the topics"); + } for (const auto& topic : getSubscribedTopics()) { subscriber->setMessageArrivedCb(topic, lambda); diff --git a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp index 53ec2813..de82d664 100644 --- a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp @@ -81,7 +81,9 @@ void MqttJsonReceiverFbImpl::createSignals() { auto lock = std::scoped_lock(sync); if (!subscribedSignals.empty()) + { LOG_I("Creating signals..."); + } for (const auto& signalId : signalIdList) { diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index e9f94cbd..1ee2d8ca 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -43,7 +43,9 @@ void MqttRawReceiverFbImpl::readProperties() { isPresent = true; if (prop.getCount() != 0) + { LOG_I("Topics in the list:"); + } for (const auto& topic : prop) { auto topicStr = topic.asPtr(); @@ -86,7 +88,9 @@ void MqttRawReceiverFbImpl::createSignals() { auto lock = std::scoped_lock(sync); if (!topicsForSubscribing.empty()) + { LOG_I("Creating signals..."); + } for (const auto& topic : topicsForSubscribing) { LOG_D("\tfor the topic: {}", topic); diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 2102bc78..928d1ab7 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -128,7 +128,9 @@ bool MqttDataWrapper::validateTopic(const daq::StringPtr topic, const daq::Logge if (!topic.assigned() || topic.getLength() == 0) { if (loggerComponent.assigned()) + { LOG_W("Empty topic is not allowed!"); + } return false; } @@ -140,7 +142,9 @@ bool MqttDataWrapper::validateTopic(const daq::StringPtr topic, const daq::Logge if (part == "#" || part == "+") { if (loggerComponent.assigned()) + { LOG_W("Wildcard characters '+' and '#' are not allowed in topic: {}", topic.toStdString()); + } return false; } } From 0d94e9e75c3b685626095b43bdf01d1af6693a3f Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 13 Nov 2025 18:13:53 +0100 Subject: [PATCH 55/55] mqtt: refactoring --- .../src/mqtt_json_receiver_fb_impl.cpp | 10 +++++----- .../src/mqtt_raw_receiver_fb_impl.cpp | 6 +++--- .../src/mqtt_streaming_client_module_impl.cpp | 2 +- mqtt_streaming_protocol/src/MqttAsyncClient.cpp | 2 +- .../src/mqtt_streaming_server_impl.cpp | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp index de82d664..bb3a86de 100644 --- a/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_json_receiver_fb_impl.cpp @@ -33,7 +33,7 @@ MqttJsonReceiverFbImpl::~MqttJsonReceiverFbImpl() void MqttJsonReceiverFbImpl::readProperties() { - auto lock = std::scoped_lock(sync); + auto lock = std::scoped_lock(sync); subscribedSignals.clear(); signalIdList.clear(); bool isPresent = false; @@ -66,7 +66,7 @@ void MqttJsonReceiverFbImpl::readProperties() void MqttJsonReceiverFbImpl::createDataPacket(const std::string& topic, const std::string& json) { - auto lock = std::scoped_lock(sync); + auto lock = std::scoped_lock(sync); jsonDataWorker.createAndSendDataPacket(topic, json); } @@ -79,7 +79,7 @@ void MqttJsonReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) void MqttJsonReceiverFbImpl::createSignals() { - auto lock = std::scoped_lock(sync); + auto lock = std::scoped_lock(sync); if (!subscribedSignals.empty()) { LOG_I("Creating signals..."); @@ -133,7 +133,7 @@ void MqttJsonReceiverFbImpl::createSignals() std::vector MqttJsonReceiverFbImpl::getSubscribedTopics() const { - auto lock = std::scoped_lock(sync); + auto lock = std::scoped_lock(sync); std::set topicsSet; for (const auto& [signalId, _] : subscribedSignals) { @@ -144,7 +144,7 @@ std::vector MqttJsonReceiverFbImpl::getSubscribedTopics() const void MqttJsonReceiverFbImpl::clearSubscribedTopics() { - auto lock = std::scoped_lock(sync); + auto lock = std::scoped_lock(sync); subscribedSignals.clear(); signalIdList.clear(); } diff --git a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp index 1ee2d8ca..b03069ae 100644 --- a/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_raw_receiver_fb_impl.cpp @@ -33,7 +33,7 @@ MqttRawReceiverFbImpl::~MqttRawReceiverFbImpl() void MqttRawReceiverFbImpl::readProperties() { - auto lock = std::scoped_lock(sync); + auto lock = std::scoped_lock(sync); topicsForSubscribing.clear(); bool isPresent = false; if (objPtr.hasProperty(PROPERTY_NAME_SIGNAL_LIST)) @@ -71,7 +71,7 @@ void MqttRawReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) { std::string topic(msg.getTopic()); - auto lock = std::scoped_lock(sync); + auto lock = std::scoped_lock(sync); auto signalIter = outputSignals.find(topic); if (signalIter == outputSignals.end()) { @@ -86,7 +86,7 @@ void MqttRawReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) void MqttRawReceiverFbImpl::createSignals() { - auto lock = std::scoped_lock(sync); + auto lock = std::scoped_lock(sync); if (!topicsForSubscribing.empty()) { LOG_I("Creating signals..."); diff --git a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp index ee545c88..71177eac 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp @@ -72,7 +72,7 @@ MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionString, con std::string host = configPtr.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS); Int port = configPtr.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT); - std::scoped_lock lock(sync); + auto lock = std::scoped_lock(sync); DevicePtr device = createWithImplementation(context, parent, configPtr); diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index 273456cc..bf2cd389 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -149,7 +149,7 @@ MqttConnectionStatus MqttAsyncClient::isConnected() const std::scoped_lock MqttAsyncClient::getCbLock() { - return std::scoped_lock(cbMtx); + return std::scoped_lock(cbMtx); } void MqttAsyncClient::setUsernamePasswrod(std::string username, std::string password) diff --git a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp index 16dad2bd..9754f649 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -149,7 +149,7 @@ void MqttStreamingServerImpl::processingThreadFunc() while (processingThreadRunning) { { - std::scoped_lock lock(readersSync); + auto lock = std::scoped_lock(readersSync); if (!topicsAreSent) sendTopicList(); bool hasPacketsToRead; @@ -350,7 +350,7 @@ void MqttStreamingServerImpl::onStopServer() void MqttStreamingServerImpl::addReader(SignalPtr signalToRead) { - std::scoped_lock lock(readersSync); + auto lock = std::scoped_lock(readersSync); signals.pushBack(signalToRead); streamReaders.emplace_back(StreamReaderBuilder() .setSignal(signalToRead)