From b2ff1a67925b1174b490ab5a4a79aa92c171991b Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 22 Sep 2025 10:38:17 +0200 Subject: [PATCH 01/46] mqtt client implementation --- CMakeLists.txt | 1 + examples/CMakeLists.txt | 1 + examples/ref-dev-mqtt-sub/CMakeLists.txt | 12 + examples/ref-dev-mqtt-sub/src/CMakeLists.txt | 7 + .../ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp | 123 +++++++++ mqtt_streaming_client_module/CMakeLists.txt | 5 + .../mqtt_streaming_client_module/common.h | 21 ++ .../mqtt_streaming_client_module/constants.h | 38 +++ .../mqtt_streaming_client_module/module_dll.h | 20 ++ .../mqtt_receiver_fb_impl.h | 67 +++++ .../mqtt_streaming_client_module_impl.h | 46 ++++ .../mqtt_streaming_device_impl.h | 63 +++++ .../src/CMakeLists.txt | 61 +++++ .../src/module_dll.cpp | 9 + .../src/mqtt_receiver_fb_impl.cpp | 200 ++++++++++++++ .../src/mqtt_streaming_client_module_impl.cpp | 259 ++++++++++++++++++ .../src/mqtt_streaming_device_impl.cpp | 162 +++++++++++ .../include/IMqttSubscriber.h | 4 +- .../include/MqttAsyncSubscriber.h | 16 +- .../include/MqttSettings.h | 13 + mqtt_streaming_protocol/src/CMakeLists.txt | 1 + .../mqtt_streaming_server_impl.h | 11 +- .../src/mqtt_streaming_server_impl.cpp | 2 +- 23 files changed, 1127 insertions(+), 15 deletions(-) create mode 100644 examples/ref-dev-mqtt-sub/CMakeLists.txt create mode 100644 examples/ref-dev-mqtt-sub/src/CMakeLists.txt create mode 100644 examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp create mode 100644 mqtt_streaming_client_module/CMakeLists.txt create mode 100644 mqtt_streaming_client_module/include/mqtt_streaming_client_module/common.h create mode 100644 mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h create mode 100644 mqtt_streaming_client_module/include/mqtt_streaming_client_module/module_dll.h create mode 100644 mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h create mode 100644 mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h create mode 100644 mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h create mode 100644 mqtt_streaming_client_module/src/CMakeLists.txt create mode 100644 mqtt_streaming_client_module/src/module_dll.cpp create mode 100644 mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp create mode 100644 mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp create mode 100644 mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp create mode 100644 mqtt_streaming_protocol/include/MqttSettings.h diff --git a/CMakeLists.txt b/CMakeLists.txt index eee2515e..a1ab0826 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ endif() add_subdirectory(external) add_subdirectory(mqtt_streaming_protocol) add_subdirectory(mqtt_streaming_server_module) +add_subdirectory(mqtt_streaming_client_module) if(OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS) message(STATUS "Example applications have been enabled") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 25c3842e..d30841eb 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,4 @@ cmake_minimum_required(VERSION 3.16) add_subdirectory(ref-dev-mqtt-pub) +add_subdirectory(ref-dev-mqtt-sub) \ No newline at end of file diff --git a/examples/ref-dev-mqtt-sub/CMakeLists.txt b/examples/ref-dev-mqtt-sub/CMakeLists.txt new file mode 100644 index 00000000..91eaee8c --- /dev/null +++ b/examples/ref-dev-mqtt-sub/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.16) + +set(EXAMPLE_PROJECT_NAME "ref-dev-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/ref-dev-mqtt-sub/src/CMakeLists.txt b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt new file mode 100644 index 00000000..f70b529f --- /dev/null +++ b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt @@ -0,0 +1,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) +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-sub/src/ref-dev-mqtt-sub.cpp b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp new file mode 100644 index 00000000..6ccd8ab9 --- /dev/null +++ b/examples/ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp @@ -0,0 +1,123 @@ +#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 brokerDevice = instance.addDevice("daq.mqtt://127.0.0.1"); + auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); + + std::vector fbList; + for (const auto& [key, value] : availableDeviceNodes) { + std::cout << "Available function block: " << key << std::endl; + fbList.push_back(brokerDevice.addFunctionBlock(key, value.createDefaultConfig())); + } + std::vector readers; + auto signals = fbList.back().getSignals(); + for (const auto& s : signals) + { + readers.emplace_back(daq::StreamReader(s, ReadTimeoutType::Any)); + } + +#if 1 + std::thread t1([readers]() { + uint64_t cnt = 0; + constexpr int size = 1000; + double samples[size]; + uint64_t timestamps[size]; + while (true) { + for (const auto& reader : readers) { + while (!reader.getEmpty()) + { + daq::SizeT count = size; + reader.readWithDomain(samples, timestamps, &count); + for (daq::SizeT i = 0; i < count; ++i) + std::cout << "READER " << cnt++ << " - Sample: " << samples[i] << " Timestamp: " << timestamps[i] << std::endl; + } + } + std::this_thread::sleep_for(20ms); + } + }); + t1.detach(); +#endif + std::cout << "Press \"enter\" to exit the application..." << std::endl; + std::cin.get(); + return 0; +} diff --git a/mqtt_streaming_client_module/CMakeLists.txt b/mqtt_streaming_client_module/CMakeLists.txt new file mode 100644 index 00000000..2c476a49 --- /dev/null +++ b/mqtt_streaming_client_module/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.10) +set_cmake_folder_context(TARGET_FOLDER_NAME) +project(MqttClientModule VERSION 3.4.0 LANGUAGES C CXX) + +add_subdirectory(src) \ No newline at end of file diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/common.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/common.h new file mode 100644 index 00000000..c8e8ff84 --- /dev/null +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/common.h @@ -0,0 +1,21 @@ +/* + * 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 + +#define BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE BEGIN_NAMESPACE_OPENDAQ_MODULE(mqtt_streaming_client_module) +#define END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE END_NAMESPACE_OPENDAQ_MODULE 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 new file mode 100644 index 00000000..3bd587d8 --- /dev/null +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/constants.h @@ -0,0 +1,38 @@ +#pragma once + +#include "common.h" + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +static const char* DaqMqttDeviceTypeId = "OpenDAQMQTTStreaming"; +static const char* DaqMqttProtocolId = "OpenDAQMQTTStreaming"; +static const char* DaqMqttDevicePrefix = "daq.mqtt"; +static const char* MqttScheme = "mqtt"; + +static const char* MODULE_NAME = "OpenDAQMQTTClientModule"; +static const char* MODULE_ID = "OpenDAQMQTTClientModule"; +static const char* SHORT_MODULE_NAME = "MQTTClient"; +static const char* PROTOCOL_NAME = "OpenDAQMQTT"; +static const char* CONNECTION_TYPE = "TCP/IP"; + +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 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_SIGNAL_LIST = "SignalList"; + +static const char* TOPIC_ALL_SIGNALS = "openDAQ/+/$signals"; +static const char* TOPIC_ALL_SIGNALS_PREFIX = "openDAQ"; +static const char* DEVICE_SIGNAL_LIST = "$signals"; + +static const char* MQTT_LOCAL_DEVICE_ID_PREFIX = "MqttDevice"; +static const char* MQTT_DEVICE_NAME = "MqttStreamingClientPseudoDevice"; + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_client_module/include/mqtt_streaming_client_module/module_dll.h b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/module_dll.h new file mode 100644 index 00000000..b0f19906 --- /dev/null +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/module_dll.h @@ -0,0 +1,20 @@ +/* + * 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 + +DECLARE_MODULE_EXPORTS(MqttStreamingClientModule) 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 new file mode 100644 index 00000000..4d7af7ef --- /dev/null +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_receiver_fb_impl.h @@ -0,0 +1,67 @@ +/* + * 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 "MqttAsyncSubscriber.h" + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +class MqttReceiverFbImpl final : public FunctionBlock +{ +public: + explicit MqttReceiverFbImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const StringPtr& localId, + std::shared_ptr subscriber, + const PropertyObjectPtr& config = nullptr); + ~MqttReceiverFbImpl() override; + + static FunctionBlockTypePtr CreateType(); + +private: + std::unordered_map outputSignals; + std::unordered_map outputDomainSignals; + + std::shared_ptr subscriber; + std::vector subscribedTopics; + + std::mutex sync; + + void createSignals(); + + void parseMessage(mqtt::MqttMessage& msg); + void createDataPacket(const std::string& topic, double value, UInt timestamp); + + void configure(); + + void initProperties(const PropertyObjectPtr& config); + void propertyChanged(bool configure); + void readProperties(); + + void onSignalsMessage(const mqtt::IMqttSubscriber& subscriber, mqtt::MqttMessage& msg); + + std::string buildSignalNameFromTopic(std::string topic) const; + std::string buildDomainSignalNameFromTopic(std::string topic) const; + +}; + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE 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 new file mode 100644 index 00000000..301fe40d --- /dev/null +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_client_module_impl.h @@ -0,0 +1,46 @@ +/* + * 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 + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +class MqttStreamingClientModule final : public Module +{ +public: + MqttStreamingClientModule(ContextPtr context); + + ListPtr onGetAvailableDevices() override; + DictPtr onGetAvailableDeviceTypes() override; + DevicePtr onCreateDevice(const StringPtr& connectionString, + const ComponentPtr& parent, + const PropertyObjectPtr& config) override; + bool acceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config); + Bool onCompleteServerCapability(const ServerCapabilityPtr& source, const ServerCapabilityConfigPtr& target) override; // ??? + +private: + void extractConnectionParams(const StringPtr& connectionString, const PropertyObjectPtr& config, std::string& hostType); + static DeviceTypePtr createDeviceType(); + static PropertyObjectPtr createDefaultConfig(); + static PropertyObjectPtr populateDefaultConfig(const PropertyObjectPtr& config); + + std::mutex sync; +}; + +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 new file mode 100644 index 00000000..be288c58 --- /dev/null +++ b/mqtt_streaming_client_module/include/mqtt_streaming_client_module/mqtt_streaming_device_impl.h @@ -0,0 +1,63 @@ +/* + * 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 "MqttAsyncSubscriber.h" +#include "MqttSettings.h" +#include +#include +#include +#include + + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +class MqttStreamingDeviceImpl : public Device +{ +public: + explicit MqttStreamingDeviceImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const PropertyObjectPtr& config); + +protected: + static std::atomic localIndex; + static std::string getLocalId(); + + void removed() override; + DeviceInfoPtr onGetInfo() override; + + bool allowAddFunctionBlocksFromModules() override + { + return true; + }; + DictPtr onGetAvailableFunctionBlockTypes() override; + FunctionBlockPtr onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) override; + + void setupMqttSubscriber(); + void onSignalsMessage(const mqtt::IMqttSubscriber& subscriber, mqtt::MqttMessage& msg); + + StringPtr connectionString; + EnumerationPtr connectionStatus; + + std::shared_ptr subscriber; + Mqtt::Utils::Settings::MqttConnectionSettings connectionSettings; + + std::promise connectedPromise; + std::future connectedFuture; + std::unordered_map> deviceMap; +}; + +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 new file mode 100644 index 00000000..6897fce8 --- /dev/null +++ b/mqtt_streaming_client_module/src/CMakeLists.txt @@ -0,0 +1,61 @@ +set(LIB_NAME mqtt_stream_cli_module) +set(MODULE_HEADERS_DIR ../include/${TARGET_FOLDER_NAME}) + +set(SRC_Include common.h + module_dll.h + mqtt_streaming_client_module_impl.h + mqtt_streaming_device_impl.h + mqtt_receiver_fb_impl.h + constants.h +) + +set(SRC_Srcs module_dll.cpp + mqtt_streaming_client_module_impl.cpp + mqtt_streaming_device_impl.cpp + mqtt_receiver_fb_impl.cpp +) + +prepend_include(${TARGET_FOLDER_NAME} SRC_Include) + +source_group("common" FILES ${MODULE_HEADERS_DIR}/common.h + ${MODULE_HEADERS_DIR}/constants.h +) + +source_group("module" FILES ${MODULE_HEADERS_DIR}/mqtt_streaming_client_module_impl.h + ${MODULE_HEADERS_DIR}/module_dll.h + module_dll.cpp + mqtt_streaming_client_module_impl.cpp +) + +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 + dispatch.h + mqtt_receiver_fb_impl.cpp +) + +add_library(${LIB_NAME} SHARED ${SRC_Include} + ${SRC_Srcs} +) +add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) + +if (MSVC) + target_compile_options(${LIB_NAME} PRIVATE /bigobj) +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}) +create_version_header(${LIB_NAME}) + diff --git a/mqtt_streaming_client_module/src/module_dll.cpp b/mqtt_streaming_client_module/src/module_dll.cpp new file mode 100644 index 00000000..b8015666 --- /dev/null +++ b/mqtt_streaming_client_module/src/module_dll.cpp @@ -0,0 +1,9 @@ +#include +#include + +#include + +using namespace daq::modules::mqtt_streaming_client_module; + +DEFINE_MODULE_EXPORTS(MqttStreamingClientModule) + diff --git a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp new file mode 100644 index 00000000..92cd5744 --- /dev/null +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -0,0 +1,200 @@ +#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" + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const StringPtr& localId, + std::shared_ptr subscriber, + const PropertyObjectPtr& config) + : FunctionBlock(CreateType(), ctx, parent, localId) + , subscriber(subscriber) +{ + initComponentStatus(); + + if (config.assigned()) + initProperties(config); + createSignals(); + + for (const auto& topic : subscribedTopics) + { + subscriber->setMessageArrivedCb(topic, std::bind(&MqttReceiverFbImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); + subscriber->subscribe(topic, 1); + } + setComponentStatus(ComponentStatus::Ok); +} + +MqttReceiverFbImpl::~MqttReceiverFbImpl() +{ + for (const auto& topic : subscribedTopics) + { + subscriber->setMessageArrivedCb(topic, nullptr); + subscriber->unsubscribe(topic); + } +} +void MqttReceiverFbImpl::onSignalsMessage(const mqtt::IMqttSubscriber& subscriber, 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()); + } + } + readProperties(); +} + +void MqttReceiverFbImpl::propertyChanged(bool configure) +{ + readProperties(); + if (configure) + this->configure(); +} + +void MqttReceiverFbImpl::readProperties() +{ + auto lock = std::lock_guard(sync); + auto prop = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtr().toVector(); + for (const auto& item : prop) + { + auto signalId = item.asPtr(); + if (signalId.assigned()) + { + LOG_I("Signal in list: {}", signalId.toStdString()); + subscribedTopics.push_back(signalId.toStdString()); + } + } +} + +FunctionBlockTypePtr MqttReceiverFbImpl::CreateType() +{ + return FunctionBlockType("MqttFBModule", "MqttSubscriber", "MQTT signal receiving"); +} + +void MqttReceiverFbImpl::configure() +{ + +} + +void MqttReceiverFbImpl::createDataPacket(const std::string& topic, double value, UInt timestamp) +{ + 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); +} + +void MqttReceiverFbImpl::parseMessage(mqtt::MqttMessage& msg) +{ + std::string topic(msg.getTopic()); + std::string jsonObjStr(msg.getData().begin(), msg.getData().end()); + try { + rapidjson::Document jsonDocument; + jsonDocument.Parse(jsonObjStr); + if (jsonDocument.HasParseError()) { + LOG_E("Error parsing mqtt payload as JSON"); + return; + } + + if (jsonDocument.IsObject()) { + double val = 0.0; + UInt timestamp = 0; + 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()){ + val = jsonDocument[name].GetDouble(); + successCnt++; + } else { + LOG_W("Value is not supported."); + } + } else if (name == "timestamp") { + if (jsonDocument[name].IsInt() || jsonDocument[name].IsUint64() || jsonDocument[name].IsInt64()){ + timestamp = jsonDocument[name].GetUint64(); + successCnt++; + } else { + LOG_W("Value is not supported."); + } + } else { + LOG_W("Field \"{}\" is not supported.", name); + } + } + if (successCnt == 2) { + createDataPacket(topic, val, timestamp); + } else { + LOG_W("Not all required fields are present."); + } + } + } + catch (DeserializeException ex) { + LOG_E("Error deserializing mqtt payload"); + } +} + +void MqttReceiverFbImpl::createSignals() +{ + auto lock = std::lock_guard(sync); + for (const auto& topic : subscribedTopics) + { + LOG_I("Subscribing to topic: {}", topic); + std::string signalName = topic; + boost::replace_all(signalName, "/", "_"); + + auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::Float64).build(); + auto domainSignalDsc = DataDescriptorBuilder().setSampleType(SampleType::UInt64).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); + } +} + +std::string MqttReceiverFbImpl::buildSignalNameFromTopic(std::string topic) const +{ + boost::replace_all(topic, "/", "_"); + topic += "_Mqtt"; + return topic; +} + +std::string MqttReceiverFbImpl::buildDomainSignalNameFromTopic(std::string topic) const +{ + boost::replace_all(topic, "/", "_"); + topic += std::string("_Mqtt") + "_domain"; + return topic; +} + +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 new file mode 100644 index 00000000..bdaade0c --- /dev/null +++ b/mqtt_streaming_client_module/src/mqtt_streaming_client_module_impl.cpp @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + +static const std::regex RegexIpv6Hostname(R"(^(.+://)?(\[[a-fA-F0-9:]+(?:\%[a-zA-Z0-9_\.-~]+)?\])(?::(\d+))?(/.*)?$)"); +static const std::regex RegexIpv4Hostname(R"(^(.+://)?([^:/\s]+)(?::(\d+))?(/.*)?$)"); + +MqttStreamingClientModule::MqttStreamingClientModule(ContextPtr context) + : Module(MODULE_NAME, + daq::VersionInfo(MQTT_STREAM_CLI_MODULE_MAJOR_VERSION, + MQTT_STREAM_CLI_MODULE_MINOR_VERSION, + MQTT_STREAM_CLI_MODULE_PATCH_VERSION), + std::move(context), + MODULE_ID) +{ + loggerComponent = this->context.getLogger().getOrAddComponent(SHORT_MODULE_NAME); +} + +ListPtr MqttStreamingClientModule::onGetAvailableDevices() +{ + auto availableDevices = List(); + return availableDevices; +} + +DictPtr MqttStreamingClientModule::onGetAvailableDeviceTypes() +{ + auto result = Dict(); + + auto deviceType = createDeviceType(); + result.set(deviceType.getId(), deviceType); + return result; +} + +DevicePtr MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionString, + const ComponentPtr& parent, + const PropertyObjectPtr& config) +{ + if (!connectionString.assigned()) + DAQ_THROW_EXCEPTION(ArgumentNullException); + + PropertyObjectPtr configPtr = config; + if (!configPtr.assigned()) + configPtr = createDefaultConfig(); + else + configPtr = populateDefaultConfig(configPtr); + + if (!acceptsConnectionParameters(connectionString, configPtr)) + DAQ_THROW_EXCEPTION(InvalidParameterException); + + if (!context.assigned()) + DAQ_THROW_EXCEPTION(InvalidParameterException, "Context is not available."); + + std::string hostType; + extractConnectionParams(connectionString, configPtr, hostType); + + std::string host = configPtr.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS); + Int port = configPtr.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT); + + std::scoped_lock lock(sync); + + DevicePtr device(createWithImplementation( + context, + parent, + configPtr + ) + ); + + // Set the connection info for the device + ServerCapabilityConfigPtr connectionInfo = device.getInfo().getConfigurationConnectionInfo(); + + const auto addressInfo = AddressInfoBuilder().setAddress(host) + .setReachabilityStatus(AddressReachabilityStatus::Reachable) + .setType(hostType) + .setConnectionString(connectionString) + .build(); + + connectionInfo.setProtocolId(DaqMqttDeviceTypeId) + .setProtocolName(PROTOCOL_NAME) + .setProtocolType(ProtocolType::Streaming) + .setConnectionType(CONNECTION_TYPE) + .addAddress(host) + .setPort(port) + .setPrefix(DaqMqttDevicePrefix) + .setConnectionString(connectionString) + .addAddressInfo(addressInfo) + .freeze(); + + + return device; +} + +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; +} + +void MqttStreamingClientModule::extractConnectionParams(const StringPtr& connectionString, const PropertyObjectPtr& config, std::string& hostType) +{ + std::string urlString = connectionString.toStdString(); + std::smatch match; + + std::string prefix = ""; + std::string path = "/"; + std::string host; + int port = 0; + + bool parsed = false; + parsed = std::regex_search(urlString, match, RegexIpv6Hostname); + hostType = "IPv6"; + + if (!parsed) + { + parsed = std::regex_search(urlString, match, RegexIpv4Hostname); + hostType = "IPv4"; + } + + if (parsed) + { + prefix = match[1]; + host = match[2]; + + if (match[3].matched) + port = std::stoi(match[3]); + + if (match[4].matched) + path = match[4]; + } + else + DAQ_THROW_EXCEPTION(InvalidParameterException, "Host name not found in url: {}", connectionString); + + if (prefix != std::string(DaqMqttDevicePrefix) + "://") + DAQ_THROW_EXCEPTION(InvalidParameterException, "MQTT does not support connection string with prefix {}", prefix); + + if (!config.assigned()) + { + DAQ_THROW_EXCEPTION(InvalidParameterException, "Config is missing"); + } + if (port != 0 && config.hasProperty(PROPERTY_NAME_MQTT_BROKER_PORT)) + config.setPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT, Int(port)); + + if (config.hasProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS)) + config.setPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS, host); +} + +bool MqttStreamingClientModule::acceptsConnectionParameters(const StringPtr& connectionString, const PropertyObjectPtr& config) +{ + std::string connStr = connectionString; + auto found = connStr.find(std::string(DaqMqttDevicePrefix) + "://"); + return found == 0; +} + +Bool MqttStreamingClientModule::onCompleteServerCapability(const ServerCapabilityPtr& source, const ServerCapabilityConfigPtr& target) +{ + if (target.getProtocolId() != DaqMqttProtocolId) + return false; + + if (source.getConnectionType() != CONNECTION_TYPE) // ??? + return false; + + if (!source.getAddresses().assigned() || !source.getAddresses().getCount()) + { + LOG_W("Source server capability address is not available when filling in missing MQTT capability information.") + return false; + } + + const auto addrInfos = source.getAddressInfo(); + if (!addrInfos.assigned() || !addrInfos.getCount()) + { + LOG_W("Source server capability addressInfo is not available when filling in missing MQTT capability information.") + return false; + } + + auto port = target.getPort(); + if (port == -1) + { + port = DEFAULT_PORT; + target.setPort(port); + LOG_W("MQTT server capability is missing port. Defaulting to {}.", port) + } + + const auto path = target.hasProperty("Path") ? target.getPropertyValue("Path") : ""; + const auto targetAddress = target.getAddresses(); + for (const auto& addrInfo : addrInfos) + { + const auto address = addrInfo.getAddress(); + if (auto it = std::find(targetAddress.begin(), targetAddress.end(), address); it != targetAddress.end()) + continue; + + const auto prefix = target.getPrefix(); + StringPtr connectionString; + if (source.getPrefix() == prefix) + connectionString = addrInfo.getConnectionString(); + else + connectionString = fmt::format("{}://{}:{}{}", prefix, address, port, path); + + const auto targetAddrInfo = AddressInfoBuilder() + .setAddress(address) + .setReachabilityStatus(addrInfo.getReachabilityStatus()) + .setType(addrInfo.getType()) + .setConnectionString(connectionString) + .build(); + + target.addAddressInfo(targetAddrInfo) + .setConnectionString(connectionString) + .addAddress(address); + } + + return true; +} + +DeviceTypePtr MqttStreamingClientModule::createDeviceType() +{ + return DeviceTypeBuilder() + .setId(DaqMqttDeviceTypeId) + .setName("MQTT enabled device") + .setDescription("Network device connected over MQTT protocol") + .setConnectionStringPrefix(DaqMqttDevicePrefix) + .setDefaultConfig(createDefaultConfig()) + .build(); +} + +PropertyObjectPtr MqttStreamingClientModule::createDefaultConfig() +{ + auto config = PropertyObject(); + + config.addProperty(StringProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS, DEFAULT_BROKER_ADDRESS)); + 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)); + + return config; +} + +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 new file mode 100644 index 00000000..0a77ed92 --- /dev/null +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -0,0 +1,162 @@ +#include "mqtt_streaming_client_module/mqtt_receiver_fb_impl.h" +#include +#include "mqtt_streaming_client_module/constants.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE + + std::atomic MqttStreamingDeviceImpl::localIndex = 0; + +MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const PropertyObjectPtr& config) + : Device(ctx, parent, getLocalId()) + , connectionStatus(Enumeration("ConnectionStatusType", "Connected", this->context.getTypeManager())) + , subscriber(std::make_shared()) +{ + this->name = MQTT_DEVICE_NAME; + + connectionSettings.mqttUrl = config.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS).asPtr().toStdString(); + connectionSettings.port = config.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT); + connectionSettings.username = config.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME).asPtr().toStdString(); + connectionSettings.password = config.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD).asPtr().toStdString(); + connectionSettings.clientId = parent.getGlobalId().toStdString(); + + connectionString = std::string(DaqMqttDevicePrefix) + "://" + connectionSettings.mqttUrl + ":" + std::to_string(connectionSettings.port); + + int initTimeout = config.getPropertyValue(PROPERTY_NAME_INIT_DELAY); + + initComponentStatus(); + + connectedPromise = std::promise(); + connectedFuture = connectedPromise.get_future(); + + setupMqttSubscriber(); + if (connectedFuture.wait_for(std::chrono::milliseconds(initTimeout)) != std::future_status::ready || connectedFuture.get() == false) + { + 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_I("MQTT: Connection established"); + + 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(initTimeout)); // TODO : remove it! + + subscriber->unsubscribe(TOPIC_ALL_SIGNALS); + subscriber->setMessageArrivedCb(nullptr); +} + +void MqttStreamingDeviceImpl::removed() +{ + Device::removed(); +} + +DeviceInfoPtr MqttStreamingDeviceImpl::onGetInfo() +{ + return DeviceInfo(connectionString, MQTT_DEVICE_NAME); +} + +void MqttStreamingDeviceImpl::setupMqttSubscriber() +{ + subscriber->disconnect(); + subscriber->setServerURL(connectionSettings.mqttUrl); + subscriber->setClientId(connectionSettings.clientId); + subscriber->setUsernamePasswrod(connectionSettings.username, connectionSettings.password); + + subscriber->setOnConnect([this] { + connectedPromise.set_value(true); + }); + + LOG_I("MQTT: Trying to connect to MQTT broker ({})", connectionSettings.mqttUrl); + subscriber->connect(); +} + +void MqttStreamingDeviceImpl::onSignalsMessage(const mqtt::IMqttSubscriber& subscriber, mqtt::MqttMessage& msg) +{ + const std::string topic = msg.getTopic(); + 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; // not a signal list message + } + + std::string& deviceName = list[1]; + const std::string signalList(msg.getData().begin(), msg.getData().end()); + + rapidjson::Document doc; + + if (doc.Parse(signalList.c_str()).HasParseError() || !doc.IsArray()) { + LOG_W("{} - Signal list format is not correct: {}", topic, signalList); + return; + } + + const auto array = doc.GetArray(); + std::vector deviceSignals; + deviceSignals.reserve(array.Size()); + for (const auto& v : array) { + if (v.IsString()) { + deviceSignals.push_back(v.GetString()); + } + } + + deviceMap.insert({std::move(deviceName), std::move(deviceSignals)}); +} + +DictPtr MqttStreamingDeviceImpl::onGetAvailableFunctionBlockTypes() +{ + auto types = Dict(); + for (const auto& device : deviceMap) + { + auto defaultConfig = PropertyObject(); + auto signalList = List(); + for (const auto& signal : device.second) + signalList.pushBack(signal); + defaultConfig.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, signalList)); + + const auto fbType = FunctionBlockType(device.first, + device.first, + "", + defaultConfig); + + + types.set(fbType.getId(), fbType); + } + return types; +} + +FunctionBlockPtr MqttStreamingDeviceImpl::onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) +{ + FunctionBlockPtr nestedFunctionBlock; + { + auto lock = this->getAcquisitionLock(); + nestedFunctionBlock = createWithImplementation(context, functionBlocks, typeId, subscriber, config); + addNestedFunctionBlock(nestedFunctionBlock); + } + + setComponentStatus(ComponentStatus::Ok); + return nestedFunctionBlock; +} + +std::string MqttStreamingDeviceImpl::getLocalId() +{ + return std::string(MQTT_LOCAL_DEVICE_ID_PREFIX + std::to_string(localIndex++)); +} +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE diff --git a/mqtt_streaming_protocol/include/IMqttSubscriber.h b/mqtt_streaming_protocol/include/IMqttSubscriber.h index a2cfdbea..66aacbcd 100644 --- a/mqtt_streaming_protocol/include/IMqttSubscriber.h +++ b/mqtt_streaming_protocol/include/IMqttSubscriber.h @@ -11,12 +11,14 @@ class IMqttSubscriber : public virtual IMqttBase virtual bool subscribe(std::string topic, int qos) = 0; virtual bool unsubscribe(std::string topic) = 0; virtual bool unsubscribeAll() = 0; + virtual void setMessageArrivedCb(std::string topic, std::function cb) = 0; virtual void setMessageArrivedCb(std::function cb) = 0; virtual ~IMqttSubscriber() { } protected: - std::function cb; + std::unordered_map> cbs; + std::function commonCb; }; } // namespace mqtt diff --git a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h index f528c024..d6b4ea0f 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h +++ b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h @@ -129,7 +129,13 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber virtual void setMessageArrivedCb(std::function cb) override { std::lock_guard guard(mtx); - this->cb = cb; + this->commonCb = cb; + } + + virtual void setMessageArrivedCb(std::string topic, std::function cb) override + { + std::lock_guard guard(mtx); + this->cbs.insert({topic, cb}); } @@ -183,15 +189,19 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber static int msgArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) { MqttAsyncSubscriber* subscriber = (MqttAsyncSubscriber*) context; - if (subscriber->cb) + auto it = subscriber->cbs.find(topicName); + if (subscriber->commonCb || it != subscriber->cbs.end()) { mqtt::MqttMessage msg; // Get the topic msg.setTopic(topicName); // Copy the payload msg.addData((uint8_t*) message->payload, message->payloadlen); + if (subscriber->commonCb) + subscriber->commonCb(*subscriber, msg); + if(it != subscriber->cbs.end() && it->second) + it->second(*subscriber, msg); - subscriber->cb(*subscriber, msg); } MQTTAsync_freeMessage(&message); 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..8f688d86 100644 --- a/mqtt_streaming_protocol/src/CMakeLists.txt +++ b/mqtt_streaming_protocol/src/CMakeLists.txt @@ -12,6 +12,7 @@ set(SRC_PublicHeaders IMqttBase.h MqttAsyncSubscriber.h MQTTClientType.h MqttMessage.h + MqttSettings.h ) set(INCLUDE_DIR ../include) 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..cf548f3b 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 @@ -26,20 +26,11 @@ #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; 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..34ae4ab1 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -58,7 +58,7 @@ MqttStreamingServerImpl::MqttStreamingServerImpl(const DevicePtr& rootDevice, ServerCapabilityConfigPtr serverCapabilityStreaming = ServerCapability(SERVER_ID_AND_CAPABILITY, SERVER_ID_AND_CAPABILITY, ProtocolType::Streaming) - .setPrefix("daq.mqtts") + .setPrefix("daq.mqtt") .setConnectionType("TCP/IP") .setPort(connectionSettings.port); From 4e6e14a9bb1009a469de69942e145148713eca1d Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 22 Sep 2025 17:08:39 +0200 Subject: [PATCH 02/46] mqtt: changes to correctly create an MQTT FB with default config --- .../mqtt_receiver_fb_impl.h | 3 +-- .../mqtt_streaming_client_module_impl.h | 1 + .../mqtt_streaming_device_impl.h | 2 ++ .../src/mqtt_receiver_fb_impl.cpp | 25 +++++++++++++------ .../src/mqtt_streaming_client_module_impl.cpp | 5 ++-- .../src/mqtt_streaming_device_impl.cpp | 19 ++++++++------ 6 files changed, 36 insertions(+), 19 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 4d7af7ef..d04d63be 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 @@ -30,13 +30,12 @@ class MqttReceiverFbImpl final : public FunctionBlock public: explicit MqttReceiverFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, + const FunctionBlockTypePtr& type, const StringPtr& localId, std::shared_ptr subscriber, const PropertyObjectPtr& config = nullptr); ~MqttReceiverFbImpl() override; - static FunctionBlockTypePtr CreateType(); - private: std::unordered_map outputSignals; std::unordered_map outputDomainSignals; 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 301fe40d..afa6453a 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 @@ -41,6 +41,7 @@ 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/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 be288c58..96b163aa 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 @@ -49,6 +49,8 @@ class MqttStreamingDeviceImpl : public Device void setupMqttSubscriber(); void onSignalsMessage(const mqtt::IMqttSubscriber& subscriber, mqtt::MqttMessage& msg); + DictObjectPtr fbTypes; + StringPtr connectionString; EnumerationPtr connectionStatus; 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 92cd5744..557768fc 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -17,16 +17,19 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, + const FunctionBlockTypePtr& type, const StringPtr& localId, std::shared_ptr subscriber, const PropertyObjectPtr& config) - : FunctionBlock(CreateType(), ctx, parent, localId) + : FunctionBlock(type, ctx, parent, localId) , subscriber(subscriber) { initComponentStatus(); if (config.assigned()) initProperties(config); + else + initProperties(type.createDefaultConfig()); createSignals(); for (const auto& topic : subscribedTopics) @@ -86,11 +89,6 @@ void MqttReceiverFbImpl::readProperties() } } -FunctionBlockTypePtr MqttReceiverFbImpl::CreateType() -{ - return FunctionBlockType("MqttFBModule", "MqttSubscriber", "MQTT signal receiving"); -} - void MqttReceiverFbImpl::configure() { @@ -175,7 +173,20 @@ void MqttReceiverFbImpl::createSignals() boost::replace_all(signalName, "/", "_"); auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::Float64).build(); - auto domainSignalDsc = DataDescriptorBuilder().setSampleType(SampleType::UInt64).build(); + 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(topic, createAndAddSignal(buildSignalNameFromTopic(topic), signalDsc))).first; auto refSD = outputDomainSignals.emplace(std::make_pair(topic, createAndAddSignal(buildDomainSignalNameFromTopic(topic), domainSignalDsc, false))).first; 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 bdaade0c..acfa55a2 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 @@ -74,12 +74,11 @@ DevicePtr MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionS std::scoped_lock lock(sync); - DevicePtr device(createWithImplementation( + device = createWithImplementation( context, parent, configPtr - ) - ); + ); // Set the connection info for the device ServerCapabilityConfigPtr connectionInfo = device.getInfo().getConfigurationConnectionInfo(); 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 0a77ed92..dfafba4f 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 @@ void MqttStreamingDeviceImpl::onSignalsMessage(const mqtt::IMqttSubscriber& subs DictPtr MqttStreamingDeviceImpl::onGetAvailableFunctionBlockTypes() { - auto types = Dict(); + fbTypes = Dict(); for (const auto& device : deviceMap) { auto defaultConfig = PropertyObject(); @@ -137,9 +137,9 @@ DictPtr MqttStreamingDeviceImpl::onGetAvailableFunc defaultConfig); - types.set(fbType.getId(), fbType); + fbTypes.set(fbType.getId(), fbType); } - return types; + return fbTypes; } FunctionBlockPtr MqttStreamingDeviceImpl::onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) @@ -147,11 +147,16 @@ FunctionBlockPtr MqttStreamingDeviceImpl::onAddFunctionBlock(const StringPtr& ty FunctionBlockPtr nestedFunctionBlock; { auto lock = this->getAcquisitionLock(); - nestedFunctionBlock = createWithImplementation(context, functionBlocks, typeId, subscriber, config); - addNestedFunctionBlock(nestedFunctionBlock); + if (fbTypes.hasKey(typeId)) + { + auto fbTypePtr = fbTypes.getOrDefault(typeId); + nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, typeId, subscriber, config); + addNestedFunctionBlock(nestedFunctionBlock); + setComponentStatus(ComponentStatus::Ok); + } else { + setComponentStatusWithMessage(ComponentStatus::Error, "Function block type is not available: " + typeId.toStdString()); + } } - - setComponentStatus(ComponentStatus::Ok); return nestedFunctionBlock; } From 6130110f6738409c0b70ead19ac23023bd033fb8 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 22 Sep 2025 17:35:31 +0200 Subject: [PATCH 03/46] mqtt: ref-dev-mqtt-subscriber updating --- .../ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 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 6ccd8ab9..ac32f933 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 @@ -59,64 +59,47 @@ int main(int argc, char* argv[]) 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(); + const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); auto brokerDevice = instance.addDevice("daq.mqtt://127.0.0.1"); auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); + 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) { std::cout << "Available function block: " << key << std::endl; - fbList.push_back(brokerDevice.addFunctionBlock(key, value.createDefaultConfig())); + fbList.push_back(brokerDevice.addFunctionBlock(key)); } + std::cout << "Try to connect the first one (" << fbList[0].getLocalId() << ")" << std::endl; + + auto signals = fbList[0].getSignals(); std::vector readers; - auto signals = fbList.back().getSignals(); - for (const auto& s : signals) - { + for (const auto& s : signals) { readers.emplace_back(daq::StreamReader(s, ReadTimeoutType::Any)); } -#if 1 std::thread t1([readers]() { - uint64_t cnt = 0; constexpr int size = 1000; double samples[size]; uint64_t timestamps[size]; while (true) { - for (const auto& reader : readers) { - while (!reader.getEmpty()) - { + 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 " << cnt++ << " - Sample: " << samples[i] << " Timestamp: " << timestamps[i] << std::endl; + std::cout << "READER #" << iRdr << " - Sample: " << samples[i] << " Timestamp: " << timestamps[i] << std::endl; } } - std::this_thread::sleep_for(20ms); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); } }); t1.detach(); -#endif + std::cout << "Press \"enter\" to exit the application..." << std::endl; std::cin.get(); return 0; From 38b174d2a30a1728c60afefe0665769de2d4ca5f Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 22 Sep 2025 17:39:04 +0200 Subject: [PATCH 04/46] mqtt: openDAQ dependence updating --- external/opendaq/CMakeLists.txt | 2 +- mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/external/opendaq/CMakeLists.txt b/external/opendaq/CMakeLists.txt index a886ad1f..b9be8a02 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 git@github.com:openDAQ/openDAQ.git - GIT_TAG b38e835b591317fdd1f9f84aad2bd79f04c06fd3 + GIT_TAG v3.20.4 GIT_PROGRESS ON EXCLUDE_FROM_ALL SYSTEM 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 34ae4ab1..6ea51299 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -516,7 +516,6 @@ void MqttStreamingServerImpl::addReader(SignalPtr signalToRead) signals.pushBack(signalToRead); streamReaders.emplace_back(StreamReaderBuilder() .setSignal(signalToRead) - .setInputPortNotificationMethod(PacketReadyNotification::None) .setValueReadType(SampleType::Float64) .setDomainReadType(SampleType::Int64) .setSkipEvents(true) From e8dc4bdaf05493227ab0b499683f00180b4137d5 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 22 Sep 2025 21:21:24 +0200 Subject: [PATCH 05/46] mqtt: reworking of mutex locking in the MQTT wrapper --- .../include/IMqttSubscriber.h | 4 +- .../include/MqttAsyncSubscriber.h | 47 +++++++++---------- .../src/MqttAsyncSubscriber.cpp | 12 ++--- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/mqtt_streaming_protocol/include/IMqttSubscriber.h b/mqtt_streaming_protocol/include/IMqttSubscriber.h index 66aacbcd..9dd4eee0 100644 --- a/mqtt_streaming_protocol/include/IMqttSubscriber.h +++ b/mqtt_streaming_protocol/include/IMqttSubscriber.h @@ -1,7 +1,8 @@ #pragma once #include "IMqttBase.h" -#include #include "MqttMessage.h" +#include +#include namespace mqtt { @@ -13,6 +14,7 @@ class IMqttSubscriber : public virtual IMqttBase virtual bool unsubscribeAll() = 0; virtual void setMessageArrivedCb(std::string topic, std::function cb) = 0; virtual void setMessageArrivedCb(std::function cb) = 0; + virtual std::lock_guard getCbLock() = 0; virtual ~IMqttSubscriber() { } diff --git a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h index d6b4ea0f..3ad90de9 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h +++ b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h @@ -63,7 +63,6 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber ~MqttAsyncSubscriber() { - std::lock_guard guard(mtx); if (this->client != nullptr) { disconnect(); @@ -85,7 +84,6 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber virtual bool unsubscribeAll() override { - std::lock_guard guard(mtx); for (auto& sub : subscriptions) { unsubscribe(sub.topic); @@ -97,7 +95,6 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber 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; @@ -113,7 +110,6 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber virtual bool unsubscribe(std::string topic) override { - std::lock_guard guard(mtx); MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; int rc; opts.onSuccess = MqttAsyncSubscriber::onSubscriber; @@ -128,16 +124,20 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber virtual void setMessageArrivedCb(std::function cb) override { - std::lock_guard guard(mtx); + auto lock = getCbLock(); this->commonCb = cb; } virtual void setMessageArrivedCb(std::string topic, std::function cb) override { - std::lock_guard guard(mtx); + auto lock = getCbLock(); this->cbs.insert({topic, cb}); } + std::lock_guard getCbLock() override + { + return std::lock_guard(cbMtx); + } virtual bool reconnect() override { @@ -161,7 +161,7 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber std::string username; std::string password; std::vector subscriptions; - std::recursive_mutex mtx; + std::recursive_mutex cbMtx; bool penddingConnect; const MQTTClientType type = MQTTClientType::subscriber; @@ -178,6 +178,7 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber void writeConnectionStatusMsg(std::string who, std::string msg, int level) { + auto lock = getCbLock(); if (this->connCb) { this->connCb(who, msg, level, type); @@ -189,21 +190,23 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber static int msgArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) { MqttAsyncSubscriber* subscriber = (MqttAsyncSubscriber*) context; - auto it = subscriber->cbs.find(topicName); - if (subscriber->commonCb || it != subscriber->cbs.end()) { - mqtt::MqttMessage msg; - // Get the topic - msg.setTopic(topicName); - // Copy the payload - msg.addData((uint8_t*) message->payload, message->payloadlen); - if (subscriber->commonCb) - subscriber->commonCb(*subscriber, msg); - if(it != subscriber->cbs.end() && it->second) - it->second(*subscriber, msg); + auto lock = subscriber->getCbLock(); + auto it = subscriber->cbs.find(topicName); + if (subscriber->commonCb || it != subscriber->cbs.end()) + { + mqtt::MqttMessage msg; + // Get the topic + msg.setTopic(topicName); + // Copy the payload + msg.addData((uint8_t*) message->payload, message->payloadlen); + if (subscriber->commonCb) + subscriber->commonCb(*subscriber, msg); + if(it != subscriber->cbs.end() && it->second) + it->second(*subscriber, msg); + } } - MQTTAsync_freeMessage(&message); if (topicLen > 0) MQTTAsync_free(topicName); @@ -218,11 +221,7 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber { 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; + auto lock = subscriber->getCbLock(); if (subscriber->onConnectCb) subscriber->onConnectCb(); } diff --git a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp index 491c3f24..8feb14b2 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp @@ -8,7 +8,6 @@ namespace mqtt { bool MqttAsyncSubscriber::connect() { - std::lock_guard guard(mtx); if (this->client != nullptr) { // Signal stop reconnecting @@ -50,21 +49,20 @@ namespace mqtt { 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; + { + 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 == "") { @@ -114,13 +112,11 @@ void MqttAsyncSubscriber::setServerURL(std::string 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; From bf09af7bf3e8adb51481146c43130201ea24e1fc Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 23 Sep 2025 10:55:31 +0200 Subject: [PATCH 06/46] mqtt: MQTT wrapper refactoring --- .../include/MqttAsyncSubscriber.h | 263 ++------------- .../src/MqttAsyncSubscriber.cpp | 299 +++++++++++++----- 2 files changed, 251 insertions(+), 311 deletions(-) diff --git a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h index 3ad90de9..2254bfa9 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h +++ b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h @@ -4,11 +4,7 @@ #include "MQTTAsync.h" #include "MqttMessage.h" -#include "MQTTClientType.h" -#include -#include #include -#include namespace mqtt { @@ -16,9 +12,11 @@ namespace mqtt struct MqttSubscription { std::string topic; int qos; + + MqttSubscription(std::string t, int q) : topic(t), qos(q) {} }; -class MqttAsyncSubscriber : public virtual IMqttSubscriber +class MqttAsyncSubscriber final : public virtual IMqttSubscriber { public: MqttAsyncSubscriber(); @@ -31,249 +29,50 @@ class MqttAsyncSubscriber : public virtual IMqttSubscriber 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() - { - 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 - { - for (auto& sub : subscriptions) - { - unsubscribe(sub.topic); - } - subscriptions.clear(); - - return true; - } + std::string privKeyPass); - virtual bool subscribe(std::string topic, int qos) override - { - 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; - } + ~MqttAsyncSubscriber(); - virtual bool unsubscribe(std::string topic) override - { - 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; - } + bool connect() override; + bool disconnect() override; + bool reconnect() override; + MqttConnectionStatus isConnected() override; + void setServerURL(std::string serverUrl) override; + void setClientId(std::string clientId) override; + void setUsernamePasswrod(std::string username, std::string password) override; - virtual void setMessageArrivedCb(std::function cb) override - { - auto lock = getCbLock(); - this->commonCb = cb; - } + MqttClientType getClientType() const override; + std::string getServerUrl() const override; + std::lock_guard getCbLock() override; - virtual void setMessageArrivedCb(std::string topic, std::function cb) override - { - auto lock = getCbLock(); - this->cbs.insert({topic, cb}); - } + bool subscribe(std::string topic, int qos) override; + bool unsubscribe(std::string topic) override; + bool unsubscribeAll() override; - std::lock_guard getCbLock() override - { - return std::lock_guard(cbMtx); - } - - virtual bool reconnect() override - { - return true; - } - - std::string getClientId() const - { - return this->clientId; - } - - std::string getServerUrl() const override - { - return this->serverUrl; - } + void setMessageArrivedCb(std::function cb) override; + void setMessageArrivedCb(std::string topic, std::function cb) override; private: - std::atomic initialConnectionThreadStart = false; std::string serverUrl; std::string clientId; std::string username; std::string password; std::vector subscriptions; std::recursive_mutex cbMtx; - bool penddingConnect; - const MQTTClientType type = MQTTClientType::subscriber; + bool pendingConnect; 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) - { - auto lock = getCbLock(); - 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; - { - auto lock = subscriber->getCbLock(); - auto it = subscriber->cbs.find(topicName); - if (subscriber->commonCb || it != subscriber->cbs.end()) - { - mqtt::MqttMessage msg; - // Get the topic - msg.setTopic(topicName); - // Copy the payload - msg.addData((uint8_t*) message->payload, message->payloadlen); - if (subscriber->commonCb) - subscriber->commonCb(*subscriber, msg); - if(it != subscriber->cbs.end() && it->second) - it->second(*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); - auto lock = subscriber->getCbLock(); - 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) - { - } + MQTTAsync_SSLOptions sslOpts; + + static int msgArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message); + static void connectedCb(void* context, char* cause); + static void connlost(void* context, char* cause); + static void onSubscriber(void* context, MQTTAsync_successData* response); + static void onSubscriberFailure(void* context, MQTTAsync_failureData* response); + static void onConnectSuccess(void* context, MQTTAsync_successData data); + static void onConnectFailure(void* context, MQTTAsync_failureData data); + static void deliveryComplete(void* context, MQTTAsync_token token); }; } // namespace mqtt diff --git a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp index 8feb14b2..1a6e3abf 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp @@ -1,117 +1,141 @@ #include "MqttAsyncSubscriber.h" namespace mqtt { - MqttAsyncSubscriber::MqttAsyncSubscriber() : - MqttAsyncSubscriber("", "", false, false, false, false, "", "", "", "") - { - } - bool MqttAsyncSubscriber::connect() +MqttAsyncSubscriber::MqttAsyncSubscriber() + : MqttAsyncSubscriber("", "", false, false, false, false, "", "", "", "") +{} + +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) + , clientId(clientId) + , username("") + , password("") + , pendingConnect(false) + , client(nullptr) { - if (this->client != nullptr) - { + setServerURL(serverUrl); + connOpts = MQTTAsync_connectOptions_initializer; + connOpts.cleansession = cleanSession ? 1 : 0; + connOpts.keepAliveInterval = 20; + connOpts.connectTimeout = 5; + connOpts.onSuccess = (MQTTAsync_onSuccess *) &MqttAsyncSubscriber::onConnectSuccess; + connOpts.onFailure = (MQTTAsync_onFailure *) &MqttAsyncSubscriber::onConnectFailure; + connOpts.automaticReconnect = true; + connOpts.minRetryInterval = 1; + connOpts.maxRetryInterval = 10; + connOpts.ssl = &sslOpts; + connOpts.context = this; + createOpts = MQTTAsync_createOptions_initializer; + sslOpts = MQTTAsync_SSLOptions_initializer; +} + +MqttAsyncSubscriber::~MqttAsyncSubscriber() { + if (client != nullptr) { + disconnect(); + MQTTAsync_destroy(&client); + } +} + +bool MqttAsyncSubscriber::connect() { + if (client != nullptr) { // Signal stop reconnecting disconnect(); - MQTTAsync_destroy(&this->client); + MQTTAsync_destroy(&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); + pendingConnect = true; + int rc = MQTTAsync_createWithOptions(&client, + serverUrl.c_str(), + clientId.c_str(), + MQTTCLIENT_PERSISTENCE_NONE, + NULL, + &createOpts); - if (rc != MQTTASYNC_SUCCESS) - { + if (rc != MQTTASYNC_SUCCESS) { return false; } - rc = MQTTAsync_setCallbacks( - this->client, this, &MqttAsyncSubscriber::connlost, &MqttAsyncSubscriber::msgArrived, &MqttAsyncSubscriber::deliveryComplete); - if (rc != MQTTASYNC_SUCCESS) - { + rc = MQTTAsync_setCallbacks(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) - { + rc = MQTTAsync_setConnected(client, this, &MqttAsyncSubscriber::connectedCb); + if (rc != MQTTASYNC_SUCCESS) { return false; } - this->connOpts.context = this; - if ((rc = MQTTAsync_connect(this->client, &this->connOpts)) != MQTTASYNC_SUCCESS) - { + rc = MQTTAsync_connect(client, &connOpts); + if (rc != MQTTASYNC_SUCCESS) { return false; } return true; } -bool MqttAsyncSubscriber::disconnect() -{ - return MQTTAsync_disconnect(this->client, NULL) == MQTTASYNC_SUCCESS; +bool MqttAsyncSubscriber::disconnect() { + return MQTTAsync_disconnect(client, NULL) == MQTTASYNC_SUCCESS; } -MqttConnectionStatus MqttAsyncSubscriber::isConnected() -{ - { - if (this->penddingConnect) - return MqttConnectionStatus::pending; - } - return MQTTAsync_isConnected(this->client) ? MqttConnectionStatus::connected : MqttConnectionStatus::not_connected; -} +MqttConnectionStatus MqttAsyncSubscriber::isConnected() { + if (pendingConnect) + return MqttConnectionStatus::pending; -void MqttAsyncSubscriber::setServerURL(std::string serverUrl) -{ - 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()); - } + return MQTTAsync_isConnected(this->client) ? MqttConnectionStatus::connected + : MqttConnectionStatus::not_connected; +} - 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(); +void MqttAsyncSubscriber::setServerURL(std::string serverUrl) { + if (serverUrl[0] == ':') { + serverUrl = ""; + } else { + std::string ssl("ssl://"); + std::string tcp("tcp://"); + // Remove any protocol if available + if ((serverUrl.size() >= ssl.size() + && std::equal(ssl.begin(), ssl.end(), serverUrl.begin()))) { + serverUrl.erase(0, ssl.length()); } - else - { - this->ssl_opts.trustStore = nullptr; + if ((serverUrl.size() >= tcp.size() + && std::equal(tcp.begin(), tcp.end(), serverUrl.begin()))) { + serverUrl.erase(0, tcp.length()); } - 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; + + if (enableSSL) { + serverUrl = "ssl://" + serverUrl; + sslOpts.enableServerCertAuth = useCertificates && verifyServerCert + && trustStorePath != ""; + sslOpts.trustStore = (trustStorePath != "") ? trustStorePath.c_str() : nullptr; + sslOpts.keyStore = (useCertificates) ? clientCertPath.c_str() : nullptr; + sslOpts.privateKey = (useCertificates) ? privKeyPath.c_str() : nullptr; + } else { + serverUrl = "tcp://" + serverUrl; } } - else - { - this->serverUrl = "tcp://" + this->serverUrl; - } + this->serverUrl = serverUrl; } -void MqttAsyncSubscriber::setClientId(std::string clientId) -{ +void MqttAsyncSubscriber::setClientId(std::string clientId) { this->clientId = clientId; } @@ -123,4 +147,121 @@ void MqttAsyncSubscriber::setUsernamePasswrod(std::string username, std::string this->connOpts.username = !username.empty() ? this->username.c_str() : NULL; this->connOpts.password = !password.empty() ? this->password.c_str() : NULL; } + +MqttClientType MqttAsyncSubscriber::getClientType() const { + return MqttClientType::async; +} + +bool MqttAsyncSubscriber::unsubscribeAll() { + for (auto &sub : subscriptions) { + unsubscribe(sub.topic); + } + subscriptions.clear(); + + return true; +} + +bool MqttAsyncSubscriber::subscribe(std::string topic, int qos) { + 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) { + subscriptions.emplace_back(topic, qos); + } + return rc == MQTTASYNC_SUCCESS; +} + +bool MqttAsyncSubscriber::unsubscribe(std::string topic) { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + opts.onSuccess = MqttAsyncSubscriber::onSubscriber; + opts.onFailure = MqttAsyncSubscriber::onSubscriberFailure; + 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; +} + +void MqttAsyncSubscriber::setMessageArrivedCb( + std::function cb) +{ + auto lock = getCbLock(); + commonCb = cb; +} + +void MqttAsyncSubscriber::setMessageArrivedCb( + std::string topic, std::function cb) +{ + auto lock = getCbLock(); + cbs.insert({topic, cb}); +} + +std::lock_guard MqttAsyncSubscriber::getCbLock() { + return std::lock_guard(cbMtx); +} + +bool MqttAsyncSubscriber::reconnect() { return true; } + +std::string MqttAsyncSubscriber::getServerUrl() const { return serverUrl; } + +void MqttAsyncSubscriber::deliveryComplete(void *context, MQTTAsync_token token) {} + +int MqttAsyncSubscriber::msgArrived(void *context, + char *topicName, + int topicLen, + MQTTAsync_message *message) +{ + MqttAsyncSubscriber *subscriber = (MqttAsyncSubscriber *) context; + { + auto lock = subscriber->getCbLock(); + auto it = subscriber->cbs.find(topicName); + if (subscriber->commonCb || it != subscriber->cbs.end()) { + mqtt::MqttMessage msg; + msg.setTopic(topicName); + msg.addData((uint8_t *) message->payload, message->payloadlen); + if (subscriber->commonCb) + subscriber->commonCb(*subscriber, msg); + if (it != subscriber->cbs.end() && it->second) + it->second(*subscriber, msg); + } + } + MQTTAsync_freeMessage(&message); + if (topicLen > 0) + MQTTAsync_free(topicName); + return 1; +} + +void MqttAsyncSubscriber::connectedCb(void *context, char *cause) { + if (context != nullptr) { + auto subscriber = (MqttAsyncSubscriber *) context; + subscriber->pendingConnect = false; + auto lock = subscriber->getCbLock(); + if (subscriber->onConnectCb) + subscriber->onConnectCb(); + } +} +void MqttAsyncSubscriber::connlost(void *context, char *cause) { + // Reconnect procedure here! } + +void MqttAsyncSubscriber::onSubscriber(void *context, MQTTAsync_successData *response) {} + +void MqttAsyncSubscriber::onSubscriberFailure(void *context, MQTTAsync_failureData *response) {} + +void MqttAsyncSubscriber::onConnectSuccess(void *context, MQTTAsync_successData data) +{ + // This callback only fires onec when call to MQTTAsync_connect is called. +} + +void MqttAsyncSubscriber::onConnectFailure(void *context, MQTTAsync_failureData data) +{ + auto subscriber = (MqttAsyncSubscriber *) context; + subscriber->pendingConnect = false; +} + +} // namespace mqtt From eec9798b2dd670af8422aa12bd5cdc0d90146542 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 23 Sep 2025 12:23:14 +0200 Subject: [PATCH 07/46] module versions = openDAQ version --- examples/ref-dev-mqtt-sub/src/CMakeLists.txt | 3 +-- external/opendaq/CMakeLists.txt | 26 ++++++++++++++++++++ mqtt_streaming_client_module/CMakeLists.txt | 9 +++++-- mqtt_streaming_protocol/CMakeLists.txt | 12 +++++---- mqtt_streaming_server_module/CMakeLists.txt | 9 +++++-- 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/examples/ref-dev-mqtt-sub/src/CMakeLists.txt b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt index f70b529f..1fe267a6 100644 --- a/examples/ref-dev-mqtt-sub/src/CMakeLists.txt +++ b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt @@ -3,5 +3,4 @@ cmake_minimum_required(VERSION 3.16) add_compile_definitions(MODULE_PATH="${OPENDAQ_MODULES_DIR}") add_executable(${EXAMPLE_PROJECT_NAME} ref-dev-mqtt-sub.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 +target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq) diff --git a/external/opendaq/CMakeLists.txt b/external/opendaq/CMakeLists.txt index b9be8a02..798a6dd2 100644 --- a/external/opendaq/CMakeLists.txt +++ b/external/opendaq/CMakeLists.txt @@ -10,3 +10,29 @@ FetchContent_Declare( ) 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_client_module/CMakeLists.txt b/mqtt_streaming_client_module/CMakeLists.txt index 2c476a49..08c42590 100644 --- a/mqtt_streaming_client_module/CMakeLists.txt +++ b/mqtt_streaming_client_module/CMakeLists.txt @@ -1,5 +1,10 @@ cmake_minimum_required(VERSION 3.10) set_cmake_folder_context(TARGET_FOLDER_NAME) -project(MqttClientModule VERSION 3.4.0 LANGUAGES C CXX) -add_subdirectory(src) \ No newline at end of file +set(MQTT_CLIENT_MODULE_VERSION ${OPENDAQ_PACKAGE_VERSION}) +set(MQTT_CLIENT_MODULE_PRJ_NAME "OpenDaqMqttClientModule") + +message(STATUS "${MQTT_CLIENT_MODULE_PRJ_NAME} version: ${MQTT_CLIENT_MODULE_VERSION}") +project(${MQTT_CLIENT_MODULE_PRJ_NAME} VERSION ${MQTT_CLIENT_MODULE_VERSION} LANGUAGES C CXX) + +add_subdirectory(src) 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_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) From ff6877521bec486d8d5eeaf54db18dd258c3bad5 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 23 Sep 2025 15:41:22 +0200 Subject: [PATCH 08/46] mqtt client: mqtt clientId = device globalId --- mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 dfafba4f..10350870 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -30,7 +30,7 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, connectionSettings.port = config.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT); connectionSettings.username = config.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME).asPtr().toStdString(); connectionSettings.password = config.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD).asPtr().toStdString(); - connectionSettings.clientId = parent.getGlobalId().toStdString(); + connectionSettings.clientId = globalId.toStdString(); connectionString = std::string(DaqMqttDevicePrefix) + "://" + connectionSettings.mqttUrl + ":" + std::to_string(connectionSettings.port); From 8146a2740f42dcfabfdc888da9068aafd50a3f9d Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 23 Sep 2025 15:44:00 +0200 Subject: [PATCH 09/46] mqtt client: tests --- mqtt_streaming_client_module/CMakeLists.txt | 1 + .../tests/CMakeLists.txt | 18 +++ .../tests/test_app.cpp | 21 +++ .../test_mqtt_streaming_client_module.cpp | 143 ++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 mqtt_streaming_client_module/tests/CMakeLists.txt create mode 100644 mqtt_streaming_client_module/tests/test_app.cpp create mode 100644 mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp diff --git a/mqtt_streaming_client_module/CMakeLists.txt b/mqtt_streaming_client_module/CMakeLists.txt index 08c42590..571e429e 100644 --- a/mqtt_streaming_client_module/CMakeLists.txt +++ b/mqtt_streaming_client_module/CMakeLists.txt @@ -8,3 +8,4 @@ message(STATUS "${MQTT_CLIENT_MODULE_PRJ_NAME} version: ${MQTT_CLIENT_MODULE_VER project(${MQTT_CLIENT_MODULE_PRJ_NAME} VERSION ${MQTT_CLIENT_MODULE_VERSION} LANGUAGES C CXX) add_subdirectory(src) +add_subdirectory(tests) diff --git a/mqtt_streaming_client_module/tests/CMakeLists.txt b/mqtt_streaming_client_module/tests/CMakeLists.txt new file mode 100644 index 00000000..20e1f6f4 --- /dev/null +++ b/mqtt_streaming_client_module/tests/CMakeLists.txt @@ -0,0 +1,18 @@ +set(MODULE_NAME mqtt_stream_cli_module) +set(TEST_APP test_${MODULE_NAME}) + +set(TEST_SOURCES test_mqtt_streaming_client_module.cpp + test_app.cpp +) + +add_executable(${TEST_APP} ${TEST_SOURCES} +) + +target_link_libraries(${TEST_APP} PRIVATE daq::test_utils + ${MODULE_NAME} +) + +add_test(NAME ${TEST_APP} + COMMAND $ + WORKING_DIRECTORY $ +) diff --git a/mqtt_streaming_client_module/tests/test_app.cpp b/mqtt_streaming_client_module/tests/test_app.cpp new file mode 100644 index 00000000..d92f41f2 --- /dev/null +++ b/mqtt_streaming_client_module/tests/test_app.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include +#include +#include + +int main(int argc, char** args) +{ + daq::daqInitializeCoreObjectsTesting(); + daqInitModuleManagerLibrary(); + + testing::InitGoogleTest(&argc, args); + + testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new DaqMemCheckListener()); + + auto res = RUN_ALL_TESTS(); + + return res; +} 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 new file mode 100644 index 00000000..5a67f568 --- /dev/null +++ b/mqtt_streaming_client_module/tests/test_mqtt_streaming_client_module.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +using MqttStreamingClientModuleTest = testing::Test; +using namespace daq; +using namespace daq::modules::mqtt_streaming_client_module; + +static ModulePtr CreateModule() +{ + ModulePtr module; + createModule(&module, NullContext()); + return module; +} + +TEST_F(MqttStreamingClientModuleTest, CreateModule) +{ + IModule* module = nullptr; + ErrCode errCode = createModule(&module, NullContext()); + ASSERT_TRUE(OPENDAQ_SUCCEEDED(errCode)); + + ASSERT_NE(module, nullptr); + module->releaseRef(); +} + +TEST_F(MqttStreamingClientModuleTest, ModuleName) +{ + auto module = CreateModule(); + ASSERT_EQ(module.getModuleInfo().getName(), daq::modules::mqtt_streaming_client_module::MODULE_NAME); +} + +TEST_F(MqttStreamingClientModuleTest, VersionAvailable) +{ + auto module = CreateModule(); + ASSERT_TRUE(module.getModuleInfo().getVersionInfo().assigned()); +} + +TEST_F(MqttStreamingClientModuleTest, VersionCorrect) +{ + auto module = CreateModule(); + auto version = module.getModuleInfo().getVersionInfo(); + + ASSERT_EQ(version.getMajor(), MQTT_STREAM_CLI_MODULE_MAJOR_VERSION); + ASSERT_EQ(version.getMinor(), MQTT_STREAM_CLI_MODULE_MINOR_VERSION); + ASSERT_EQ(version.getPatch(), MQTT_STREAM_CLI_MODULE_PATCH_VERSION); +} + +TEST_F(MqttStreamingClientModuleTest, EnumerateDevices) +{ + auto module = CreateModule(); + + ListPtr deviceInfo; + ASSERT_NO_THROW(deviceInfo = module.getAvailableDevices()); +} + +TEST_F(MqttStreamingClientModuleTest, CreateDeviceConnectionStringNull) +{ + auto module = CreateModule(); + + DevicePtr device; + 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(); + + DictPtr functionBlockTypes; + ASSERT_NO_THROW(functionBlockTypes = module.getAvailableFunctionBlockTypes()); + ASSERT_EQ(functionBlockTypes.getCount(), 0u); + + DictPtr deviceTypes; + ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); + ASSERT_EQ(deviceTypes.getCount(), 1u); + ASSERT_TRUE(deviceTypes.hasKey(DaqMqttDeviceTypeId)); + ASSERT_EQ(deviceTypes.get(DaqMqttDeviceTypeId).getId(), DaqMqttDeviceTypeId); + + DictPtr serverTypes; + ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); + ASSERT_EQ(serverTypes.getCount(), 0u); + + // Check module info for module + ModuleInfoPtr moduleInfo; + ASSERT_NO_THROW(moduleInfo = module.getModuleInfo()); + ASSERT_NE(moduleInfo, nullptr); + ASSERT_EQ(moduleInfo.getName(), MODULE_NAME); + ASSERT_EQ(moduleInfo.getId(), MODULE_ID); + + // Check version info for module + VersionInfoPtr versionInfoModule; + ASSERT_NO_THROW(versionInfoModule = moduleInfo.getVersionInfo()); + ASSERT_NE(versionInfoModule, nullptr); + ASSERT_EQ(versionInfoModule.getMajor(), MQTT_STREAM_CLI_MODULE_MAJOR_VERSION); + ASSERT_EQ(versionInfoModule.getMinor(), MQTT_STREAM_CLI_MODULE_MINOR_VERSION); + ASSERT_EQ(versionInfoModule.getPatch(), MQTT_STREAM_CLI_MODULE_PATCH_VERSION); + + // Check module and version info for device types + for (const auto& deviceType : deviceTypes) + { + ModuleInfoPtr moduleInfoDeviceType; + ASSERT_NO_THROW(moduleInfoDeviceType = deviceType.second.getModuleInfo()); + ASSERT_NE(moduleInfoDeviceType, nullptr); + ASSERT_EQ(moduleInfoDeviceType.getName(), MODULE_NAME); + ASSERT_EQ(moduleInfoDeviceType.getId(), MODULE_ID); + + VersionInfoPtr versionInfoDeviceType; + ASSERT_NO_THROW(versionInfoDeviceType = moduleInfoDeviceType.getVersionInfo()); + ASSERT_NE(versionInfoDeviceType, nullptr); + ASSERT_EQ(versionInfoDeviceType.getMajor(), MQTT_STREAM_CLI_MODULE_MAJOR_VERSION); + ASSERT_EQ(versionInfoDeviceType.getMinor(), MQTT_STREAM_CLI_MODULE_MINOR_VERSION); + 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 pseudoDeviceConfig = deviceTypes.get(DaqMqttDeviceTypeId).createDefaultConfig(); + ASSERT_TRUE(pseudoDeviceConfig.assigned()); +} From 0b63a115a2620182fa450d3dccd14b32e03bb525 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 23 Sep 2025 17:22:40 +0200 Subject: [PATCH 10/46] mqtt server: moving constants to a separate file; removins unused properties; removing useless code --- .../ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp | 2 +- .../mqtt_streaming_server_module/constants.h | 31 ++++++++ .../mqtt_streaming_server_impl.h | 1 - .../src/CMakeLists.txt | 5 ++ .../src/mqtt_streaming_server_impl.cpp | 78 +++++++------------ .../src/mqtt_streaming_server_module_impl.cpp | 5 +- 6 files changed, 68 insertions(+), 54 deletions(-) create mode 100644 mqtt_streaming_server_module/include/mqtt_streaming_server_module/constants.h 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 3ee4ca13..421cfe4f 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 @@ -85,7 +85,7 @@ int main(int argc, char* argv[]) refDevice.setPropertyValue("EnableProtectedChannel", true); auto serverConfig = instance.getAvailableServerTypes().get("OpenDAQMQTT").createDefaultConfig(); - serverConfig.setPropertyValue("BrokerAddress", "127.0.0.1"); + serverConfig.setPropertyValue("MqttBrokerAddress", "127.0.0.1"); const auto mqttServer = instance.addServer("OpenDAQMQTT", serverConfig); std::cout << "Press \"enter\" to exit the application..." << std::endl; 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 cf548f3b..4d8ed5e1 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 @@ -48,7 +48,6 @@ 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(); diff --git a/mqtt_streaming_server_module/src/CMakeLists.txt b/mqtt_streaming_server_module/src/CMakeLists.txt index ad1d9cd4..8d82e5d7 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,6 +20,10 @@ 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 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 6ea51299..0e749207 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -19,26 +19,14 @@ #include #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" +#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()) @@ -46,29 +34,35 @@ 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)); + 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(MQTT_PREFIX) + .setConnectionType(CONNECTION_TYPE) + .setPort(connectionSettings.port); - ServerCapabilityConfigPtr serverCapabilityStreaming = - ServerCapability(SERVER_ID_AND_CAPABILITY, SERVER_ID_AND_CAPABILITY, ProtocolType::Streaming) - .setPrefix("daq.mqtt") - .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); @@ -236,12 +230,12 @@ std::string MqttStreamingServerImpl::prepareJsonTopics() std::string MqttStreamingServerImpl::buildTopicFromId(const std::string& globalId) { - return ("openDAQ" + globalId); + return (TOPIC_ALL_SIGNALS_PREFIX + globalId); } std::string MqttStreamingServerImpl::buildSignalsTopic() { - return ("openDAQ" + rootDeviceGlobalId + "/$signals"); + return (TOPIC_ALL_SIGNALS_PREFIX + rootDeviceGlobalId + "/" + DEVICE_SIGNAL_LIST); } void MqttStreamingServerImpl::sendTopicList() @@ -260,7 +254,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); @@ -417,7 +411,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 " @@ -426,7 +420,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 " @@ -436,7 +430,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); @@ -458,11 +452,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; } @@ -480,17 +469,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( 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 1df9bb791c428bd477f51233034f3adf1c14e4b4 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 23 Sep 2025 17:24:37 +0200 Subject: [PATCH 11/46] mqtt server: tests --- mqtt_streaming_server_module/CMakeLists.txt | 1 + .../tests/CMakeLists.txt | 22 +++ .../tests/test_app.cpp | 22 +++ .../test_mqtt_streaming_server_module.cpp | 177 ++++++++++++++++++ 4 files changed, 222 insertions(+) create mode 100644 mqtt_streaming_server_module/tests/CMakeLists.txt create mode 100644 mqtt_streaming_server_module/tests/test_app.cpp create mode 100644 mqtt_streaming_server_module/tests/test_mqtt_streaming_server_module.cpp diff --git a/mqtt_streaming_server_module/CMakeLists.txt b/mqtt_streaming_server_module/CMakeLists.txt index ecae0cea..fc5b2b2c 100644 --- a/mqtt_streaming_server_module/CMakeLists.txt +++ b/mqtt_streaming_server_module/CMakeLists.txt @@ -8,3 +8,4 @@ message(STATUS "${MQTT_SERVER_MODULE_PRJ_NAME} version: ${MQTT_SERVER_MODULE_VER project(${MQTT_SERVER_MODULE_PRJ_NAME} VERSION ${MQTT_SERVER_MODULE_VERSION} LANGUAGES C CXX) add_subdirectory(src) +add_subdirectory(tests) diff --git a/mqtt_streaming_server_module/tests/CMakeLists.txt b/mqtt_streaming_server_module/tests/CMakeLists.txt new file mode 100644 index 00000000..9e7d19e2 --- /dev/null +++ b/mqtt_streaming_server_module/tests/CMakeLists.txt @@ -0,0 +1,22 @@ +set(MODULE_NAME mqtt_stream_srv_module) +set(TEST_APP test_${MODULE_NAME}) + +set(TEST_SOURCES test_mqtt_streaming_server_module.cpp + test_app.cpp +) + +add_executable(${TEST_APP} ${TEST_SOURCES} +) + +target_link_libraries(${TEST_APP} PRIVATE daq::test_utils + ${MODULE_NAME} +) + +add_test(NAME ${TEST_APP} + COMMAND $ + WORKING_DIRECTORY $ +) + +if (MSVC) # Ignoring warning for the Taskflow + target_compile_options(${TEST_APP} PRIVATE /wd4324) +endif() diff --git a/mqtt_streaming_server_module/tests/test_app.cpp b/mqtt_streaming_server_module/tests/test_app.cpp new file mode 100644 index 00000000..64dd0cc6 --- /dev/null +++ b/mqtt_streaming_server_module/tests/test_app.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include +#include + +int main(int argc, char** args) +{ + daq::daqInitializeCoreObjectsTesting(); + daqInitModuleManagerLibrary(); + daqInitOpenDaqLibrary(); + + testing::InitGoogleTest(&argc, args); + + testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new DaqMemCheckListener()); + + auto res = RUN_ALL_TESTS(); + + return res; +} diff --git a/mqtt_streaming_server_module/tests/test_mqtt_streaming_server_module.cpp b/mqtt_streaming_server_module/tests/test_mqtt_streaming_server_module.cpp new file mode 100644 index 00000000..b63c2e03 --- /dev/null +++ b/mqtt_streaming_server_module/tests/test_mqtt_streaming_server_module.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using MqttStreamingServerModuleTest = testing::Test; +using namespace daq; +using namespace daq::modules::mqtt_streaming_server_module; + +static ModulePtr CreateModule(ContextPtr context = NullContext(), ModuleManagerPtr manager = nullptr) +{ + ModulePtr module; + createMqttStreamingServerModule(&module, context); + return module; +} + +static InstancePtr CreateTestInstance() +{ + const auto logger = Logger(); + const auto moduleManager = ModuleManager("[[none]]"); + const auto authenticationProvider = AuthenticationProvider(); + const auto context = Context(Scheduler(logger), logger, TypeManager(), moduleManager, authenticationProvider); + + const ModulePtr daqMqttStreamingServerModule = CreateModule(context, moduleManager); + moduleManager.addModule(daqMqttStreamingServerModule); + + auto instance = InstanceCustom(context, "localInstance"); + for (const auto& deviceInfo : instance.getAvailableDevices()) + instance.addDevice(deviceInfo.getConnectionString()); + + for (const auto& [id, _] : instance.getAvailableFunctionBlockTypes()) + instance.addFunctionBlock(id); + + return instance; +} + +static PropertyObjectPtr CreateServerConfig(const InstancePtr& instance) +{ + auto config = instance.getAvailableServerTypes().get(SERVER_ID_AND_CAPABILITY).createDefaultConfig(); + return config; +} + +TEST_F(MqttStreamingServerModuleTest, CreateModule) +{ + IModule* module = nullptr; + ErrCode errCode = createModule(&module, NullContext()); + ASSERT_TRUE(OPENDAQ_SUCCEEDED(errCode)); + + ASSERT_NE(module, nullptr); + module->releaseRef(); +} + +TEST_F(MqttStreamingServerModuleTest, ModuleName) +{ + auto module = CreateModule(); + ASSERT_EQ(module.getModuleInfo().getName(), MODULE_NAME); +} + +TEST_F(MqttStreamingServerModuleTest, VersionAvailable) +{ + auto module = CreateModule(); + ASSERT_TRUE(module.getModuleInfo().getVersionInfo().assigned()); +} + +TEST_F(MqttStreamingServerModuleTest, VersionCorrect) +{ + auto module = CreateModule(); + auto version = module.getModuleInfo().getVersionInfo(); + + ASSERT_EQ(version.getMajor(), MQTT_STREAM_SRV_MODULE_MAJOR_VERSION); + ASSERT_EQ(version.getMinor(), MQTT_STREAM_SRV_MODULE_MINOR_VERSION); + ASSERT_EQ(version.getPatch(), MQTT_STREAM_SRV_MODULE_PATCH_VERSION); +} + +TEST_F(MqttStreamingServerModuleTest, GetAvailableComponentTypes) +{ + const auto module = CreateModule(); + + DictPtr functionBlockTypes; + ASSERT_NO_THROW(functionBlockTypes = module.getAvailableFunctionBlockTypes()); + ASSERT_EQ(functionBlockTypes.getCount(), 0u); + + DictPtr deviceTypes; + ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); + ASSERT_EQ(deviceTypes.getCount(), 0u); + + DictPtr serverTypes; + ASSERT_NO_THROW(serverTypes = module.getAvailableServerTypes()); + ASSERT_EQ(serverTypes.getCount(), 1u); + ASSERT_TRUE(serverTypes.hasKey(SERVER_ID_AND_CAPABILITY)); + ASSERT_EQ(serverTypes.get(SERVER_ID_AND_CAPABILITY).getId(), SERVER_ID_AND_CAPABILITY); + + // Check module info for module + ModuleInfoPtr moduleInfo; + ASSERT_NO_THROW(moduleInfo = module.getModuleInfo()); + ASSERT_NE(moduleInfo, nullptr); + ASSERT_EQ(moduleInfo.getName(), MODULE_NAME); + ASSERT_EQ(moduleInfo.getId(), MODULE_ID); + + // Check version info for module + VersionInfoPtr versionInfoModule; + ASSERT_NO_THROW(versionInfoModule = moduleInfo.getVersionInfo()); + ASSERT_NE(versionInfoModule, nullptr); + ASSERT_EQ(versionInfoModule.getMajor(), MQTT_STREAM_SRV_MODULE_MAJOR_VERSION); + ASSERT_EQ(versionInfoModule.getMinor(), MQTT_STREAM_SRV_MODULE_MINOR_VERSION); + ASSERT_EQ(versionInfoModule.getPatch(), MQTT_STREAM_SRV_MODULE_PATCH_VERSION); + + // Check module version info for server types + for (const auto& serverType : serverTypes) + { + ModuleInfoPtr moduleInfoServerType; + ASSERT_NO_THROW(moduleInfoServerType = serverType.second.getModuleInfo()); + ASSERT_NE(moduleInfoServerType, nullptr); + ASSERT_EQ(moduleInfoServerType.getName(), MODULE_NAME); + ASSERT_EQ(moduleInfoServerType.getId(), MODULE_ID); + + VersionInfoPtr versionInfoServerType; + ASSERT_NO_THROW(versionInfoServerType = moduleInfoServerType.getVersionInfo()); + ASSERT_NE(versionInfoServerType, nullptr); + ASSERT_EQ(versionInfoServerType.getMajor(), MQTT_STREAM_SRV_MODULE_MAJOR_VERSION); + ASSERT_EQ(versionInfoServerType.getMinor(), MQTT_STREAM_SRV_MODULE_MINOR_VERSION); + ASSERT_EQ(versionInfoServerType.getPatch(), MQTT_STREAM_SRV_MODULE_PATCH_VERSION); + } +} + +TEST_F(MqttStreamingServerModuleTest, ServerConfig) +{ + auto module = CreateModule(); + + DictPtr serverTypes = module.getAvailableServerTypes(); + ASSERT_TRUE(serverTypes.hasKey(SERVER_ID_AND_CAPABILITY)); + auto config = serverTypes.get(SERVER_ID_AND_CAPABILITY).createDefaultConfig(); + ASSERT_TRUE(config.assigned()); + + ASSERT_TRUE(config.hasProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS)); + ASSERT_EQ(config.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS), DEFAULT_BROKER_ADDRESS); + + ASSERT_TRUE(config.hasProperty(PROPERTY_NAME_MQTT_BROKER_PORT)); + ASSERT_EQ(config.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT), DEFAULT_PORT); + + ASSERT_TRUE(config.hasProperty(PROPERTY_NAME_MQTT_USERNAME)); + ASSERT_EQ(config.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME), DEFAULT_USERNAME); + + ASSERT_TRUE(config.hasProperty(PROPERTY_NAME_MQTT_PASSWORD)); + ASSERT_EQ(config.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD), DEFAULT_PASSWORD); + + ASSERT_TRUE(config.hasProperty(PROPERTY_NAME_MAX_PACKET_READ_COUNT)); + ASSERT_EQ(config.getPropertyValue(PROPERTY_NAME_MAX_PACKET_READ_COUNT), DEFAULT_MAX_PACKET_READ_COUNT); + + ASSERT_TRUE(config.hasProperty(PROPERTY_NAME_POLLING_PERIOD)); + ASSERT_EQ(config.getPropertyValue(PROPERTY_NAME_POLLING_PERIOD), DEFAULT_POLLING_PERIOD); +} + +TEST_F(MqttStreamingServerModuleTest, CreateServer) +{ + auto device = CreateTestInstance(); + auto module = CreateModule(device.getContext()); + auto config = CreateServerConfig(device); + + ASSERT_NO_THROW(module.createServer(SERVER_ID_AND_CAPABILITY, device.getRootDevice(), config)); +} + +TEST_F(MqttStreamingServerModuleTest, CreateServerFromInstance) +{ + auto device = CreateTestInstance(); + auto config = CreateServerConfig(device); + + ASSERT_NO_THROW(device.addServer(SERVER_ID_AND_CAPABILITY, config)); +} From 79b2316aea68d75db693a43ab0db84facbc8b7a5 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 24 Sep 2025 15:57:03 +0200 Subject: [PATCH 12/46] mqtt: MQTT wrapper refactoring --- .../src/mqtt_streaming_device_impl.cpp | 2 +- mqtt_streaming_protocol/include/IMqttBase.h | 29 +- .../include/IMqttPublisher.h | 16 +- .../include/IMqttSubscriber.h | 10 +- .../include/MQTTClientType.h | 7 - .../include/MqttAsyncPublisher.h | 161 ++------- .../include/MqttAsyncSubscriber.h | 16 +- mqtt_streaming_protocol/src/CMakeLists.txt | 1 - .../src/MqttAsyncPublisher.cpp | 309 ++++++++++-------- .../src/MqttAsyncSubscriber.cpp | 59 ++-- .../src/mqtt_streaming_server_impl.cpp | 2 +- 11 files changed, 261 insertions(+), 351 deletions(-) delete mode 100644 mqtt_streaming_protocol/include/MQTTClientType.h 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 10350870..8751490a 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -78,7 +78,7 @@ void MqttStreamingDeviceImpl::setupMqttSubscriber() subscriber->setClientId(connectionSettings.clientId); subscriber->setUsernamePasswrod(connectionSettings.username, connectionSettings.password); - subscriber->setOnConnect([this] { + subscriber->setOnConnected([this] { connectedPromise.set_value(true); }); diff --git a/mqtt_streaming_protocol/include/IMqttBase.h b/mqtt_streaming_protocol/include/IMqttBase.h index 14254fee..b222a0bb 100644 --- a/mqtt_streaming_protocol/include/IMqttBase.h +++ b/mqtt_streaming_protocol/include/IMqttBase.h @@ -1,7 +1,8 @@ #pragma once -#include + #include -#include "MQTTClientType.h" +#include +#include enum class MqttClientType { @@ -41,11 +42,10 @@ class IMqttBase 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; + + void setOnConnected(std::function cb) { + auto lock = getCbLock(); + onConnectedCb = cb; } virtual void setUsernamePasswrod(std::string username, std::string password) = 0; virtual void setServerURL(std::string serverUrl) = 0; @@ -69,9 +69,8 @@ class IMqttBase } virtual MqttConnectionStatus isConnected() = 0; virtual MqttClientType getClientType() const = 0; - virtual ~IMqttBase() - { - } + + virtual ~IMqttBase() = default; protected: bool enableSSL; @@ -81,7 +80,13 @@ class IMqttBase std::string clientCertPath; std::string privKeyPath; std::string privKeyPass; - std::function connCb; - std::function onConnectCb; + + std::recursive_mutex cbMtx; + + std::function onConnectedCb; + + std::lock_guard getCbLock() { + return std::lock_guard(cbMtx); + } }; } // namespace mqtt diff --git a/mqtt_streaming_protocol/include/IMqttPublisher.h b/mqtt_streaming_protocol/include/IMqttPublisher.h index 0559c2f0..94840284 100644 --- a/mqtt_streaming_protocol/include/IMqttPublisher.h +++ b/mqtt_streaming_protocol/include/IMqttPublisher.h @@ -9,24 +9,22 @@ 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; + auto lock = getCbLock(); + onSentSuccessCb = cb; } virtual void setPublishFailCB(std::function cb) { - this->sentFailCb = cb; + auto lock = getCbLock(); + onSentFailCb = cb; } - virtual ~IMqttPublisher() - { - } + virtual ~IMqttPublisher() = default; protected: - std::function cb; - std::function sentSuccessCb; - std::function sentFailCb; + std::function onSentSuccessCb; + std::function onSentFailCb; }; } // namespace mqtt diff --git a/mqtt_streaming_protocol/include/IMqttSubscriber.h b/mqtt_streaming_protocol/include/IMqttSubscriber.h index 9dd4eee0..772f65f8 100644 --- a/mqtt_streaming_protocol/include/IMqttSubscriber.h +++ b/mqtt_streaming_protocol/include/IMqttSubscriber.h @@ -2,7 +2,6 @@ #include "IMqttBase.h" #include "MqttMessage.h" #include -#include namespace mqtt { @@ -14,13 +13,10 @@ class IMqttSubscriber : public virtual IMqttBase virtual bool unsubscribeAll() = 0; virtual void setMessageArrivedCb(std::string topic, std::function cb) = 0; virtual void setMessageArrivedCb(std::function cb) = 0; - virtual std::lock_guard getCbLock() = 0; - virtual ~IMqttSubscriber() - { - } + virtual ~IMqttSubscriber() = default; protected: - std::unordered_map> cbs; - std::function commonCb; + std::unordered_map> onMsgArrivedCbs; + std::function onMsgArrivedCmnCb; }; } // 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/MqttAsyncPublisher.h b/mqtt_streaming_protocol/include/MqttAsyncPublisher.h index 3c5a6d53..a8176f9a 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncPublisher.h +++ b/mqtt_streaming_protocol/include/MqttAsyncPublisher.h @@ -2,14 +2,11 @@ #include "IMqttPublisher.h" #include "MQTTAsync.h" -#include "MqttMessage.h" -#include "MQTTClientType.h" -#include -#include +#include namespace mqtt { -class MqttAsyncPublisher : public virtual IMqttPublisher +class MqttAsyncPublisher final : public virtual IMqttPublisher { public: MqttAsyncPublisher(); @@ -24,144 +21,42 @@ class MqttAsyncPublisher : public virtual IMqttPublisher 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; - } + bool connect() override; + bool disconnect() override; + bool reconnect() override; + MqttConnectionStatus isConnected() override; + void setServerURL(std::string serverUrl) override; + void setClientId(std::string clientId) override; + void setUsernamePasswrod(std::string username, std::string password) override; + void publishBirthCertificates() override; - std::string getServerUrl() const override - { - return this->serverUrl; - } + 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; + + + MqttClientType getClientType() const override; + std::string getServerUrl() const override; private: std::string serverUrl; std::string clientId; std::string username; std::string password; + + std::atomic pendingConnect; + 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); - } + MQTTAsync_SSLOptions sslOpts = MQTTAsync_SSLOptions_initializer; + + 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 onSend(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); }; } // namespace mqtt diff --git a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h index 2254bfa9..6ea9d644 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h +++ b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h @@ -4,6 +4,7 @@ #include "MQTTAsync.h" #include "MqttMessage.h" +#include #include namespace mqtt @@ -43,7 +44,6 @@ class MqttAsyncSubscriber final : public virtual IMqttSubscriber MqttClientType getClientType() const override; std::string getServerUrl() const override; - std::lock_guard getCbLock() override; bool subscribe(std::string topic, int qos) override; bool unsubscribe(std::string topic) override; @@ -59,20 +59,20 @@ class MqttAsyncSubscriber final : public virtual IMqttSubscriber std::string password; std::vector subscriptions; std::recursive_mutex cbMtx; - bool pendingConnect; + std::atomic pendingConnect; MQTTAsync client; MQTTAsync_connectOptions connOpts; MQTTAsync_createOptions createOpts; MQTTAsync_SSLOptions sslOpts; - static int msgArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message); - static void connectedCb(void* context, char* cause); - static void connlost(void* context, char* cause); - static void onSubscriber(void* context, MQTTAsync_successData* response); - static void onSubscriberFailure(void* context, MQTTAsync_failureData* response); + static int onMsgArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message); + static void onConnected(void* context, char* cause); + static void onConnectionLost(void* context, char* cause); + static void onSubscribeSuccess(void* context, MQTTAsync_successData* response); + static void onSubscribeFailure(void* context, MQTTAsync_failureData* response); static void onConnectSuccess(void* context, MQTTAsync_successData data); static void onConnectFailure(void* context, MQTTAsync_failureData data); - static void deliveryComplete(void* context, MQTTAsync_token token); + static void onDeliveryCompleted(void* context, MQTTAsync_token token); }; } // namespace mqtt diff --git a/mqtt_streaming_protocol/src/CMakeLists.txt b/mqtt_streaming_protocol/src/CMakeLists.txt index 8f688d86..24b54f52 100644 --- a/mqtt_streaming_protocol/src/CMakeLists.txt +++ b/mqtt_streaming_protocol/src/CMakeLists.txt @@ -10,7 +10,6 @@ set(SRC_PublicHeaders IMqttBase.h MqttAsyncPublisher.h IMqttSubscriber.h MqttAsyncSubscriber.h - MQTTClientType.h MqttMessage.h MqttSettings.h ) diff --git a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp index 6b637703..9b428b61 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp @@ -1,216 +1,243 @@ #include "MqttAsyncPublisher.h" -#include - namespace mqtt { -MqttAsyncPublisher::MqttAsyncPublisher() : - MqttAsyncPublisher("", "", false, false, false, false, "", "", "", "") -{ - -} +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) + 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) + , clientId(clientId) + , username("") + , password("") + , pendingConnect(false) + , client(nullptr) { - 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; - this->ssl_opts = MQTTAsync_SSLOptions_initializer; - this->connOpts.ssl = &this->ssl_opts; - this->client = nullptr; setServerURL(serverUrl); + connOpts = MQTTAsync_connectOptions_initializer; + connOpts.cleansession = cleanSession ? 1 : 0; + connOpts.keepAliveInterval = 20; + connOpts.connectTimeout = 5; + connOpts.onSuccess = (MQTTAsync_onSuccess *) &MqttAsyncPublisher::onConnectSuccess; + connOpts.onFailure = (MQTTAsync_onFailure *) &MqttAsyncPublisher::onConnectFailure; + connOpts.automaticReconnect = true; + connOpts.minRetryInterval = 1; + connOpts.maxRetryInterval = 10; + connOpts.ssl = &sslOpts; + connOpts.context = this; + createOpts = MQTTAsync_createOptions_initializer; + sslOpts = MQTTAsync_SSLOptions_initializer; } -MqttAsyncPublisher::~MqttAsyncPublisher() -{ - std::lock_guard guard(mtx); - if (this->client != nullptr) - { +MqttAsyncPublisher::~MqttAsyncPublisher() { + if (client != nullptr) { disconnect(); - MQTTAsync_destroy(&this->client); + MQTTAsync_destroy(&client); } } -bool MqttAsyncPublisher::connect() -{ - std::lock_guard guard(mtx); - if (this->client != nullptr) - { +bool MqttAsyncPublisher::connect() { + if (client != nullptr) { // Signal stop reconnecting disconnect(); - MQTTAsync_destroy(&this->client); + MQTTAsync_destroy(&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); + pendingConnect = true; + int rc = MQTTAsync_createWithOptions(&client, + serverUrl.c_str(), + clientId.c_str(), + MQTTCLIENT_PERSISTENCE_NONE, + NULL, + &createOpts); - if (rc != MQTTASYNC_SUCCESS) - { + if (rc != MQTTASYNC_SUCCESS) { return false; } - rc = MQTTAsync_setCallbacks(this->client, this, &MqttAsyncPublisher::connlost, &MqttAsyncPublisher::msgArrived, &MqttAsyncPublisher::deliveryComplete); - if (rc != MQTTASYNC_SUCCESS) - { + rc = MQTTAsync_setCallbacks(client, + this, + &MqttAsyncPublisher::onConnectionLost, + &MqttAsyncPublisher::onMsgArrived, + &MqttAsyncPublisher::onDeliveryCompleted); + + if (rc != MQTTASYNC_SUCCESS) { return false; } - rc = MQTTAsync_setConnected(this->client, this, &MqttAsyncPublisher::connectedCb); - if (rc != MQTTASYNC_SUCCESS) - { + rc = MQTTAsync_setConnected(client, this, &MqttAsyncPublisher::onConnected); + if (rc != MQTTASYNC_SUCCESS) { return false; } - this->connOpts.context = this; - if ((rc = MQTTAsync_connect(this->client, &this->connOpts)) != MQTTASYNC_SUCCESS) - { + rc = MQTTAsync_connect(client, &connOpts); + if (rc != MQTTASYNC_SUCCESS) { return false; } - return true; } -bool MqttAsyncPublisher::disconnect() -{ - std::lock_guard guard(mtx); - return MQTTAsync_disconnect(this->client, NULL) == MQTTASYNC_SUCCESS; +bool MqttAsyncPublisher::disconnect() { + return MQTTAsync_disconnect(client, NULL) == MQTTASYNC_SUCCESS; } -MqttConnectionStatus MqttAsyncPublisher::isConnected() -{ - std::lock_guard guard(mtx); - if (this->penddingConnect) +MqttConnectionStatus MqttAsyncPublisher::isConnected() { + if (pendingConnect) return MqttConnectionStatus::pending; - return MQTTAsync_isConnected(this->client) ? MqttConnectionStatus::connected : MqttConnectionStatus::not_connected; + + return MQTTAsync_isConnected(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; + connOpts.username = !username.empty() ? username.c_str() : NULL; + connOpts.password = !password.empty() ? password.c_str() : NULL; } -void MqttAsyncPublisher::publishBirthCertificates() -{ -} +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); +bool MqttAsyncPublisher::publish(const std::string &topic, void *data, + size_t dataLen, std::string *err, int qos, + int *token, bool retained) { 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.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) - { + if ((rc = MQTTAsync_sendMessage(client, topic.c_str(), &pubmsg, &opts)) != MQTTASYNC_SUCCESS) { + if (err != nullptr) { *err = MQTTAsync_strerror(rc); } } - if (token != nullptr) - { + 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; +void MqttAsyncPublisher::setServerURL(std::string serverUrl) { + if (serverUrl[0] == ':') { + serverUrl = ""; + } else { + std::string ssl("ssl://"); + std::string tcp("tcp://"); + // Remove any protocol if available + if ((serverUrl.size() >= ssl.size() + && std::equal(ssl.begin(), ssl.end(), serverUrl.begin()))) { + serverUrl.erase(0, ssl.length()); } - if (this->useCertificates) - { - this->ssl_opts.keyStore = clientCertPath.c_str(); - this->ssl_opts.privateKey = privKeyPath.c_str(); + if ((serverUrl.size() >= tcp.size() + && std::equal(tcp.begin(), tcp.end(), serverUrl.begin()))) { + serverUrl.erase(0, tcp.length()); } - else - { - this->ssl_opts.keyStore = nullptr; - this->ssl_opts.privateKey = nullptr; + + if (enableSSL) { + serverUrl = "ssl://" + serverUrl; + sslOpts.enableServerCertAuth = useCertificates && verifyServerCert + && trustStorePath != ""; + sslOpts.trustStore = (trustStorePath != "") ? trustStorePath.c_str() : nullptr; + sslOpts.keyStore = (useCertificates) ? clientCertPath.c_str() : nullptr; + sslOpts.privateKey = (useCertificates) ? privKeyPath.c_str() : nullptr; + } else { + serverUrl = "tcp://" + serverUrl; } } - else - { - this->serverUrl = "tcp://" + this->serverUrl; + this->serverUrl = serverUrl; +} + +void MqttAsyncPublisher::setClientId(std::string clientId) { + this->clientId = clientId; +} + +bool MqttAsyncPublisher::reconnect() { return false; } + +MqttClientType MqttAsyncPublisher::getClientType() const { + return MqttClientType::async; +} + +std::string MqttAsyncPublisher::getServerUrl() const { return serverUrl; } + +void MqttAsyncPublisher::onDeliveryCompleted(void *context, MQTTAsync_token token) {} + +void MqttAsyncPublisher::onConnected(void *context, char *cause) { + if (context != nullptr) { + auto publisher = (MqttAsyncPublisher *) context; + publisher->pendingConnect = false; + auto lock = publisher->getCbLock(); + if (publisher->onConnectedCb) + publisher->onConnectedCb(); + } +} + +void MqttAsyncPublisher::onConnectionLost(void *context, char *cause) { + (void) cause; + // Reconnect procedure here! + if (context != nullptr) { + auto publisher = (MqttAsyncPublisher *) context; + publisher->pendingConnect = false; } } -void MqttAsyncPublisher::setClientId(std::string clientId) +int MqttAsyncPublisher::onMsgArrived(void *context, + char *topicName, + int topicLen, + MQTTAsync_message *message) { - std::lock_guard guard(mtx); - this->clientId = clientId; + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return 1; +} + +void MqttAsyncPublisher::onSend(void *context, MQTTAsync_successData *data) { + auto publisher = (MqttAsyncPublisher *) context; + auto lock = publisher->getCbLock(); + if (publisher->onSentSuccessCb) + publisher->onSentSuccessCb(data->token); } -bool MqttAsyncPublisher::reconnect() + +void MqttAsyncPublisher::onSendFailure(void *context, MQTTAsync_failureData *data) +{ + auto publisher = (MqttAsyncPublisher *) context; + auto lock = publisher->getCbLock(); + if (publisher->onSentFailCb) + publisher->onSentFailCb(data->token); +} + +void MqttAsyncPublisher::onConnectSuccess(void *context, MQTTAsync_successData data) { - return false; + // TODO : check when this is called } + +void MqttAsyncPublisher::onConnectFailure(void *context, MQTTAsync_failureData data) +{ + // TODO : check when this is called + auto publisher = (MqttAsyncPublisher *) context; + publisher->pendingConnect = false; } +} // namespace mqtt diff --git a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp index 1a6e3abf..ed6743c2 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp @@ -46,6 +46,7 @@ MqttAsyncSubscriber::MqttAsyncSubscriber(std::string serverUrl, } MqttAsyncSubscriber::~MqttAsyncSubscriber() { + unsubscribeAll(); if (client != nullptr) { disconnect(); MQTTAsync_destroy(&client); @@ -73,14 +74,14 @@ bool MqttAsyncSubscriber::connect() { rc = MQTTAsync_setCallbacks(client, this, - &MqttAsyncSubscriber::connlost, - &MqttAsyncSubscriber::msgArrived, - &MqttAsyncSubscriber::deliveryComplete); + &MqttAsyncSubscriber::onConnectionLost, + &MqttAsyncSubscriber::onMsgArrived, + &MqttAsyncSubscriber::onDeliveryCompleted); if (rc != MQTTASYNC_SUCCESS) { return false; } - rc = MQTTAsync_setConnected(client, this, &MqttAsyncSubscriber::connectedCb); + rc = MQTTAsync_setConnected(client, this, &MqttAsyncSubscriber::onConnected); if (rc != MQTTASYNC_SUCCESS) { return false; } @@ -101,8 +102,8 @@ MqttConnectionStatus MqttAsyncSubscriber::isConnected() { if (pendingConnect) return MqttConnectionStatus::pending; - return MQTTAsync_isConnected(this->client) ? MqttConnectionStatus::connected - : MqttConnectionStatus::not_connected; + return MQTTAsync_isConnected(client) ? MqttConnectionStatus::connected + : MqttConnectionStatus::not_connected; } void MqttAsyncSubscriber::setServerURL(std::string serverUrl) { @@ -144,8 +145,8 @@ void MqttAsyncSubscriber::setUsernamePasswrod(std::string username, std::string 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; + connOpts.username = !username.empty() ? username.c_str() : NULL; + connOpts.password = !password.empty() ? password.c_str() : NULL; } MqttClientType MqttAsyncSubscriber::getClientType() const { @@ -164,8 +165,8 @@ bool MqttAsyncSubscriber::unsubscribeAll() { bool MqttAsyncSubscriber::subscribe(std::string topic, int qos) { MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; int rc; - opts.onSuccess = MqttAsyncSubscriber::onSubscriber; - opts.onFailure = MqttAsyncSubscriber::onSubscriberFailure; + opts.onSuccess = MqttAsyncSubscriber::onSubscribeSuccess; + opts.onFailure = MqttAsyncSubscriber::onSubscribeFailure; opts.context = this; if ((rc = MQTTAsync_subscribe(client, topic.c_str(), qos, &opts)) == MQTTASYNC_SUCCESS) { subscriptions.emplace_back(topic, qos); @@ -175,8 +176,8 @@ bool MqttAsyncSubscriber::subscribe(std::string topic, int qos) { bool MqttAsyncSubscriber::unsubscribe(std::string topic) { MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - opts.onSuccess = MqttAsyncSubscriber::onSubscriber; - opts.onFailure = MqttAsyncSubscriber::onSubscriberFailure; + opts.onSuccess = MqttAsyncSubscriber::onSubscribeSuccess; + opts.onFailure = MqttAsyncSubscriber::onSubscribeFailure; opts.context = this; int rc = MQTTAsync_unsubscribe(client, topic.c_str(), &opts); auto it = std::find_if(subscriptions.begin(), @@ -191,27 +192,23 @@ void MqttAsyncSubscriber::setMessageArrivedCb( std::function cb) { auto lock = getCbLock(); - commonCb = cb; + onMsgArrivedCmnCb = cb; } void MqttAsyncSubscriber::setMessageArrivedCb( std::string topic, std::function cb) { auto lock = getCbLock(); - cbs.insert({topic, cb}); -} - -std::lock_guard MqttAsyncSubscriber::getCbLock() { - return std::lock_guard(cbMtx); + onMsgArrivedCbs.insert({topic, cb}); } bool MqttAsyncSubscriber::reconnect() { return true; } std::string MqttAsyncSubscriber::getServerUrl() const { return serverUrl; } -void MqttAsyncSubscriber::deliveryComplete(void *context, MQTTAsync_token token) {} +void MqttAsyncSubscriber::onDeliveryCompleted(void *context, MQTTAsync_token token) {} -int MqttAsyncSubscriber::msgArrived(void *context, +int MqttAsyncSubscriber::onMsgArrived(void *context, char *topicName, int topicLen, MQTTAsync_message *message) @@ -219,14 +216,14 @@ int MqttAsyncSubscriber::msgArrived(void *context, MqttAsyncSubscriber *subscriber = (MqttAsyncSubscriber *) context; { auto lock = subscriber->getCbLock(); - auto it = subscriber->cbs.find(topicName); - if (subscriber->commonCb || it != subscriber->cbs.end()) { + auto it = subscriber->onMsgArrivedCbs.find(topicName); + if (subscriber->onMsgArrivedCmnCb || it != subscriber->onMsgArrivedCbs.end()) { mqtt::MqttMessage msg; msg.setTopic(topicName); msg.addData((uint8_t *) message->payload, message->payloadlen); - if (subscriber->commonCb) - subscriber->commonCb(*subscriber, msg); - if (it != subscriber->cbs.end() && it->second) + if (subscriber->onMsgArrivedCmnCb) + subscriber->onMsgArrivedCmnCb(*subscriber, msg); + if (it != subscriber->onMsgArrivedCbs.end() && it->second) it->second(*subscriber, msg); } } @@ -236,22 +233,22 @@ int MqttAsyncSubscriber::msgArrived(void *context, return 1; } -void MqttAsyncSubscriber::connectedCb(void *context, char *cause) { +void MqttAsyncSubscriber::onConnected(void *context, char *cause) { if (context != nullptr) { auto subscriber = (MqttAsyncSubscriber *) context; subscriber->pendingConnect = false; auto lock = subscriber->getCbLock(); - if (subscriber->onConnectCb) - subscriber->onConnectCb(); + if (subscriber->onConnectedCb) + subscriber->onConnectedCb(); } } -void MqttAsyncSubscriber::connlost(void *context, char *cause) { +void MqttAsyncSubscriber::onConnectionLost(void *context, char *cause) { // Reconnect procedure here! } -void MqttAsyncSubscriber::onSubscriber(void *context, MQTTAsync_successData *response) {} +void MqttAsyncSubscriber::onSubscribeSuccess(void *context, MQTTAsync_successData *response) {} -void MqttAsyncSubscriber::onSubscriberFailure(void *context, MQTTAsync_failureData *response) {} +void MqttAsyncSubscriber::onSubscribeFailure(void *context, MQTTAsync_failureData *response) {} void MqttAsyncSubscriber::onConnectSuccess(void *context, MQTTAsync_successData data) { 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 0e749207..3313e1f4 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -162,7 +162,7 @@ void MqttStreamingServerImpl::setupMqttPublisher() publisher.setServerURL(connectionSettings.mqttUrl); publisher.setClientId(connectionSettings.clientId); publisher.setUsernamePasswrod(connectionSettings.username, connectionSettings.password); - publisher.setOnConnect([this]() { LOG_I("MQTT: Connection established"); }); + publisher.setOnConnected([this]() { LOG_I("MQTT: Connection established"); }); LOG_I("MQTT: Trying to connect to MQTT broker ({})", connectionSettings.mqttUrl); publisher.connect(); From 4c837e85a9226cba4e3a48196938fcc043a68328 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 24 Sep 2025 18:54:56 +0200 Subject: [PATCH 13/46] mqtt: wrapper tests --- mqtt_streaming_protocol/CMakeLists.txt | 1 + mqtt_streaming_protocol/src/CMakeLists.txt | 9 +- .../src/MqttAsyncPublisher.cpp | 9 +- .../src/MqttAsyncSubscriber.cpp | 8 +- mqtt_streaming_protocol/tests/CMakeLists.txt | 18 +++ mqtt_streaming_protocol/tests/test_app.cpp | 21 ++++ .../tests/test_mqtt_streaming_protocol.cpp | 103 ++++++++++++++++++ 7 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 mqtt_streaming_protocol/tests/CMakeLists.txt create mode 100644 mqtt_streaming_protocol/tests/test_app.cpp create mode 100644 mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp diff --git a/mqtt_streaming_protocol/CMakeLists.txt b/mqtt_streaming_protocol/CMakeLists.txt index d65c2e35..4113dc74 100644 --- a/mqtt_streaming_protocol/CMakeLists.txt +++ b/mqtt_streaming_protocol/CMakeLists.txt @@ -8,3 +8,4 @@ message(STATUS "${MQTT_STREAMING_PROTOCOL_PRJ_NAME} version: ${MQTT_STREAMING_PR project(${MQTT_STREAMING_PROTOCOL_PRJ_NAME} VERSION ${MQTT_STREAMING_PROTOCOL_VERSION} LANGUAGES C CXX) add_subdirectory(src) +add_subdirectory(tests) diff --git a/mqtt_streaming_protocol/src/CMakeLists.txt b/mqtt_streaming_protocol/src/CMakeLists.txt index 24b54f52..4c7acb61 100644 --- a/mqtt_streaming_protocol/src/CMakeLists.txt +++ b/mqtt_streaming_protocol/src/CMakeLists.txt @@ -1,5 +1,4 @@ -set(BASE_NAME mqtt_streaming_protocol) -set(LIB_NAME ${BASE_NAME}) +set(LIB_NAME mqtt_streaming_protocol) set(SRC_Cpp MqttAsyncPublisher.cpp MqttAsyncSubscriber.cpp @@ -17,15 +16,13 @@ set(SRC_PublicHeaders IMqttBase.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) diff --git a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp index 9b428b61..45584b9e 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp @@ -59,7 +59,10 @@ bool MqttAsyncPublisher::connect() { MQTTAsync_destroy(&client); } - pendingConnect = true; + if (serverUrl.empty() || clientId.empty()) { + return false; + } + int rc = MQTTAsync_createWithOptions(&client, serverUrl.c_str(), clientId.c_str(), @@ -90,7 +93,7 @@ bool MqttAsyncPublisher::connect() { if (rc != MQTTASYNC_SUCCESS) { return false; } - + pendingConnect = true; return true; } @@ -142,7 +145,7 @@ bool MqttAsyncPublisher::publish(const std::string &topic, void *data, } void MqttAsyncPublisher::setServerURL(std::string serverUrl) { - if (serverUrl[0] == ':') { + if (serverUrl[0] == ':' || serverUrl == "") { serverUrl = ""; } else { std::string ssl("ssl://"); diff --git a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp index ed6743c2..490fc5e2 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp @@ -60,7 +60,10 @@ bool MqttAsyncSubscriber::connect() { MQTTAsync_destroy(&client); } - pendingConnect = true; + if (serverUrl.empty() || clientId.empty()) { + return false; + } + int rc = MQTTAsync_createWithOptions(&client, serverUrl.c_str(), clientId.c_str(), @@ -91,6 +94,7 @@ bool MqttAsyncSubscriber::connect() { return false; } + pendingConnect = true; return true; } @@ -107,7 +111,7 @@ MqttConnectionStatus MqttAsyncSubscriber::isConnected() { } void MqttAsyncSubscriber::setServerURL(std::string serverUrl) { - if (serverUrl[0] == ':') { + if (serverUrl[0] == ':' || serverUrl == "") { serverUrl = ""; } else { std::string ssl("ssl://"); diff --git a/mqtt_streaming_protocol/tests/CMakeLists.txt b/mqtt_streaming_protocol/tests/CMakeLists.txt new file mode 100644 index 00000000..4c0dd569 --- /dev/null +++ b/mqtt_streaming_protocol/tests/CMakeLists.txt @@ -0,0 +1,18 @@ +set(MODULE_NAME mqtt_streaming_protocol) +set(TEST_APP test_${MODULE_NAME}) + +set(TEST_SOURCES test_mqtt_streaming_protocol.cpp + test_app.cpp +) + +add_executable(${TEST_APP} ${TEST_SOURCES} +) + +target_link_libraries(${TEST_APP} PRIVATE daq::test_utils + ${MODULE_NAME} +) + +add_test(NAME ${TEST_APP} + COMMAND $ + WORKING_DIRECTORY $ +) diff --git a/mqtt_streaming_protocol/tests/test_app.cpp b/mqtt_streaming_protocol/tests/test_app.cpp new file mode 100644 index 00000000..d92f41f2 --- /dev/null +++ b/mqtt_streaming_protocol/tests/test_app.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include +#include +#include + +int main(int argc, char** args) +{ + daq::daqInitializeCoreObjectsTesting(); + daqInitModuleManagerLibrary(); + + testing::InitGoogleTest(&argc, args); + + testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new DaqMemCheckListener()); + + auto res = RUN_ALL_TESTS(); + + return res; +} diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp new file mode 100644 index 00000000..dd4f145e --- /dev/null +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -0,0 +1,103 @@ +#include "MqttAsyncPublisher.h" +#include "MqttAsyncSubscriber.h" +#include +#include +#include + +using namespace mqtt; + +class MqttStreamingProtocolTest : public ::testing::Test { +protected: + MqttAsyncPublisher publisher; + + std::promise connectedPromise; + std::future connectedFuture; + int successTimeout = 5000; + int failureTimeout = 3000; + + bool createConnection(std::string url, std::string id) { + publisher.setServerURL(url); + publisher.setClientId(id); + + connectedPromise = std::promise(); + connectedFuture = connectedPromise.get_future(); + + publisher.setOnConnected([this]() { + connectedPromise.set_value(true); + }); + return publisher.connect(); + } +}; + +TEST_F(MqttStreamingProtocolTest, Connection) +{ + auto ok = createConnection("127.0.0.1", "testPublisherId"); + ASSERT_TRUE(ok); + + auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); + + ASSERT_TRUE(status == std::future_status::ready); + ASSERT_TRUE(connectedFuture.get()); +} + +TEST_F(MqttStreamingProtocolTest, WrongUrlConnection) +{ + auto ok = createConnection("", "testPublisherId"); + ASSERT_FALSE(ok); + + auto status = connectedFuture.wait_for(std::chrono::milliseconds(failureTimeout)); + + ASSERT_TRUE(status == std::future_status::timeout); +} + +TEST_F(MqttStreamingProtocolTest, WrongIdConnection) +{ + auto ok = createConnection("127.0.0.1", ""); + ASSERT_FALSE(ok); + + auto status = connectedFuture.wait_for(std::chrono::milliseconds(failureTimeout)); + + ASSERT_TRUE(status == std::future_status::timeout); +} + +TEST_F(MqttStreamingProtocolTest, WrongPortConnection) +{ + auto ok = createConnection("127.0.0.1:1888", "testPublisherId"); + ASSERT_TRUE(ok); + + auto status = connectedFuture.wait_for(std::chrono::milliseconds(failureTimeout)); + + ASSERT_TRUE(status == std::future_status::timeout); +} + +TEST_F(MqttStreamingProtocolTest, Connected) +{ + auto ok = createConnection("127.0.0.1", "testPublisherId"); + ASSERT_TRUE(ok); + + auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); + + ASSERT_TRUE(status == std::future_status::ready); + ASSERT_TRUE(connectedFuture.get()); + ASSERT_TRUE(publisher.isConnected() == MqttConnectionStatus::connected); +} + +TEST_F(MqttStreamingProtocolTest, Disconnection) +{ + auto ok = createConnection("127.0.0.1", "testPublisherId"); + ASSERT_TRUE(ok); + + auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); + + ASSERT_TRUE(status == std::future_status::ready); + ASSERT_TRUE(connectedFuture.get()); + ASSERT_TRUE(publisher.isConnected() == MqttConnectionStatus::connected); + + auto disconnectionOk = publisher.disconnect(); + ASSERT_TRUE(disconnectionOk); +} + +TEST_F(MqttStreamingProtocolTest, NotConnected) +{ + ASSERT_TRUE(publisher.isConnected() == MqttConnectionStatus::not_connected); +} From c2322ad7b238a0a890823bec1829633e846b19ba Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 25 Sep 2025 11:47:30 +0200 Subject: [PATCH 14/46] mqtt: nullptr checks for wrappers --- .../src/MqttAsyncPublisher.cpp | 33 ++++++++++----- .../src/MqttAsyncSubscriber.cpp | 40 +++++++++++-------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp index 45584b9e..36e169dd 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp @@ -212,35 +212,46 @@ int MqttAsyncPublisher::onMsgArrived(void *context, int topicLen, MQTTAsync_message *message) { - MQTTAsync_freeMessage(&message); + if (message != nullptr) + MQTTAsync_freeMessage(&message); MQTTAsync_free(topicName); return 1; } void MqttAsyncPublisher::onSend(void *context, MQTTAsync_successData *data) { - auto publisher = (MqttAsyncPublisher *) context; - auto lock = publisher->getCbLock(); - if (publisher->onSentSuccessCb) - publisher->onSentSuccessCb(data->token); + if (context != nullptr && data != nullptr) { + auto publisher = (MqttAsyncPublisher *) context; + auto lock = publisher->getCbLock(); + if (publisher->onSentSuccessCb) + publisher->onSentSuccessCb(data->token); + } } void MqttAsyncPublisher::onSendFailure(void *context, MQTTAsync_failureData *data) { - auto publisher = (MqttAsyncPublisher *) context; - auto lock = publisher->getCbLock(); - if (publisher->onSentFailCb) - publisher->onSentFailCb(data->token); + if (context != nullptr && data != nullptr) { + auto publisher = (MqttAsyncPublisher *) context; + auto lock = publisher->getCbLock(); + if (publisher->onSentFailCb) + publisher->onSentFailCb(data->token); + } } void MqttAsyncPublisher::onConnectSuccess(void *context, MQTTAsync_successData data) { // TODO : check when this is called + if (context != nullptr) { + auto publisher = (MqttAsyncPublisher *) context; + publisher->pendingConnect = false; + } } void MqttAsyncPublisher::onConnectFailure(void *context, MQTTAsync_failureData data) { // TODO : check when this is called - auto publisher = (MqttAsyncPublisher *) context; - publisher->pendingConnect = false; + if (context != nullptr) { + auto publisher = (MqttAsyncPublisher *) context; + publisher->pendingConnect = false; + } } } // namespace mqtt diff --git a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp index 490fc5e2..a9d0dc38 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp @@ -217,23 +217,27 @@ int MqttAsyncSubscriber::onMsgArrived(void *context, int topicLen, MQTTAsync_message *message) { - MqttAsyncSubscriber *subscriber = (MqttAsyncSubscriber *) context; - { - auto lock = subscriber->getCbLock(); - auto it = subscriber->onMsgArrivedCbs.find(topicName); - if (subscriber->onMsgArrivedCmnCb || it != subscriber->onMsgArrivedCbs.end()) { - mqtt::MqttMessage msg; - msg.setTopic(topicName); - msg.addData((uint8_t *) message->payload, message->payloadlen); - if (subscriber->onMsgArrivedCmnCb) - subscriber->onMsgArrivedCmnCb(*subscriber, msg); - if (it != subscriber->onMsgArrivedCbs.end() && it->second) - it->second(*subscriber, msg); + if (context != nullptr && message != nullptr) { + MqttAsyncSubscriber *subscriber = (MqttAsyncSubscriber *) context; + { + auto lock = subscriber->getCbLock(); + auto it = (topicName != nullptr) ? + subscriber->onMsgArrivedCbs.find(topicName) + : subscriber->onMsgArrivedCbs.end(); + if (subscriber->onMsgArrivedCmnCb || it != subscriber->onMsgArrivedCbs.end()) { + mqtt::MqttMessage msg; + msg.setTopic(topicName); + msg.addData((uint8_t *) message->payload, message->payloadlen); + if (subscriber->onMsgArrivedCmnCb) + subscriber->onMsgArrivedCmnCb(*subscriber, msg); + if (it != subscriber->onMsgArrivedCbs.end() && it->second) + it->second(*subscriber, msg); + } } } - MQTTAsync_freeMessage(&message); - if (topicLen > 0) - MQTTAsync_free(topicName); + if (message != nullptr) + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); return 1; } @@ -261,8 +265,10 @@ void MqttAsyncSubscriber::onConnectSuccess(void *context, MQTTAsync_successData void MqttAsyncSubscriber::onConnectFailure(void *context, MQTTAsync_failureData data) { - auto subscriber = (MqttAsyncSubscriber *) context; - subscriber->pendingConnect = false; + if (context != nullptr) { + auto subscriber = (MqttAsyncSubscriber *) context; + subscriber->pendingConnect = false; + } } } // namespace mqtt From d89a8de1c1d467b49c8900e493146613b75b6e9f Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 25 Sep 2025 12:30:12 +0200 Subject: [PATCH 15/46] mqtt: publisher/subscriber wrapper tests --- .../tests/test_mqtt_streaming_protocol.cpp | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp index dd4f145e..d78cb42b 100644 --- a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -6,30 +6,39 @@ using namespace mqtt; -class MqttStreamingProtocolTest : public ::testing::Test { +class MqttStreamingProtocolTest : public ::testing::TestWithParam> { protected: - MqttAsyncPublisher publisher; + std::shared_ptr instance; std::promise connectedPromise; std::future connectedFuture; + int successTimeout = 5000; int failureTimeout = 3000; + void SetUp() override { + instance = GetParam(); + } + + void TearDown() override { + instance->disconnect(); + } + bool createConnection(std::string url, std::string id) { - publisher.setServerURL(url); - publisher.setClientId(id); + instance->setServerURL(url); + instance->setClientId(id); connectedPromise = std::promise(); connectedFuture = connectedPromise.get_future(); - publisher.setOnConnected([this]() { + instance->setOnConnected([this]() { connectedPromise.set_value(true); }); - return publisher.connect(); + return instance->connect(); } }; -TEST_F(MqttStreamingProtocolTest, Connection) +TEST_P(MqttStreamingProtocolTest, Connection) { auto ok = createConnection("127.0.0.1", "testPublisherId"); ASSERT_TRUE(ok); @@ -40,7 +49,7 @@ TEST_F(MqttStreamingProtocolTest, Connection) ASSERT_TRUE(connectedFuture.get()); } -TEST_F(MqttStreamingProtocolTest, WrongUrlConnection) +TEST_P(MqttStreamingProtocolTest, WrongUrlConnection) { auto ok = createConnection("", "testPublisherId"); ASSERT_FALSE(ok); @@ -50,7 +59,7 @@ TEST_F(MqttStreamingProtocolTest, WrongUrlConnection) ASSERT_TRUE(status == std::future_status::timeout); } -TEST_F(MqttStreamingProtocolTest, WrongIdConnection) +TEST_P(MqttStreamingProtocolTest, WrongIdConnection) { auto ok = createConnection("127.0.0.1", ""); ASSERT_FALSE(ok); @@ -60,7 +69,7 @@ TEST_F(MqttStreamingProtocolTest, WrongIdConnection) ASSERT_TRUE(status == std::future_status::timeout); } -TEST_F(MqttStreamingProtocolTest, WrongPortConnection) +TEST_P(MqttStreamingProtocolTest, WrongPortConnection) { auto ok = createConnection("127.0.0.1:1888", "testPublisherId"); ASSERT_TRUE(ok); @@ -70,7 +79,7 @@ TEST_F(MqttStreamingProtocolTest, WrongPortConnection) ASSERT_TRUE(status == std::future_status::timeout); } -TEST_F(MqttStreamingProtocolTest, Connected) +TEST_P(MqttStreamingProtocolTest, Connected) { auto ok = createConnection("127.0.0.1", "testPublisherId"); ASSERT_TRUE(ok); @@ -79,10 +88,10 @@ TEST_F(MqttStreamingProtocolTest, Connected) ASSERT_TRUE(status == std::future_status::ready); ASSERT_TRUE(connectedFuture.get()); - ASSERT_TRUE(publisher.isConnected() == MqttConnectionStatus::connected); + ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::connected); } -TEST_F(MqttStreamingProtocolTest, Disconnection) +TEST_P(MqttStreamingProtocolTest, Disconnection) { auto ok = createConnection("127.0.0.1", "testPublisherId"); ASSERT_TRUE(ok); @@ -91,13 +100,24 @@ TEST_F(MqttStreamingProtocolTest, Disconnection) ASSERT_TRUE(status == std::future_status::ready); ASSERT_TRUE(connectedFuture.get()); - ASSERT_TRUE(publisher.isConnected() == MqttConnectionStatus::connected); + ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::connected); - auto disconnectionOk = publisher.disconnect(); + auto disconnectionOk = instance->disconnect(); ASSERT_TRUE(disconnectionOk); + // It is necessary to give the client time to disconnect. + // ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::not_connected); } -TEST_F(MqttStreamingProtocolTest, NotConnected) +TEST_P(MqttStreamingProtocolTest, NotConnected) { - ASSERT_TRUE(publisher.isConnected() == MqttConnectionStatus::not_connected); + ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::not_connected); } + +// Instantiate with values +INSTANTIATE_TEST_SUITE_P(PublisherGroup, + MqttStreamingProtocolTest, + ::testing::Values(std::make_shared())); + +INSTANTIATE_TEST_SUITE_P(SubscriberGroup, + MqttStreamingProtocolTest, + ::testing::Values(std::make_shared())); From 25c799791eeae4d60ba06f1364adfa60cec115a7 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 25 Sep 2025 15:18:43 +0200 Subject: [PATCH 16/46] mqtt: reworking of mqtt wrappers; MqttAsyncPublisher/MqttAsyncSubscriber -> MqttAsyncClient --- .../mqtt_receiver_fb_impl.h | 8 +- .../mqtt_streaming_device_impl.h | 6 +- .../src/mqtt_receiver_fb_impl.cpp | 4 +- .../src/mqtt_streaming_device_impl.cpp | 6 +- mqtt_streaming_protocol/include/IMqttBase.h | 92 ------ .../include/IMqttPublisher.h | 30 -- .../include/IMqttSubscriber.h | 22 -- .../include/MqttAsyncClient.h | 102 ++++++ .../include/MqttAsyncPublisher.h | 62 ---- .../include/MqttAsyncSubscriber.h | 78 ----- mqtt_streaming_protocol/src/CMakeLists.txt | 9 +- .../src/MqttAsyncClient.cpp | 307 ++++++++++++++++++ .../src/MqttAsyncPublisher.cpp | 257 --------------- .../src/MqttAsyncSubscriber.cpp | 274 ---------------- .../tests/test_mqtt_streaming_protocol.cpp | 47 ++- .../mqtt_streaming_server_impl.h | 4 +- .../src/mqtt_streaming_server_impl.cpp | 2 +- 17 files changed, 445 insertions(+), 865 deletions(-) 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 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/src/MqttAsyncClient.cpp delete mode 100644 mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp delete mode 100644 mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp 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 d04d63be..061a1498 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 @@ -21,7 +21,7 @@ #include #include -#include "MqttAsyncSubscriber.h" +#include "MqttAsyncClient.h" BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE @@ -32,7 +32,7 @@ class MqttReceiverFbImpl final : public FunctionBlock const ComponentPtr& parent, const FunctionBlockTypePtr& type, const StringPtr& localId, - std::shared_ptr subscriber, + std::shared_ptr subscriber, const PropertyObjectPtr& config = nullptr); ~MqttReceiverFbImpl() override; @@ -40,7 +40,7 @@ class MqttReceiverFbImpl final : public FunctionBlock std::unordered_map outputSignals; std::unordered_map outputDomainSignals; - std::shared_ptr subscriber; + std::shared_ptr subscriber; std::vector subscribedTopics; std::mutex sync; @@ -56,7 +56,7 @@ class MqttReceiverFbImpl final : public FunctionBlock void propertyChanged(bool configure); void readProperties(); - void onSignalsMessage(const mqtt::IMqttSubscriber& subscriber, mqtt::MqttMessage& msg); + void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); std::string buildSignalNameFromTopic(std::string topic) const; std::string buildDomainSignalNameFromTopic(std::string topic) const; 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 96b163aa..3df5435c 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 @@ -15,7 +15,7 @@ */ #pragma once -#include "MqttAsyncSubscriber.h" +#include "MqttAsyncClient.h" #include "MqttSettings.h" #include #include @@ -47,14 +47,14 @@ class MqttStreamingDeviceImpl : public Device FunctionBlockPtr onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) override; void setupMqttSubscriber(); - void onSignalsMessage(const mqtt::IMqttSubscriber& subscriber, mqtt::MqttMessage& msg); + void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); DictObjectPtr fbTypes; StringPtr connectionString; EnumerationPtr connectionStatus; - std::shared_ptr subscriber; + std::shared_ptr subscriber; Mqtt::Utils::Settings::MqttConnectionSettings connectionSettings; std::promise connectedPromise; 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 557768fc..2eb1434b 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -19,7 +19,7 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, const FunctionBlockTypePtr& type, const StringPtr& localId, - std::shared_ptr subscriber, + std::shared_ptr subscriber, const PropertyObjectPtr& config) : FunctionBlock(type, ctx, parent, localId) , subscriber(subscriber) @@ -48,7 +48,7 @@ MqttReceiverFbImpl::~MqttReceiverFbImpl() subscriber->unsubscribe(topic); } } -void MqttReceiverFbImpl::onSignalsMessage(const mqtt::IMqttSubscriber& subscriber, mqtt::MqttMessage& msg) +void MqttReceiverFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg) { parseMessage(msg); } 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 8751490a..ceaec39b 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -22,7 +22,7 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const PropertyObjectPtr& config) : Device(ctx, parent, getLocalId()) , connectionStatus(Enumeration("ConnectionStatusType", "Connected", this->context.getTypeManager())) - , subscriber(std::make_shared()) + , subscriber(std::make_shared()) { this->name = MQTT_DEVICE_NAME; @@ -78,7 +78,7 @@ void MqttStreamingDeviceImpl::setupMqttSubscriber() subscriber->setClientId(connectionSettings.clientId); subscriber->setUsernamePasswrod(connectionSettings.username, connectionSettings.password); - subscriber->setOnConnected([this] { + subscriber->setConnectedCb([this] { connectedPromise.set_value(true); }); @@ -86,7 +86,7 @@ void MqttStreamingDeviceImpl::setupMqttSubscriber() subscriber->connect(); } -void MqttStreamingDeviceImpl::onSignalsMessage(const mqtt::IMqttSubscriber& subscriber, mqtt::MqttMessage& msg) +void MqttStreamingDeviceImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg) { const std::string topic = msg.getTopic(); std::vector list; diff --git a/mqtt_streaming_protocol/include/IMqttBase.h b/mqtt_streaming_protocol/include/IMqttBase.h deleted file mode 100644 index b222a0bb..00000000 --- a/mqtt_streaming_protocol/include/IMqttBase.h +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -#include -#include -#include - -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 setOnConnected(std::function cb) { - auto lock = getCbLock(); - onConnectedCb = 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() = default; - -protected: - bool enableSSL; - bool useCertificates; - bool verifyServerCert; - std::string trustStorePath; - std::string clientCertPath; - std::string privKeyPath; - std::string privKeyPass; - - std::recursive_mutex cbMtx; - - std::function onConnectedCb; - - std::lock_guard getCbLock() { - return std::lock_guard(cbMtx); - } -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/IMqttPublisher.h b/mqtt_streaming_protocol/include/IMqttPublisher.h deleted file mode 100644 index 94840284..00000000 --- a/mqtt_streaming_protocol/include/IMqttPublisher.h +++ /dev/null @@ -1,30 +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 setPublishSuccessCB(std::function cb) - { - auto lock = getCbLock(); - onSentSuccessCb = cb; - } - - virtual void setPublishFailCB(std::function cb) - { - auto lock = getCbLock(); - onSentFailCb = cb; - } - - virtual ~IMqttPublisher() = default; - -protected: - std::function onSentSuccessCb; - std::function onSentFailCb; -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/IMqttSubscriber.h b/mqtt_streaming_protocol/include/IMqttSubscriber.h deleted file mode 100644 index 772f65f8..00000000 --- a/mqtt_streaming_protocol/include/IMqttSubscriber.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "IMqttBase.h" -#include "MqttMessage.h" -#include - -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::string topic, std::function cb) = 0; - virtual void setMessageArrivedCb(std::function cb) = 0; - virtual ~IMqttSubscriber() = default; - -protected: - std::unordered_map> onMsgArrivedCbs; - std::function onMsgArrivedCmnCb; -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h new file mode 100644 index 00000000..fcb805d2 --- /dev/null +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -0,0 +1,102 @@ +#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(); + + 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 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_createOptions createOpts; + MQTTAsync_SSLOptions sslOpts = MQTTAsync_SSLOptions_initializer; + + std::recursive_mutex cbMtx; + + std::function onConnectedCb; + std::function onSentSuccessCb; + std::function onSentFailCb; + 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 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 a8176f9a..00000000 --- a/mqtt_streaming_protocol/include/MqttAsyncPublisher.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once -#include "IMqttPublisher.h" - -#include "MQTTAsync.h" -#include - -namespace mqtt -{ -class MqttAsyncPublisher final : 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(); - - bool connect() override; - bool disconnect() override; - bool reconnect() override; - MqttConnectionStatus isConnected() override; - void setServerURL(std::string serverUrl) override; - void setClientId(std::string clientId) override; - void setUsernamePasswrod(std::string username, std::string password) override; - void publishBirthCertificates() override; - - 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; - - - MqttClientType getClientType() const override; - std::string getServerUrl() const override; - -private: - std::string serverUrl; - std::string clientId; - std::string username; - std::string password; - - std::atomic pendingConnect; - - MQTTAsync client; - MQTTAsync_connectOptions connOpts; - MQTTAsync_createOptions createOpts; - MQTTAsync_SSLOptions sslOpts = MQTTAsync_SSLOptions_initializer; - - 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 onSend(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); -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h b/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h deleted file mode 100644 index 6ea9d644..00000000 --- a/mqtt_streaming_protocol/include/MqttAsyncSubscriber.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include "IMqttSubscriber.h" - -#include "MQTTAsync.h" -#include "MqttMessage.h" -#include -#include - -namespace mqtt -{ - -struct MqttSubscription { - std::string topic; - int qos; - - MqttSubscription(std::string t, int q) : topic(t), qos(q) {} -}; - -class MqttAsyncSubscriber final : 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); - - ~MqttAsyncSubscriber(); - - bool connect() override; - bool disconnect() override; - bool reconnect() override; - MqttConnectionStatus isConnected() override; - void setServerURL(std::string serverUrl) override; - void setClientId(std::string clientId) override; - void setUsernamePasswrod(std::string username, std::string password) override; - - MqttClientType getClientType() const override; - std::string getServerUrl() const override; - - bool subscribe(std::string topic, int qos) override; - bool unsubscribe(std::string topic) override; - bool unsubscribeAll() override; - - void setMessageArrivedCb(std::function cb) override; - void setMessageArrivedCb(std::string topic, std::function cb) override; - -private: - std::string serverUrl; - std::string clientId; - std::string username; - std::string password; - std::vector subscriptions; - std::recursive_mutex cbMtx; - std::atomic pendingConnect; - - MQTTAsync client; - MQTTAsync_connectOptions connOpts; - MQTTAsync_createOptions createOpts; - MQTTAsync_SSLOptions sslOpts; - - static int onMsgArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message); - static void onConnected(void* context, char* cause); - static void onConnectionLost(void* context, char* cause); - static void onSubscribeSuccess(void* context, MQTTAsync_successData* response); - static void onSubscribeFailure(void* context, MQTTAsync_failureData* response); - static void onConnectSuccess(void* context, MQTTAsync_successData data); - static void onConnectFailure(void* context, MQTTAsync_failureData data); - static void onDeliveryCompleted(void* context, MQTTAsync_token token); -}; -} // namespace mqtt diff --git a/mqtt_streaming_protocol/src/CMakeLists.txt b/mqtt_streaming_protocol/src/CMakeLists.txt index 4c7acb61..3bee608c 100644 --- a/mqtt_streaming_protocol/src/CMakeLists.txt +++ b/mqtt_streaming_protocol/src/CMakeLists.txt @@ -1,14 +1,9 @@ set(LIB_NAME mqtt_streaming_protocol) -set(SRC_Cpp MqttAsyncPublisher.cpp - MqttAsyncSubscriber.cpp +set(SRC_Cpp MqttAsyncClient.cpp ) -set(SRC_PublicHeaders IMqttBase.h - IMqttPublisher.h - MqttAsyncPublisher.h - IMqttSubscriber.h - MqttAsyncSubscriber.h +set(SRC_PublicHeaders MqttAsyncClient.h MqttMessage.h MqttSettings.h ) diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp new file mode 100644 index 00000000..d5bf2a37 --- /dev/null +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -0,0 +1,307 @@ +#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.ssl = &sslOpts; + connOpts.context = this; + createOpts = MQTTAsync_createOptions_initializer; + sslOpts = MQTTAsync_SSLOptions_initializer; +} + +MqttAsyncClient::~MqttAsyncClient() { + if (client != nullptr) { + disconnect(); + MQTTAsync_destroy(&client); + } +} + +bool MqttAsyncClient::connect() { + if (client != nullptr) { + // Signal stop reconnecting + disconnect(); + 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() { + return MQTTAsync_disconnect(client, NULL) == 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) { + 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 = MqttAsyncClient::onUnsubscribeSuccess; + opts.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) {} + +void MqttAsyncClient::onConnected(void *context, char *cause) { + if (context != nullptr) { + auto publisher = (MqttAsyncClient *) context; + publisher->pendingConnect = false; + auto lock = publisher->getCbLock(); + if (publisher->onConnectedCb) + publisher->onConnectedCb(); + } +} + +void MqttAsyncClient::onConnectionLost(void *context, char *cause) { + (void) cause; + // Reconnect procedure here! + if (context != nullptr) { + auto publisher = (MqttAsyncClient *) context; + publisher->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); + 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 publisher = (MqttAsyncClient *) context; + auto lock = publisher->getCbLock(); + if (publisher->onSentSuccessCb) + publisher->onSentSuccessCb(data->token); + } +} + +void MqttAsyncClient::onSendFailure(void *context, MQTTAsync_failureData *data) +{ + if (context != nullptr && data != nullptr) { + auto publisher = (MqttAsyncClient *) context; + auto lock = publisher->getCbLock(); + if (publisher->onSentFailCb) + publisher->onSentFailCb(data->token); + } +} + +void MqttAsyncClient::onConnectSuccess(void *context, MQTTAsync_successData data) +{ + // TODO : check when this is called + if (context != nullptr) { + auto publisher = (MqttAsyncClient *) context; + publisher->pendingConnect = false; + } +} + +void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData data) +{ + // TODO : check when this is called + if (context != nullptr) { + auto publisher = (MqttAsyncClient *) context; + publisher->pendingConnect = 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; +} +} // namespace mqtt diff --git a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp b/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp deleted file mode 100644 index 36e169dd..00000000 --- a/mqtt_streaming_protocol/src/MqttAsyncPublisher.cpp +++ /dev/null @@ -1,257 +0,0 @@ -#include "MqttAsyncPublisher.h" - -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) - , 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 *) &MqttAsyncPublisher::onConnectSuccess; - connOpts.onFailure = (MQTTAsync_onFailure *) &MqttAsyncPublisher::onConnectFailure; - connOpts.automaticReconnect = true; - connOpts.minRetryInterval = 1; - connOpts.maxRetryInterval = 10; - connOpts.ssl = &sslOpts; - connOpts.context = this; - createOpts = MQTTAsync_createOptions_initializer; - sslOpts = MQTTAsync_SSLOptions_initializer; -} - -MqttAsyncPublisher::~MqttAsyncPublisher() { - if (client != nullptr) { - disconnect(); - MQTTAsync_destroy(&client); - } -} - -bool MqttAsyncPublisher::connect() { - if (client != nullptr) { - // Signal stop reconnecting - disconnect(); - 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, - &MqttAsyncPublisher::onConnectionLost, - &MqttAsyncPublisher::onMsgArrived, - &MqttAsyncPublisher::onDeliveryCompleted); - - if (rc != MQTTASYNC_SUCCESS) { - return false; - } - - rc = MQTTAsync_setConnected(client, this, &MqttAsyncPublisher::onConnected); - if (rc != MQTTASYNC_SUCCESS) { - return false; - } - - rc = MQTTAsync_connect(client, &connOpts); - if (rc != MQTTASYNC_SUCCESS) { - return false; - } - pendingConnect = true; - return true; -} - -bool MqttAsyncPublisher::disconnect() { - return MQTTAsync_disconnect(client, NULL) == MQTTASYNC_SUCCESS; -} - -MqttConnectionStatus MqttAsyncPublisher::isConnected() { - if (pendingConnect) - return MqttConnectionStatus::pending; - - return MQTTAsync_isConnected(client) ? MqttConnectionStatus::connected - : MqttConnectionStatus::not_connected; -} - -void MqttAsyncPublisher::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; -} - -void MqttAsyncPublisher::publishBirthCertificates() {} - -bool MqttAsyncPublisher::publish(const std::string &topic, void *data, - size_t dataLen, std::string *err, int qos, - int *token, bool retained) { - 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::setServerURL(std::string serverUrl) { - if (serverUrl[0] == ':' || serverUrl == "") { - serverUrl = ""; - } else { - std::string ssl("ssl://"); - std::string tcp("tcp://"); - // Remove any protocol if available - if ((serverUrl.size() >= ssl.size() - && std::equal(ssl.begin(), ssl.end(), serverUrl.begin()))) { - serverUrl.erase(0, ssl.length()); - } - if ((serverUrl.size() >= tcp.size() - && std::equal(tcp.begin(), tcp.end(), serverUrl.begin()))) { - serverUrl.erase(0, tcp.length()); - } - - if (enableSSL) { - serverUrl = "ssl://" + serverUrl; - sslOpts.enableServerCertAuth = useCertificates && verifyServerCert - && trustStorePath != ""; - sslOpts.trustStore = (trustStorePath != "") ? trustStorePath.c_str() : nullptr; - sslOpts.keyStore = (useCertificates) ? clientCertPath.c_str() : nullptr; - sslOpts.privateKey = (useCertificates) ? privKeyPath.c_str() : nullptr; - } else { - serverUrl = "tcp://" + serverUrl; - } - } - this->serverUrl = serverUrl; -} - -void MqttAsyncPublisher::setClientId(std::string clientId) { - this->clientId = clientId; -} - -bool MqttAsyncPublisher::reconnect() { return false; } - -MqttClientType MqttAsyncPublisher::getClientType() const { - return MqttClientType::async; -} - -std::string MqttAsyncPublisher::getServerUrl() const { return serverUrl; } - -void MqttAsyncPublisher::onDeliveryCompleted(void *context, MQTTAsync_token token) {} - -void MqttAsyncPublisher::onConnected(void *context, char *cause) { - if (context != nullptr) { - auto publisher = (MqttAsyncPublisher *) context; - publisher->pendingConnect = false; - auto lock = publisher->getCbLock(); - if (publisher->onConnectedCb) - publisher->onConnectedCb(); - } -} - -void MqttAsyncPublisher::onConnectionLost(void *context, char *cause) { - (void) cause; - // Reconnect procedure here! - if (context != nullptr) { - auto publisher = (MqttAsyncPublisher *) context; - publisher->pendingConnect = false; - } -} - -int MqttAsyncPublisher::onMsgArrived(void *context, - char *topicName, - int topicLen, - MQTTAsync_message *message) -{ - if (message != nullptr) - MQTTAsync_freeMessage(&message); - MQTTAsync_free(topicName); - return 1; -} - -void MqttAsyncPublisher::onSend(void *context, MQTTAsync_successData *data) { - if (context != nullptr && data != nullptr) { - auto publisher = (MqttAsyncPublisher *) context; - auto lock = publisher->getCbLock(); - if (publisher->onSentSuccessCb) - publisher->onSentSuccessCb(data->token); - } -} - -void MqttAsyncPublisher::onSendFailure(void *context, MQTTAsync_failureData *data) -{ - if (context != nullptr && data != nullptr) { - auto publisher = (MqttAsyncPublisher *) context; - auto lock = publisher->getCbLock(); - if (publisher->onSentFailCb) - publisher->onSentFailCb(data->token); - } -} - -void MqttAsyncPublisher::onConnectSuccess(void *context, MQTTAsync_successData data) -{ - // TODO : check when this is called - if (context != nullptr) { - auto publisher = (MqttAsyncPublisher *) context; - publisher->pendingConnect = false; - } -} - -void MqttAsyncPublisher::onConnectFailure(void *context, MQTTAsync_failureData data) -{ - // TODO : check when this is called - if (context != nullptr) { - auto publisher = (MqttAsyncPublisher *) context; - publisher->pendingConnect = false; - } -} -} // namespace mqtt diff --git a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp b/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp deleted file mode 100644 index a9d0dc38..00000000 --- a/mqtt_streaming_protocol/src/MqttAsyncSubscriber.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include "MqttAsyncSubscriber.h" - -namespace mqtt { - -MqttAsyncSubscriber::MqttAsyncSubscriber() - : MqttAsyncSubscriber("", "", false, false, false, false, "", "", "", "") -{} - -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) - , 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 *) &MqttAsyncSubscriber::onConnectSuccess; - connOpts.onFailure = (MQTTAsync_onFailure *) &MqttAsyncSubscriber::onConnectFailure; - connOpts.automaticReconnect = true; - connOpts.minRetryInterval = 1; - connOpts.maxRetryInterval = 10; - connOpts.ssl = &sslOpts; - connOpts.context = this; - createOpts = MQTTAsync_createOptions_initializer; - sslOpts = MQTTAsync_SSLOptions_initializer; -} - -MqttAsyncSubscriber::~MqttAsyncSubscriber() { - unsubscribeAll(); - if (client != nullptr) { - disconnect(); - MQTTAsync_destroy(&client); - } -} - -bool MqttAsyncSubscriber::connect() { - if (client != nullptr) { - // Signal stop reconnecting - disconnect(); - 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, - &MqttAsyncSubscriber::onConnectionLost, - &MqttAsyncSubscriber::onMsgArrived, - &MqttAsyncSubscriber::onDeliveryCompleted); - if (rc != MQTTASYNC_SUCCESS) { - return false; - } - - rc = MQTTAsync_setConnected(client, this, &MqttAsyncSubscriber::onConnected); - if (rc != MQTTASYNC_SUCCESS) { - return false; - } - - rc = MQTTAsync_connect(client, &connOpts); - if (rc != MQTTASYNC_SUCCESS) { - return false; - } - - pendingConnect = true; - return true; -} - -bool MqttAsyncSubscriber::disconnect() { - return MQTTAsync_disconnect(client, NULL) == MQTTASYNC_SUCCESS; -} - -MqttConnectionStatus MqttAsyncSubscriber::isConnected() { - if (pendingConnect) - return MqttConnectionStatus::pending; - - return MQTTAsync_isConnected(client) ? MqttConnectionStatus::connected - : MqttConnectionStatus::not_connected; -} - -void MqttAsyncSubscriber::setServerURL(std::string serverUrl) { - if (serverUrl[0] == ':' || serverUrl == "") { - serverUrl = ""; - } else { - std::string ssl("ssl://"); - std::string tcp("tcp://"); - // Remove any protocol if available - if ((serverUrl.size() >= ssl.size() - && std::equal(ssl.begin(), ssl.end(), serverUrl.begin()))) { - serverUrl.erase(0, ssl.length()); - } - if ((serverUrl.size() >= tcp.size() - && std::equal(tcp.begin(), tcp.end(), serverUrl.begin()))) { - serverUrl.erase(0, tcp.length()); - } - - if (enableSSL) { - serverUrl = "ssl://" + serverUrl; - sslOpts.enableServerCertAuth = useCertificates && verifyServerCert - && trustStorePath != ""; - sslOpts.trustStore = (trustStorePath != "") ? trustStorePath.c_str() : nullptr; - sslOpts.keyStore = (useCertificates) ? clientCertPath.c_str() : nullptr; - sslOpts.privateKey = (useCertificates) ? privKeyPath.c_str() : nullptr; - } else { - serverUrl = "tcp://" + serverUrl; - } - } - this->serverUrl = serverUrl; -} - -void MqttAsyncSubscriber::setClientId(std::string clientId) { - this->clientId = clientId; -} - -void MqttAsyncSubscriber::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; -} - -MqttClientType MqttAsyncSubscriber::getClientType() const { - return MqttClientType::async; -} - -bool MqttAsyncSubscriber::unsubscribeAll() { - for (auto &sub : subscriptions) { - unsubscribe(sub.topic); - } - subscriptions.clear(); - - return true; -} - -bool MqttAsyncSubscriber::subscribe(std::string topic, int qos) { - MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - int rc; - opts.onSuccess = MqttAsyncSubscriber::onSubscribeSuccess; - opts.onFailure = MqttAsyncSubscriber::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 MqttAsyncSubscriber::unsubscribe(std::string topic) { - MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - opts.onSuccess = MqttAsyncSubscriber::onSubscribeSuccess; - opts.onFailure = MqttAsyncSubscriber::onSubscribeFailure; - 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; -} - -void MqttAsyncSubscriber::setMessageArrivedCb( - std::function cb) -{ - auto lock = getCbLock(); - onMsgArrivedCmnCb = cb; -} - -void MqttAsyncSubscriber::setMessageArrivedCb( - std::string topic, std::function cb) -{ - auto lock = getCbLock(); - onMsgArrivedCbs.insert({topic, cb}); -} - -bool MqttAsyncSubscriber::reconnect() { return true; } - -std::string MqttAsyncSubscriber::getServerUrl() const { return serverUrl; } - -void MqttAsyncSubscriber::onDeliveryCompleted(void *context, MQTTAsync_token token) {} - -int MqttAsyncSubscriber::onMsgArrived(void *context, - char *topicName, - int topicLen, - MQTTAsync_message *message) -{ - if (context != nullptr && message != nullptr) { - MqttAsyncSubscriber *subscriber = (MqttAsyncSubscriber *) context; - { - auto lock = subscriber->getCbLock(); - auto it = (topicName != nullptr) ? - subscriber->onMsgArrivedCbs.find(topicName) - : subscriber->onMsgArrivedCbs.end(); - if (subscriber->onMsgArrivedCmnCb || it != subscriber->onMsgArrivedCbs.end()) { - mqtt::MqttMessage msg; - msg.setTopic(topicName); - msg.addData((uint8_t *) message->payload, message->payloadlen); - if (subscriber->onMsgArrivedCmnCb) - subscriber->onMsgArrivedCmnCb(*subscriber, msg); - if (it != subscriber->onMsgArrivedCbs.end() && it->second) - it->second(*subscriber, msg); - } - } - } - if (message != nullptr) - MQTTAsync_freeMessage(&message); - MQTTAsync_free(topicName); - return 1; -} - -void MqttAsyncSubscriber::onConnected(void *context, char *cause) { - if (context != nullptr) { - auto subscriber = (MqttAsyncSubscriber *) context; - subscriber->pendingConnect = false; - auto lock = subscriber->getCbLock(); - if (subscriber->onConnectedCb) - subscriber->onConnectedCb(); - } -} -void MqttAsyncSubscriber::onConnectionLost(void *context, char *cause) { - // Reconnect procedure here! -} - -void MqttAsyncSubscriber::onSubscribeSuccess(void *context, MQTTAsync_successData *response) {} - -void MqttAsyncSubscriber::onSubscribeFailure(void *context, MQTTAsync_failureData *response) {} - -void MqttAsyncSubscriber::onConnectSuccess(void *context, MQTTAsync_successData data) -{ - // This callback only fires onec when call to MQTTAsync_connect is called. -} - -void MqttAsyncSubscriber::onConnectFailure(void *context, MQTTAsync_failureData data) -{ - if (context != nullptr) { - auto subscriber = (MqttAsyncSubscriber *) context; - subscriber->pendingConnect = false; - } -} - -} // namespace mqtt diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp index d78cb42b..33603214 100644 --- a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -1,27 +1,27 @@ -#include "MqttAsyncPublisher.h" -#include "MqttAsyncSubscriber.h" +#include "MqttAsyncClient.h" #include #include #include using namespace mqtt; -class MqttStreamingProtocolTest : public ::testing::TestWithParam> { +class MqttStreamingProtocolTest : public ::testing::Test { protected: - std::shared_ptr instance; + std::shared_ptr instance; std::promise connectedPromise; std::future connectedFuture; int successTimeout = 5000; int failureTimeout = 3000; + std::string clientId = "testMqttClientId"; void SetUp() override { - instance = GetParam(); + instance = std::make_shared(); } void TearDown() override { - instance->disconnect(); + instance.reset(); } bool createConnection(std::string url, std::string id) { @@ -31,16 +31,16 @@ class MqttStreamingProtocolTest : public ::testing::TestWithParam(); connectedFuture = connectedPromise.get_future(); - instance->setOnConnected([this]() { + instance->setConnectedCb([this]() { connectedPromise.set_value(true); }); return instance->connect(); } }; -TEST_P(MqttStreamingProtocolTest, Connection) +TEST_F(MqttStreamingProtocolTest, Connection) { - auto ok = createConnection("127.0.0.1", "testPublisherId"); + auto ok = createConnection("127.0.0.1", clientId); ASSERT_TRUE(ok); auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); @@ -49,9 +49,9 @@ TEST_P(MqttStreamingProtocolTest, Connection) ASSERT_TRUE(connectedFuture.get()); } -TEST_P(MqttStreamingProtocolTest, WrongUrlConnection) +TEST_F(MqttStreamingProtocolTest, WrongUrlConnection) { - auto ok = createConnection("", "testPublisherId"); + auto ok = createConnection("", clientId); ASSERT_FALSE(ok); auto status = connectedFuture.wait_for(std::chrono::milliseconds(failureTimeout)); @@ -59,7 +59,7 @@ TEST_P(MqttStreamingProtocolTest, WrongUrlConnection) ASSERT_TRUE(status == std::future_status::timeout); } -TEST_P(MqttStreamingProtocolTest, WrongIdConnection) +TEST_F(MqttStreamingProtocolTest, WrongIdConnection) { auto ok = createConnection("127.0.0.1", ""); ASSERT_FALSE(ok); @@ -69,9 +69,9 @@ TEST_P(MqttStreamingProtocolTest, WrongIdConnection) ASSERT_TRUE(status == std::future_status::timeout); } -TEST_P(MqttStreamingProtocolTest, WrongPortConnection) +TEST_F(MqttStreamingProtocolTest, WrongPortConnection) { - auto ok = createConnection("127.0.0.1:1888", "testPublisherId"); + auto ok = createConnection("127.0.0.1:1888", clientId); ASSERT_TRUE(ok); auto status = connectedFuture.wait_for(std::chrono::milliseconds(failureTimeout)); @@ -79,9 +79,9 @@ TEST_P(MqttStreamingProtocolTest, WrongPortConnection) ASSERT_TRUE(status == std::future_status::timeout); } -TEST_P(MqttStreamingProtocolTest, Connected) +TEST_F(MqttStreamingProtocolTest, Connected) { - auto ok = createConnection("127.0.0.1", "testPublisherId"); + auto ok = createConnection("127.0.0.1", clientId); ASSERT_TRUE(ok); auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); @@ -91,9 +91,9 @@ TEST_P(MqttStreamingProtocolTest, Connected) ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::connected); } -TEST_P(MqttStreamingProtocolTest, Disconnection) +TEST_F(MqttStreamingProtocolTest, Disconnection) { - auto ok = createConnection("127.0.0.1", "testPublisherId"); + auto ok = createConnection("127.0.0.1", clientId); ASSERT_TRUE(ok); auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); @@ -108,16 +108,7 @@ TEST_P(MqttStreamingProtocolTest, Disconnection) // ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::not_connected); } -TEST_P(MqttStreamingProtocolTest, NotConnected) +TEST_F(MqttStreamingProtocolTest, NotConnected) { ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::not_connected); } - -// Instantiate with values -INSTANTIATE_TEST_SUITE_P(PublisherGroup, - MqttStreamingProtocolTest, - ::testing::Values(std::make_shared())); - -INSTANTIATE_TEST_SUITE_P(SubscriberGroup, - MqttStreamingProtocolTest, - ::testing::Values(std::make_shared())); 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 4d8ed5e1..0de065c8 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 @@ -25,7 +25,7 @@ //#include #include #include -#include +#include #include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_SERVER_MODULE @@ -90,7 +90,7 @@ class MqttStreamingServerImpl : public daq::Server bool serverStopped; size_t maxPacketReadCount; std::chrono::milliseconds processingThreadSleepTime; - mqtt::MqttAsyncPublisher publisher; + mqtt::MqttAsyncClient publisher; Mqtt::Utils::Settings::MqttConnectionSettings connectionSettings; std::mutex readersSync; bool processingThreadRunning; 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 3313e1f4..666ccdf4 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -162,7 +162,7 @@ void MqttStreamingServerImpl::setupMqttPublisher() publisher.setServerURL(connectionSettings.mqttUrl); publisher.setClientId(connectionSettings.clientId); publisher.setUsernamePasswrod(connectionSettings.username, connectionSettings.password); - publisher.setOnConnected([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); publisher.connect(); From a7b6cc3a22c1bff2533b33ad1586743260f80f2e Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 25 Sep 2025 15:26:11 +0200 Subject: [PATCH 17/46] mqtt: deleta move and copy constructor/operator= of MqttAsyncClient --- mqtt_streaming_protocol/include/MqttAsyncClient.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h index fcb805d2..d0d1ccab 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncClient.h +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -33,6 +33,10 @@ class MqttAsyncClient final 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(); From d38f2a58374982c5e0c2bacc1a859cc1e5700afd Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 25 Sep 2025 15:32:33 +0200 Subject: [PATCH 18/46] mqtt: renaming --- .../src/MqttAsyncClient.cpp | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index d5bf2a37..d74e9bb2 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -185,11 +185,11 @@ void MqttAsyncClient::onDeliveryCompleted(void *context, MQTTAsync_token token) void MqttAsyncClient::onConnected(void *context, char *cause) { if (context != nullptr) { - auto publisher = (MqttAsyncClient *) context; - publisher->pendingConnect = false; - auto lock = publisher->getCbLock(); - if (publisher->onConnectedCb) - publisher->onConnectedCb(); + auto clienttInst = (MqttAsyncClient *) context; + clienttInst->pendingConnect = false; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onConnectedCb) + clienttInst->onConnectedCb(); } } @@ -197,8 +197,8 @@ void MqttAsyncClient::onConnectionLost(void *context, char *cause) { (void) cause; // Reconnect procedure here! if (context != nullptr) { - auto publisher = (MqttAsyncClient *) context; - publisher->pendingConnect = false; + auto clienttInst = (MqttAsyncClient *) context; + clienttInst->pendingConnect = false; } } @@ -233,20 +233,20 @@ int MqttAsyncClient::onMsgArrived(void *context, void MqttAsyncClient::onSendSuccess(void *context, MQTTAsync_successData *data) { if (context != nullptr && data != nullptr) { - auto publisher = (MqttAsyncClient *) context; - auto lock = publisher->getCbLock(); - if (publisher->onSentSuccessCb) - publisher->onSentSuccessCb(data->token); + auto clienttInst = (MqttAsyncClient *) context; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onSentSuccessCb) + clienttInst->onSentSuccessCb(data->token); } } void MqttAsyncClient::onSendFailure(void *context, MQTTAsync_failureData *data) { if (context != nullptr && data != nullptr) { - auto publisher = (MqttAsyncClient *) context; - auto lock = publisher->getCbLock(); - if (publisher->onSentFailCb) - publisher->onSentFailCb(data->token); + auto clienttInst = (MqttAsyncClient *) context; + auto lock = clienttInst->getCbLock(); + if (clienttInst->onSentFailCb) + clienttInst->onSentFailCb(data->token); } } @@ -254,8 +254,8 @@ void MqttAsyncClient::onConnectSuccess(void *context, MQTTAsync_successData data { // TODO : check when this is called if (context != nullptr) { - auto publisher = (MqttAsyncClient *) context; - publisher->pendingConnect = false; + auto clienttInst = (MqttAsyncClient *) context; + clienttInst->pendingConnect = false; } } @@ -263,8 +263,8 @@ void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData data { // TODO : check when this is called if (context != nullptr) { - auto publisher = (MqttAsyncClient *) context; - publisher->pendingConnect = false; + auto clienttInst = (MqttAsyncClient *) context; + clienttInst->pendingConnect = false; } } From 43d0d1f509f8c71a16efbbe8b101019cb5f74650 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 25 Sep 2025 16:13:36 +0200 Subject: [PATCH 19/46] mqtt: MqttAsyncClient disconnection improving; tests --- .../include/MqttAsyncClient.h | 9 +++- .../src/MqttAsyncClient.cpp | 49 +++++++++++++++---- .../tests/test_mqtt_streaming_protocol.cpp | 30 +++++++++++- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h index d0d1ccab..41cff591 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncClient.h +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -57,6 +57,7 @@ class MqttAsyncClient final void setConnectedCb(std::function cb); void setMessageArrivedCb(std::string topic, std::function cb); void setMessageArrivedCb(std::function cb); + void setDisconnectCb(std::function cb); void setServerURL(std::string serverUrl); std::string getServerUrl() const; @@ -74,6 +75,7 @@ class MqttAsyncClient final MQTTAsync client; MQTTAsync_connectOptions connOpts; + MQTTAsync_disconnectOptions disconnOpts; MQTTAsync_createOptions createOpts; MQTTAsync_SSLOptions sslOpts = MQTTAsync_SSLOptions_initializer; @@ -81,6 +83,7 @@ class MqttAsyncClient final std::function onConnectedCb; std::function onSentSuccessCb; + std::function onDisconnectCb; std::function onSentFailCb; std::function onMsgArrivedCmnCb; std::unordered_map> onMsgArrivedCbs; @@ -95,8 +98,10 @@ class MqttAsyncClient final 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 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); diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index d74e9bb2..b367d2db 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -27,21 +27,24 @@ MqttAsyncClient::MqttAsyncClient(std::string serverUrl, connOpts.maxRetryInterval = 10; connOpts.ssl = &sslOpts; 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; sslOpts = MQTTAsync_SSLOptions_initializer; } MqttAsyncClient::~MqttAsyncClient() { if (client != nullptr) { - disconnect(); MQTTAsync_destroy(&client); } } bool MqttAsyncClient::connect() { if (client != nullptr) { - // Signal stop reconnecting - disconnect(); MQTTAsync_destroy(&client); } @@ -84,7 +87,8 @@ bool MqttAsyncClient::connect() { } bool MqttAsyncClient::disconnect() { - return MQTTAsync_disconnect(client, NULL) == MQTTASYNC_SUCCESS; + // It is only the result of the request to disconnect (queuing) + return MQTTAsync_disconnect(client, &disconnOpts) == MQTTASYNC_SUCCESS; } MqttConnectionStatus MqttAsyncClient::isConnected() const{ @@ -97,7 +101,7 @@ MqttConnectionStatus MqttAsyncClient::isConnected() const{ std::lock_guard MqttAsyncClient::getCbLock() { - return std::lock_guard(cbMtx); + return std::lock_guard(cbMtx); } void MqttAsyncClient::setUsernamePasswrod(std::string username, std::string password) @@ -147,8 +151,8 @@ bool MqttAsyncClient::subscribe(std::string topic, int qos) { bool MqttAsyncClient::unsubscribe(std::string topic) { MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; - opts.onSuccess = MqttAsyncClient::onUnsubscribeSuccess; - opts.onFailure = MqttAsyncClient::onUnsubscribeFailure; + 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(), @@ -250,7 +254,7 @@ void MqttAsyncClient::onSendFailure(void *context, MQTTAsync_failureData *data) } } -void MqttAsyncClient::onConnectSuccess(void *context, MQTTAsync_successData data) +void MqttAsyncClient::onConnectSuccess(void *context, MQTTAsync_successData* data) { // TODO : check when this is called if (context != nullptr) { @@ -259,7 +263,7 @@ void MqttAsyncClient::onConnectSuccess(void *context, MQTTAsync_successData data } } -void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData data) +void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData* data) { // TODO : check when this is called if (context != nullptr) { @@ -268,6 +272,28 @@ void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData data } } +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) { @@ -304,4 +330,9 @@ void MqttAsyncClient::setMessageArrivedCb(std::function cb) { auto lock = getCbLock(); onMsgArrivedCmnCb = cb; } + +void MqttAsyncClient::setDisconnectCb(std::function cb) { + auto lock = getCbLock(); + onDisconnectCb = cb; +} } // namespace mqtt diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp index 33603214..84f1c258 100644 --- a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -49,6 +49,25 @@ TEST_F(MqttStreamingProtocolTest, Connection) ASSERT_TRUE(connectedFuture.get()); } +TEST_F(MqttStreamingProtocolTest, Reconnection) +{ + auto ok = createConnection("127.0.0.1", clientId); + ASSERT_TRUE(ok); + + auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); + + ASSERT_TRUE(status == std::future_status::ready); + ASSERT_TRUE(connectedFuture.get()); + + ok = createConnection("127.0.0.1", clientId); + ASSERT_TRUE(ok); + + status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); + + ASSERT_TRUE(status == std::future_status::ready); + ASSERT_TRUE(connectedFuture.get()); +} + TEST_F(MqttStreamingProtocolTest, WrongUrlConnection) { auto ok = createConnection("", clientId); @@ -105,7 +124,16 @@ TEST_F(MqttStreamingProtocolTest, Disconnection) auto disconnectionOk = instance->disconnect(); ASSERT_TRUE(disconnectionOk); // It is necessary to give the client time to disconnect. - // ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::not_connected); + std::promise disconnectedPromise; + auto disconnectedFuture = disconnectedPromise.get_future(); + instance->setDisconnectCb([promise = &disconnectedPromise](bool result) { + promise->set_value(result); + }); + status = disconnectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); + + ASSERT_TRUE(status == std::future_status::ready); + ASSERT_TRUE(disconnectedFuture.get()); + ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::not_connected); } TEST_F(MqttStreamingProtocolTest, NotConnected) From 93cc3140eb383d8867a296cbb8b0e103904f1a5d Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 25 Sep 2025 16:16:08 +0200 Subject: [PATCH 20/46] mqtt: only formatting --- .../include/MqttAsyncClient.h | 61 ++++++++-------- .../src/MqttAsyncClient.cpp | 69 ++++++++----------- 2 files changed, 59 insertions(+), 71 deletions(-) diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h index 41cff591..2cc0786d 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncClient.h +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -1,15 +1,14 @@ #pragma once +#include #include #include #include -#include #include "MQTTAsync.h" #include "MqttMessage.h" -namespace mqtt -{ +namespace mqtt { enum class MqttConnectionStatus { not_connected, @@ -21,22 +20,22 @@ struct MqttSubscription { std::string topic; int qos; - MqttSubscription(std::string t, int q) : topic(t), qos(q) {} + MqttSubscription(std::string t, int q) + : topic(t) + , qos(q) + {} }; -class MqttAsyncClient final -{ +class MqttAsyncClient final { public: - using MsgArrivedCb_type = void(const MqttAsyncClient&, mqtt::MqttMessage& msg); + 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(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(); @@ -92,20 +91,22 @@ class MqttAsyncClient final 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); - + 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 +} // namespace mqtt diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index b367d2db..5d00ba68 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -2,13 +2,9 @@ namespace mqtt { -MqttAsyncClient::MqttAsyncClient() - : MqttAsyncClient("", "", false) -{} +MqttAsyncClient::MqttAsyncClient() : MqttAsyncClient("", "", false) {} -MqttAsyncClient::MqttAsyncClient(std::string serverUrl, - std::string clientId, - bool cleanSession) +MqttAsyncClient::MqttAsyncClient(std::string serverUrl, std::string clientId, bool cleanSession) : clientId(clientId) , username("") , password("") @@ -91,7 +87,7 @@ bool MqttAsyncClient::disconnect() { return MQTTAsync_disconnect(client, &disconnOpts) == MQTTASYNC_SUCCESS; } -MqttConnectionStatus MqttAsyncClient::isConnected() const{ +MqttConnectionStatus MqttAsyncClient::isConnected() const { if (pendingConnect) return MqttConnectionStatus::pending; @@ -99,8 +95,7 @@ MqttConnectionStatus MqttAsyncClient::isConnected() const{ : MqttConnectionStatus::not_connected; } -std::lock_guard MqttAsyncClient::getCbLock() -{ +std::lock_guard MqttAsyncClient::getCbLock() { return std::lock_guard(cbMtx); } @@ -113,9 +108,14 @@ 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) { +bool MqttAsyncClient::publish(const std::string &topic, + void *data, + size_t dataLen, + std::string *err, + int qos, + int *token, + bool retained) +{ MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; MQTTAsync_message pubmsg = MQTTAsync_message_initializer; int rc; @@ -207,16 +207,15 @@ void MqttAsyncClient::onConnectionLost(void *context, char *cause) { } int MqttAsyncClient::onMsgArrived(void *context, - char *topicName, - int topicLen, - MQTTAsync_message *message) + 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) + auto it = (topicName != nullptr) ? client->onMsgArrivedCbs.find(topicName) : client->onMsgArrivedCbs.end(); if (client->onMsgArrivedCmnCb || it != client->onMsgArrivedCbs.end()) { mqtt::MqttMessage msg; @@ -235,7 +234,8 @@ int MqttAsyncClient::onMsgArrived(void *context, return 1; } -void MqttAsyncClient::onSendSuccess(void *context, MQTTAsync_successData *data) { +void MqttAsyncClient::onSendSuccess(void *context, MQTTAsync_successData *data) +{ if (context != nullptr && data != nullptr) { auto clienttInst = (MqttAsyncClient *) context; auto lock = clienttInst->getCbLock(); @@ -254,7 +254,7 @@ void MqttAsyncClient::onSendFailure(void *context, MQTTAsync_failureData *data) } } -void MqttAsyncClient::onConnectSuccess(void *context, MQTTAsync_successData* data) +void MqttAsyncClient::onConnectSuccess(void *context, MQTTAsync_successData *data) { // TODO : check when this is called if (context != nullptr) { @@ -263,7 +263,7 @@ void MqttAsyncClient::onConnectSuccess(void *context, MQTTAsync_successData* dat } } -void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData* data) +void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData *data) { // TODO : check when this is called if (context != nullptr) { @@ -272,7 +272,7 @@ void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData* dat } } -void MqttAsyncClient::onDisconnectSuccess(void *context, MQTTAsync_successData* data) +void MqttAsyncClient::onDisconnectSuccess(void *context, MQTTAsync_successData *data) { // TODO : check when this is called if (context != nullptr) { @@ -283,7 +283,7 @@ void MqttAsyncClient::onDisconnectSuccess(void *context, MQTTAsync_successData* } } -void MqttAsyncClient::onDisconnectFailure(void *context, MQTTAsync_failureData* data) +void MqttAsyncClient::onDisconnectFailure(void *context, MQTTAsync_failureData *data) { // TODO : check when this is called if (context != nullptr) { @@ -294,29 +294,16 @@ void MqttAsyncClient::onDisconnectFailure(void *context, MQTTAsync_failureData* } } -void MqttAsyncClient::onSubscribeSuccess(void* context, MQTTAsync_successData* response) -{ +void MqttAsyncClient::onSubscribeSuccess(void *context, MQTTAsync_successData *response) {} -} +void MqttAsyncClient::onSubscribeFailure(void *context, MQTTAsync_failureData *response) {} -void MqttAsyncClient::onSubscribeFailure(void* context, MQTTAsync_failureData* response) -{ - -} +void MqttAsyncClient::onUnsubscribeSuccess(void *context, MQTTAsync_successData *response) {} -void MqttAsyncClient::onUnsubscribeSuccess(void* context, MQTTAsync_successData* response) -{ +void MqttAsyncClient::onUnsubscribeFailure(void *context, MQTTAsync_failureData *response) {} -} - -void MqttAsyncClient::onUnsubscribeFailure(void* context, MQTTAsync_failureData* response) -{ - -} - -void MqttAsyncClient::setConnectedCb(std::function cb) -{ - auto lock = getCbLock(); +void MqttAsyncClient::setConnectedCb(std::function cb) { + auto lock = getCbLock(); onConnectedCb = cb; } From 795a68076e42b633f8d503bfe57ac7e4e521466a Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 25 Sep 2025 18:03:52 +0200 Subject: [PATCH 21/46] mqtt: MqttAsyncClient improving; tests --- .../include/MqttAsyncClient.h | 6 +- .../src/MqttAsyncClient.cpp | 59 +++++++++++++--- .../tests/test_mqtt_streaming_protocol.cpp | 67 ++++++++++++++++++- 3 files changed, 120 insertions(+), 12 deletions(-) diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h index 2cc0786d..ea642c4b 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncClient.h +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -57,6 +57,8 @@ class MqttAsyncClient final { 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; @@ -81,9 +83,9 @@ class MqttAsyncClient final { std::recursive_mutex cbMtx; std::function onConnectedCb; - std::function onSentSuccessCb; + std::function onSentCb; std::function onDisconnectCb; - std::function onSentFailCb; + std::function onDeliveryCompletedCb; std::function onMsgArrivedCmnCb; std::unordered_map> onMsgArrivedCbs; diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index 5d00ba68..67eaf399 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -116,6 +116,26 @@ bool MqttAsyncClient::publish(const std::string &topic, 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; @@ -185,7 +205,15 @@ void MqttAsyncClient::setClientId(std::string clientId) { std::string MqttAsyncClient::getServerUrl() const { return serverUrl; } -void MqttAsyncClient::onDeliveryCompleted(void *context, MQTTAsync_token token) {} +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) { @@ -239,8 +267,8 @@ void MqttAsyncClient::onSendSuccess(void *context, MQTTAsync_successData *data) if (context != nullptr && data != nullptr) { auto clienttInst = (MqttAsyncClient *) context; auto lock = clienttInst->getCbLock(); - if (clienttInst->onSentSuccessCb) - clienttInst->onSentSuccessCb(data->token); + if (clienttInst->onSentCb) + clienttInst->onSentCb(data->token, true); } } @@ -249,8 +277,8 @@ void MqttAsyncClient::onSendFailure(void *context, MQTTAsync_failureData *data) if (context != nullptr && data != nullptr) { auto clienttInst = (MqttAsyncClient *) context; auto lock = clienttInst->getCbLock(); - if (clienttInst->onSentFailCb) - clienttInst->onSentFailCb(data->token); + if (clienttInst->onSentCb) + clienttInst->onSentCb(data->token, false); } } @@ -302,7 +330,8 @@ void MqttAsyncClient::onUnsubscribeSuccess(void *context, MQTTAsync_successData void MqttAsyncClient::onUnsubscribeFailure(void *context, MQTTAsync_failureData *response) {} -void MqttAsyncClient::setConnectedCb(std::function cb) { +void MqttAsyncClient::setConnectedCb(std::function cb) +{ auto lock = getCbLock(); onConnectedCb = cb; } @@ -313,13 +342,27 @@ void MqttAsyncClient::setMessageArrivedCb(std::string topic, std::function cb) { +void MqttAsyncClient::setMessageArrivedCb(std::function cb) +{ auto lock = getCbLock(); onMsgArrivedCmnCb = cb; } -void MqttAsyncClient::setDisconnectCb(std::function 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/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp index 84f1c258..196877c5 100644 --- a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -36,6 +36,15 @@ class MqttStreamingProtocolTest : public ::testing::Test { }); return instance->connect(); } + + bool connect(std::string url, std::string id) { + bool res = createConnection(url, id); + if (res) { + auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); + res = (status == std::future_status::ready && connectedFuture.get() == true); + } + return res; + } }; TEST_F(MqttStreamingProtocolTest, Connection) @@ -121,14 +130,16 @@ TEST_F(MqttStreamingProtocolTest, Disconnection) ASSERT_TRUE(connectedFuture.get()); ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::connected); - auto disconnectionOk = instance->disconnect(); - ASSERT_TRUE(disconnectionOk); // It is necessary to give the client time to disconnect. std::promise disconnectedPromise; auto disconnectedFuture = disconnectedPromise.get_future(); instance->setDisconnectCb([promise = &disconnectedPromise](bool result) { promise->set_value(result); }); + + auto disconnectionOk = instance->disconnect(); + ASSERT_TRUE(disconnectionOk); + status = disconnectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); ASSERT_TRUE(status == std::future_status::ready); @@ -140,3 +151,55 @@ TEST_F(MqttStreamingProtocolTest, NotConnected) { ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::not_connected); } + +TEST_F(MqttStreamingProtocolTest, PublishingWithoutDataControl) +{ + auto ok = connect("127.0.0.1", clientId); + ASSERT_TRUE(ok); + + int token = 0; + std::promise sendPromise; + auto sendFuture = sendPromise.get_future(); + instance->setSentCb([promise = &sendPromise, token = &token](int receivedToken, bool result) { + if (receivedToken == *token) + promise->set_value(result); + }); + + std::promise deliveryPromise; + auto deliveryFuture = deliveryPromise.get_future(); + instance->setDeliveryCompletedCb([promise = &deliveryPromise, token = &token](int receivedToken) { + if (receivedToken == *token) + promise->set_value(true); + }); + + const std::string topic = "test/topic"; + const std::string data = "test data"; + + const auto start{std::chrono::steady_clock::now()}; + ok = instance->publish(topic, (void *)(data.c_str()), data.size(), nullptr, 1, &token, false); + ASSERT_TRUE(ok); + ASSERT_TRUE(token != 0); + + + auto tout = std::chrono::milliseconds(successTimeout); + auto status = sendFuture.wait_for(tout); + ASSERT_TRUE(status == std::future_status::ready); + ASSERT_TRUE(sendFuture.get()); + + const auto elapsed_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start); + std::chrono::milliseconds newTout = (elapsed_ms >= tout) ? std::chrono::milliseconds(0) + : tout - elapsed_ms; + status = deliveryFuture.wait_for(newTout); + ASSERT_TRUE(status == std::future_status::ready); + ASSERT_TRUE(deliveryFuture.get()); +} + +TEST_F(MqttStreamingProtocolTest, PublishingWithoutConnection) +{ + const std::string topic = "test/topic"; + 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); +} From 1c054ebb496480ef4f89eedb940fbb92e977687b Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 26 Sep 2025 18:05:46 +0200 Subject: [PATCH 22/46] mqtt: MqttAsyncClient improving; tests --- mqtt_streaming_protocol/include/MqttMessage.h | 23 +- .../src/MqttAsyncClient.cpp | 2 + .../tests/test_mqtt_streaming_protocol.cpp | 347 ++++++++++++++---- 3 files changed, 302 insertions(+), 70 deletions(-) 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/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index 67eaf399..f8f7a7fa 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -249,6 +249,8 @@ int MqttAsyncClient::onMsgArrived(void *context, 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) diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp index 196877c5..37950d47 100644 --- a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -2,29 +2,49 @@ #include #include #include +#include using namespace mqtt; +using namespace std::chrono; -class MqttStreamingProtocolTest : public ::testing::Test { -protected: - std::shared_ptr instance; - - std::promise connectedPromise; - std::future connectedFuture; - - int successTimeout = 5000; - int failureTimeout = 3000; - std::string clientId = "testMqttClientId"; - - void SetUp() override { - instance = std::make_shared(); +class Timer +{ +public: + Timer(int ms) + { + start = steady_clock::now(); + timeout = milliseconds(ms); } - - void TearDown() override { - instance.reset(); + 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; +}; - bool createConnection(std::string url, std::string id) { +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); @@ -37,14 +57,101 @@ class MqttStreamingProtocolTest : public ::testing::Test { return instance->connect(); } - bool connect(std::string url, std::string id) { + 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(std::chrono::milliseconds(successTimeout)); + 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; + + 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(); + } + + void TearDown() override { + instance.reset(); + } }; TEST_F(MqttStreamingProtocolTest, Connection) @@ -52,29 +159,19 @@ TEST_F(MqttStreamingProtocolTest, Connection) auto ok = createConnection("127.0.0.1", clientId); ASSERT_TRUE(ok); - auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); - + auto status = connectedFuture.wait_for(Timer(successTimeout).remain()); + instance->setConnectedCb(nullptr); ASSERT_TRUE(status == std::future_status::ready); ASSERT_TRUE(connectedFuture.get()); } TEST_F(MqttStreamingProtocolTest, Reconnection) { - auto ok = createConnection("127.0.0.1", clientId); + auto ok = connect("127.0.0.1", clientId); ASSERT_TRUE(ok); - auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); - - ASSERT_TRUE(status == std::future_status::ready); - ASSERT_TRUE(connectedFuture.get()); - - ok = createConnection("127.0.0.1", clientId); + ok = connect("127.0.0.1", clientId); ASSERT_TRUE(ok); - - status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); - - ASSERT_TRUE(status == std::future_status::ready); - ASSERT_TRUE(connectedFuture.get()); } TEST_F(MqttStreamingProtocolTest, WrongUrlConnection) @@ -82,8 +179,8 @@ TEST_F(MqttStreamingProtocolTest, WrongUrlConnection) auto ok = createConnection("", clientId); ASSERT_FALSE(ok); - auto status = connectedFuture.wait_for(std::chrono::milliseconds(failureTimeout)); - + auto status = connectedFuture.wait_for(Timer(failureTimeout).remain()); + instance->setConnectedCb(nullptr); ASSERT_TRUE(status == std::future_status::timeout); } @@ -92,8 +189,8 @@ TEST_F(MqttStreamingProtocolTest, WrongIdConnection) auto ok = createConnection("127.0.0.1", ""); ASSERT_FALSE(ok); - auto status = connectedFuture.wait_for(std::chrono::milliseconds(failureTimeout)); - + auto status = connectedFuture.wait_for(Timer(failureTimeout).remain()); + instance->setConnectedCb(nullptr); ASSERT_TRUE(status == std::future_status::timeout); } @@ -102,20 +199,15 @@ TEST_F(MqttStreamingProtocolTest, WrongPortConnection) auto ok = createConnection("127.0.0.1:1888", clientId); ASSERT_TRUE(ok); - auto status = connectedFuture.wait_for(std::chrono::milliseconds(failureTimeout)); - + auto status = connectedFuture.wait_for(Timer(failureTimeout).remain()); + instance->setConnectedCb(nullptr); ASSERT_TRUE(status == std::future_status::timeout); } TEST_F(MqttStreamingProtocolTest, Connected) { - auto ok = createConnection("127.0.0.1", clientId); + auto ok = connect("127.0.0.1", clientId); ASSERT_TRUE(ok); - - auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); - - ASSERT_TRUE(status == std::future_status::ready); - ASSERT_TRUE(connectedFuture.get()); ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::connected); } @@ -124,24 +216,29 @@ TEST_F(MqttStreamingProtocolTest, Disconnection) auto ok = createConnection("127.0.0.1", clientId); ASSERT_TRUE(ok); - auto status = connectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); - + Timer timer(successTimeout); + auto status = connectedFuture.wait_for(timer.remain()); + instance->setConnectedCb(nullptr); ASSERT_TRUE(status == std::future_status::ready); ASSERT_TRUE(connectedFuture.get()); ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::connected); // It is necessary to give the client time to disconnect. + std::atomic done{false}; std::promise disconnectedPromise; auto disconnectedFuture = disconnectedPromise.get_future(); - instance->setDisconnectCb([promise = &disconnectedPromise](bool result) { - promise->set_value(result); + 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(); ASSERT_TRUE(disconnectionOk); - status = disconnectedFuture.wait_for(std::chrono::milliseconds(successTimeout)); - + status = disconnectedFuture.wait_for(timer.remain()); + instance->setDisconnectCb(nullptr); ASSERT_TRUE(status == std::future_status::ready); ASSERT_TRUE(disconnectedFuture.get()); ASSERT_TRUE(instance->isConnected() == MqttConnectionStatus::not_connected); @@ -158,46 +255,160 @@ TEST_F(MqttStreamingProtocolTest, PublishingWithoutDataControl) ASSERT_TRUE(ok); int token = 0; + std::atomic sendDone{false}; std::promise sendPromise; auto sendFuture = sendPromise.get_future(); - instance->setSentCb([promise = &sendPromise, token = &token](int receivedToken, bool result) { - if (receivedToken == *token) - promise->set_value(result); + instance->setSentCb([promise = &sendPromise, token = &token, &sendDone](int receivedToken, bool result) { + bool expected = false; + if (receivedToken == *token) { + if (sendDone.compare_exchange_strong(expected, true)) { + promise->set_value(true); + } + } }); + std::atomic deliveryDone{false}; std::promise deliveryPromise; auto deliveryFuture = deliveryPromise.get_future(); - instance->setDeliveryCompletedCb([promise = &deliveryPromise, token = &token](int receivedToken) { - if (receivedToken == *token) - promise->set_value(true); - }); + instance->setDeliveryCompletedCb( + [promise = &deliveryPromise, token = &token, &deliveryDone](int receivedToken) { + bool expected = false; + if (receivedToken == *token) { + if (deliveryDone.compare_exchange_strong(expected, true)) { + promise->set_value(true); + } + } + }); - const std::string topic = "test/topic"; + const std::string topic = buildTopicName(); const std::string data = "test data"; - const auto start{std::chrono::steady_clock::now()}; ok = instance->publish(topic, (void *)(data.c_str()), data.size(), nullptr, 1, &token, false); ASSERT_TRUE(ok); ASSERT_TRUE(token != 0); - auto tout = std::chrono::milliseconds(successTimeout); - auto status = sendFuture.wait_for(tout); + Timer timer(successTimeout); + auto status = sendFuture.wait_for(timer.remain()); + instance->setSentCb(nullptr); ASSERT_TRUE(status == std::future_status::ready); ASSERT_TRUE(sendFuture.get()); - const auto elapsed_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start); - std::chrono::milliseconds newTout = (elapsed_ms >= tout) ? std::chrono::milliseconds(0) - : tout - elapsed_ms; - status = deliveryFuture.wait_for(newTout); + status = deliveryFuture.wait_for(timer.remain()); + instance->setDeliveryCompletedCb(nullptr); ASSERT_TRUE(status == std::future_status::ready); ASSERT_TRUE(deliveryFuture.get()); } +TEST_F(MqttStreamingProtocolTest, PublishingRetainedWithNullData) +{ + { + auto ok = connect("127.0.0.1", clientId); + ASSERT_TRUE(ok); + } + const std::string topic = buildTopicName(); + { + auto ok = removeRetainedTopic(topic); + ASSERT_TRUE(ok); + } +} + +TEST_F(MqttStreamingProtocolTest, PublishingRetainedWithReceivingControl) +{ + ASSERT_TRUE(connect("127.0.0.1", clientId)); + + const std::string topic = buildTopicName(); + ASSERT_TRUE(removeRetainedTopic(topic)); + + const std::string text = "test data"; + const MqttMessage msg(topic, std::vector(text.begin(), text.end()), 1, true); + + ASSERT_TRUE(publishMsg(msg)); + ASSERT_TRUE(instance->disconnect()); + + + std::this_thread::sleep_for(milliseconds(500)); // Give some time to the broker to store the retained message) + + MqttAsyncClientWrapper subscriber(std::make_shared(), "testSubscriberId"); + ASSERT_TRUE(subscriber.connect("127.0.0.1")); + + std::promise receivedPromise; + auto receivedFuture = receivedPromise.get_future(); + std::atomic done{false}; + subscriber.instance + ->setMessageArrivedCb(msg.getTopic(), + [&msg, + &done, + promise = &receivedPromise](const mqtt::MqttAsyncClient &subscriber, + mqtt::MqttMessage &receivedMsg) { + if (receivedMsg.getData().empty()) { + return; + } + bool expected = false; + if (done.compare_exchange_strong(expected, true)) { + promise->set_value(receivedMsg); + } + }); + + Timer receiveTimer(successTimeout); + auto ok = subscriber.instance->subscribe(msg.getTopic(), msg.getQos()); + ASSERT_TRUE(ok); + auto status = receivedFuture.wait_for(receiveTimer.remain()); + instance->setMessageArrivedCb(nullptr); + ASSERT_TRUE(status == std::future_status::ready); + + auto receivedMsg = receivedFuture.get(); + ASSERT_TRUE(receivedMsg == msg); + // ASSERT_TRUE(receivedMsg.getRetained()); + +} + +TEST_F(MqttStreamingProtocolTest, PublishingWithReceivingControl) +{ + ASSERT_TRUE(connect("127.0.0.1", clientId)); + + const std::string topic = buildTopicName(); + ASSERT_TRUE(removeRetainedTopic(topic)); + + const std::string text = "test data"; + const MqttMessage msg(topic, std::vector(text.begin(), text.end()), 1, false); + MqttAsyncClientWrapper subscriber(std::make_shared(), "testSubscriberId"); + ASSERT_TRUE(subscriber.connect("127.0.0.1")); + + std::promise receivedPromise; + auto receivedFuture = receivedPromise.get_future(); + std::atomic done{false}; + subscriber.instance + ->setMessageArrivedCb(msg.getTopic(), + [&msg, + &done, + promise = &receivedPromise](const mqtt::MqttAsyncClient &subscriber, + mqtt::MqttMessage &receivedMsg) { + if (receivedMsg.getData().empty()) { + return; + } + bool expected = false; + if (done.compare_exchange_strong(expected, true)) { + promise->set_value(receivedMsg); + } + }); + + Timer receiveTimer(successTimeout); + auto ok = subscriber.instance->subscribe(msg.getTopic(), msg.getQos()); + ASSERT_TRUE(ok); + ASSERT_TRUE(publishMsg(msg)); + + auto status = receivedFuture.wait_for(receiveTimer.remain()); + instance->setMessageArrivedCb(nullptr); + ASSERT_TRUE(status == std::future_status::ready); + + auto receivedMsg = receivedFuture.get(); + ASSERT_TRUE(receivedMsg == msg); +} + TEST_F(MqttStreamingProtocolTest, PublishingWithoutConnection) { - const std::string topic = "test/topic"; + 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); From 570fd6f5309cd79fae818c5ac19028b35b9986e1 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 29 Sep 2025 12:13:17 +0200 Subject: [PATCH 23/46] mqtt: improving client/server thread safety --- .../mqtt_streaming_device_impl.h | 3 ++ .../src/mqtt_streaming_device_impl.cpp | 49 +++++++++++++------ .../tests/test_mqtt_streaming_protocol.cpp | 7 ++- .../mqtt_streaming_server_impl.h | 4 +- .../src/CMakeLists.txt | 1 + .../src/mqtt_streaming_server_impl.cpp | 9 ++-- 6 files changed, 49 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 3df5435c..dae58261 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,6 +47,8 @@ class MqttStreamingDeviceImpl : public Device FunctionBlockPtr onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) override; void setupMqttSubscriber(); + bool waitForConnection(const int timeoutMs); + void receiveSignalTopics(const int timeoutMs); void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); DictObjectPtr fbTypes; @@ -59,6 +61,7 @@ class MqttStreamingDeviceImpl : public Device std::promise connectedPromise; std::future connectedFuture; + std::atomic connectedDone{false}; std::unordered_map> deviceMap; }; 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 ceaec39b..a4a039c4 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -36,29 +36,17 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, int initTimeout = config.getPropertyValue(PROPERTY_NAME_INIT_DELAY); - initComponentStatus(); - - connectedPromise = std::promise(); - connectedFuture = connectedPromise.get_future(); + initComponentStatus(); setupMqttSubscriber(); - if (connectedFuture.wait_for(std::chrono::milliseconds(initTimeout)) != std::future_status::ready || connectedFuture.get() == false) + if (!waitForConnection(initTimeout)) { 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_I("MQTT: Connection established"); - - 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(initTimeout)); // TODO : remove it! - - subscriber->unsubscribe(TOPIC_ALL_SIGNALS); - subscriber->setMessageArrivedCb(nullptr); + receiveSignalTopics(initTimeout); } void MqttStreamingDeviceImpl::removed() @@ -78,14 +66,43 @@ void MqttStreamingDeviceImpl::setupMqttSubscriber() subscriber->setClientId(connectionSettings.clientId); subscriber->setUsernamePasswrod(connectionSettings.username, connectionSettings.password); + connectedDone = false; + connectedPromise = std::promise(); + connectedFuture = connectedPromise.get_future(); + subscriber->setConnectedCb([this] { - connectedPromise.set_value(true); + bool expected = false; + if (connectedDone.compare_exchange_strong(expected, true)) { + connectedPromise.set_value(true); + } }); LOG_I("MQTT: Trying to connect to MQTT broker ({})", connectionSettings.mqttUrl); subscriber->connect(); } +bool MqttStreamingDeviceImpl::waitForConnection(const int timeoutMs) +{ + bool res = (connectedFuture.wait_for(std::chrono::milliseconds(timeoutMs)) + == std::future_status::ready + && connectedFuture.get() == true); + subscriber->setConnectedCb(nullptr); + return res; +} + +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); + + std::this_thread::sleep_for(std::chrono::milliseconds(timeoutMs)); // TODO : remove it! + + subscriber->unsubscribe(TOPIC_ALL_SIGNALS); + subscriber->setMessageArrivedCb(nullptr); +} + void MqttStreamingDeviceImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg) { const std::string topic = msg.getTopic(); diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp index 37950d47..95c16fe9 100644 --- a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp +++ b/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp @@ -48,11 +48,15 @@ class MqttAsyncClientWrapper instance->setServerURL(url); instance->setClientId(id); + connectedDone = false; connectedPromise = std::promise(); connectedFuture = connectedPromise.get_future(); instance->setConnectedCb([this]() { - connectedPromise.set_value(true); + bool expected = false; + if (connectedDone.compare_exchange_strong(expected, true)) { + connectedPromise.set_value(true); + } }); return instance->connect(); } @@ -137,6 +141,7 @@ class MqttAsyncClientWrapper std::shared_ptr instance; std::promise connectedPromise; std::future connectedFuture; + std::atomic connectedDone{false}; int successTimeout = 5000; int failureTimeout = 3000; 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 0de065c8..489c732f 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 @@ -87,13 +87,13 @@ class MqttStreamingServerImpl : public daq::Server LoggerPtr logger; LoggerComponentPtr loggerComponent; - bool serverStopped; + std::atomic serverStopped; size_t maxPacketReadCount; std::chrono::milliseconds processingThreadSleepTime; 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 8d82e5d7..b78e3e59 100644 --- a/mqtt_streaming_server_module/src/CMakeLists.txt +++ b/mqtt_streaming_server_module/src/CMakeLists.txt @@ -29,6 +29,7 @@ source_group("module" FILES ${MODULE_HEADERS_DIR}/mqtt_streaming_server_module_i ${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 666ccdf4..93df4184 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -299,8 +299,8 @@ void MqttStreamingServerImpl::processingThreadFunc() void MqttStreamingServerImpl::startProcessingThread() { - assert(!processingThreadRunning); - processingThreadRunning = true; + if (processingThreadRunning.exchange(true)) + return; processingThread = std::thread(&MqttStreamingServerImpl::processingThreadFunc, this); } @@ -316,10 +316,9 @@ 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; From 878383bae133319b85501ebbf3adbdcc2dd1bd57 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 30 Sep 2025 10:41:21 +0200 Subject: [PATCH 24/46] mqtt: ref-dev-mqtt-pub example changes --- .../ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) 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 421cfe4f..70e43760 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 @@ -59,30 +59,25 @@ int main(int argc, char* argv[]) 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) + .setRootDevice("daqref://device0") .build(); - - auto refDevice = instance.addDevice("daqref://device0"); - refDevice.setPropertyValue("EnableProtectedChannel", true); + auto refDevice = instance.getRootDevice(); + refDevice.setPropertyValue("NumberOfChannels", 4); + + + const auto channels = refDevice.getChannelsRecursive(); + channels[0].setPropertyValue("UseGlobalSampleRate", false); + channels[0].setPropertyValue("SampleRate", 10); + channels[0].setPropertyValue("Frequency", 1); + channels[0].setPropertyValue("Waveform", 1); + channels[1].setPropertyValue("UseGlobalSampleRate", false); + channels[1].setPropertyValue("SampleRate", 20); + channels[1].setPropertyValue("Frequency", 1); + channels[1].setPropertyValue("Waveform", 3); + channels[2].setPropertyValue("UseGlobalSampleRate", false); + channels[2].setPropertyValue("SampleRate", 200); + channels[2].setPropertyValue("Frequency", 4); auto serverConfig = instance.getAvailableServerTypes().get("OpenDAQMQTT").createDefaultConfig(); serverConfig.setPropertyValue("MqttBrokerAddress", "127.0.0.1"); From 2c919c664b325093d90f2581f12e64d0f71bb067 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 30 Sep 2025 15:24:01 +0200 Subject: [PATCH 25/46] mqtt: separate MqttDataWrapper class for serialize/deserialize MQTT payload --- .../mqtt_streaming_client_module/constants.h | 2 - .../mqtt_receiver_fb_impl.h | 2 +- .../mqtt_streaming_device_impl.h | 3 +- .../src/mqtt_receiver_fb_impl.cpp | 64 ++------ .../src/mqtt_streaming_device_impl.cpp | 51 +++--- .../include/MqttDataWrapper.h | 44 +++++ mqtt_streaming_protocol/src/CMakeLists.txt | 8 +- .../src/MqttDataWrapper.cpp | 152 ++++++++++++++++++ .../mqtt_streaming_server_impl.h | 5 +- .../src/mqtt_streaming_server_impl.cpp | 54 ++----- 10 files changed, 251 insertions(+), 134 deletions(-) create mode 100644 mqtt_streaming_protocol/include/MqttDataWrapper.h create mode 100644 mqtt_streaming_protocol/src/MqttDataWrapper.cpp 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 3bd587d8..a6efa8af 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,8 +29,6 @@ static constexpr const char* PROPERTY_NAME_INIT_DELAY = "InitDelay"; static constexpr const char* PROPERTY_NAME_SIGNAL_LIST = "SignalList"; static const char* TOPIC_ALL_SIGNALS = "openDAQ/+/$signals"; -static const char* TOPIC_ALL_SIGNALS_PREFIX = "openDAQ"; -static const char* DEVICE_SIGNAL_LIST = "$signals"; static const char* MQTT_LOCAL_DEVICE_ID_PREFIX = "MqttDevice"; static const char* MQTT_DEVICE_NAME = "MqttStreamingClientPseudoDevice"; 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 061a1498..d542da54 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; - std::vector subscribedTopics; + DictObjectPtr subscribedSignals; std::mutex sync; 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 dae58261..f294d782 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 @@ -21,6 +21,7 @@ #include #include #include +#include "MqttDataWrapper.h" BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE @@ -62,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; }; 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 2eb1434b..5f749e01 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -12,6 +12,7 @@ #include #include #include "mqtt_streaming_client_module/constants.h" +#include "MqttDataWrapper.h" BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE @@ -32,7 +33,7 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, initProperties(type.createDefaultConfig()); createSignals(); - for (const auto& topic : subscribedTopics) + for (const auto& topic : subscribedSignals.getKeys()) { subscriber->setMessageArrivedCb(topic, std::bind(&MqttReceiverFbImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); subscriber->subscribe(topic, 1); @@ -42,7 +43,7 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, MqttReceiverFbImpl::~MqttReceiverFbImpl() { - for (const auto& topic : subscribedTopics) + for (const auto& topic : subscribedSignals.getKeys()) { subscriber->setMessageArrivedCb(topic, nullptr); subscriber->unsubscribe(topic); @@ -77,14 +78,15 @@ void MqttReceiverFbImpl::propertyChanged(bool configure) void MqttReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); - auto prop = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtr().toVector(); - for (const auto& item : prop) + auto prop = objPtr.getPropertyValue(PROPERTY_NAME_SIGNAL_LIST).asPtr(); + subscribedSignals = Dict(); + for (const auto& [topic, descriptor] : prop) { - auto signalId = item.asPtr(); + auto signalId = topic.asPtr(); if (signalId.assigned()) { LOG_I("Signal in list: {}", signalId.toStdString()); - subscribedTopics.push_back(signalId.toStdString()); + subscribedSignals.set(signalId, descriptor); } } } @@ -119,60 +121,26 @@ void MqttReceiverFbImpl::parseMessage(mqtt::MqttMessage& msg) { std::string topic(msg.getTopic()); std::string jsonObjStr(msg.getData().begin(), msg.getData().end()); - try { - rapidjson::Document jsonDocument; - jsonDocument.Parse(jsonObjStr); - if (jsonDocument.HasParseError()) { - LOG_E("Error parsing mqtt payload as JSON"); - return; + 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); } - - if (jsonDocument.IsObject()) { - double val = 0.0; - UInt timestamp = 0; - 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()){ - val = jsonDocument[name].GetDouble(); - successCnt++; - } else { - LOG_W("Value is not supported."); - } - } else if (name == "timestamp") { - if (jsonDocument[name].IsInt() || jsonDocument[name].IsUint64() || jsonDocument[name].IsInt64()){ - timestamp = jsonDocument[name].GetUint64(); - successCnt++; - } else { - LOG_W("Value is not supported."); - } - } else { - LOG_W("Field \"{}\" is not supported.", name); - } - } - if (successCnt == 2) { - createDataPacket(topic, val, timestamp); - } else { - LOG_W("Not all required fields are present."); - } - } - } - catch (DeserializeException ex) { - LOG_E("Error deserializing mqtt payload"); } } void MqttReceiverFbImpl::createSignals() { auto lock = std::lock_guard(sync); - for (const auto& topic : subscribedTopics) + for (const auto& [topic, descriptor] : subscribedSignals) { LOG_I("Subscribing to topic: {}", topic); std::string signalName = topic; boost::replace_all(signalName, "/", "_"); - auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::Float64).build(); + 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]; 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 a4a039c4..6054381b 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -105,36 +105,15 @@ void MqttStreamingDeviceImpl::receiveSignalTopics(const int timeoutMs) void MqttStreamingDeviceImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg) { - const std::string topic = msg.getTopic(); - 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; // not a signal list message - } - - std::string& deviceName = list[1]; const std::string signalList(msg.getData().begin(), msg.getData().end()); - - rapidjson::Document doc; - - if (doc.Parse(signalList.c_str()).HasParseError() || !doc.IsArray()) { - LOG_W("{} - Signal list format is not correct: {}", topic, signalList); - return; - } - - const auto array = doc.GetArray(); - std::vector deviceSignals; - deviceSignals.reserve(array.Size()); - for (const auto& v : array) { - if (v.IsString()) { - deviceSignals.push_back(v.GetString()); - } + 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); } - - deviceMap.insert({std::move(deviceName), std::move(deviceSignals)}); } DictPtr MqttStreamingDeviceImpl::onGetAvailableFunctionBlockTypes() @@ -143,10 +122,18 @@ DictPtr MqttStreamingDeviceImpl::onGetAvailableFunc for (const auto& device : deviceMap) { auto defaultConfig = PropertyObject(); - auto signalList = List(); - for (const auto& signal : device.second) - signalList.pushBack(signal); - defaultConfig.addProperty(ListProperty(PROPERTY_NAME_SIGNAL_LIST, signalList)); + 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()) { + builder.setUnit(Unit(signal.unit)); + } + auto signalDsc = builder.build(); + signalDict.set(signal.topic, signalDsc); + } + defaultConfig.addProperty(DictProperty(PROPERTY_NAME_SIGNAL_LIST, signalDict)); const auto fbType = FunctionBlockType(device.first, device.first, diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/mqtt_streaming_protocol/include/MqttDataWrapper.h new file mode 100644 index 00000000..d1aee8e1 --- /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::string 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/src/CMakeLists.txt b/mqtt_streaming_protocol/src/CMakeLists.txt index 3bee608c..87f3d3b2 100644 --- a/mqtt_streaming_protocol/src/CMakeLists.txt +++ b/mqtt_streaming_protocol/src/CMakeLists.txt @@ -1,11 +1,13 @@ set(LIB_NAME mqtt_streaming_protocol) set(SRC_Cpp MqttAsyncClient.cpp + MqttDataWrapper.cpp ) set(SRC_PublicHeaders MqttAsyncClient.h MqttMessage.h MqttSettings.h + MqttDataWrapper.h ) set(INCLUDE_DIR ../include) @@ -40,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/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp new file mode 100644 index 00000000..938dd08a --- /dev/null +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -0,0 +1,152 @@ +#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(); + 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::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.IsString()) { + SignalDescriptor sd; + sd.topic = v.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/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 489c732f..3721880c 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,7 +22,6 @@ #include #include #include -//#include #include #include #include @@ -66,10 +65,8 @@ class MqttStreamingServerImpl : public daq::Server 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(); 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 93df4184..60909e07 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -20,6 +20,7 @@ #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" #include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_SERVER_MODULE using namespace daq; @@ -173,7 +174,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; @@ -185,22 +186,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; @@ -208,39 +199,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 (TOPIC_ALL_SIGNALS_PREFIX + globalId); -} - -std::string MqttStreamingServerImpl::buildSignalsTopic() -{ - return (TOPIC_ALL_SIGNALS_PREFIX + rootDeviceGlobalId + "/" + DEVICE_SIGNAL_LIST); + 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); @@ -282,8 +246,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; From fe8d0de10094b44a9ac178b8e891300c8b319281 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 30 Sep 2025 15:39:02 +0200 Subject: [PATCH 26/46] mqtt: list of signals improving - adding Unit data --- .../src/MqttDataWrapper.cpp | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 938dd08a..8e205fa7 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -85,10 +85,30 @@ std::string MqttDataWrapper::serializeSignalDescriptors( std::string result; rapidjson::Document doc; doc.SetArray(); + + auto& allocator = doc.GetAllocator(); + for (const auto& signal : signals) { - rapidjson::Value topicValue; - topicValue.SetString(buildTopicFromId(signal.getGlobalId().toStdString()).c_str(), doc.GetAllocator()); - doc.PushBack(topicValue, doc.GetAllocator()); + 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(); + std::string unitStr = ""; + if (unit.assigned()) + unitStr = unit.getSymbol().toStdString(); + obj.AddMember("unit", rapidjson::Value(unitStr.c_str(), allocator), allocator); + + doc.PushBack(obj, allocator); } // Serialize to string @@ -130,9 +150,19 @@ MqttDataWrapper::parseSignalDescriptors(const std::string& topic, const std::str signalDesc.reserve(array.Size()); for (const auto& v : array) { - if (v.IsString()) { + if (v.IsObject()) { SignalDescriptor sd; - sd.topic = v.GetString(); + 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"].IsString()) { + sd.unit = v["unit"].GetString(); + } signalDesc.emplace_back(std::move(sd)); } } From 236e167c0d2ed8ef8b6c33e4de7654933edf9aa6 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Wed, 1 Oct 2025 11:20:18 +0200 Subject: [PATCH 27/46] mqtt: improving serialization/deserialization of the "Unit" field of the signal data descriptor --- .../src/mqtt_streaming_device_impl.cpp | 11 ++++- .../include/MqttDataWrapper.h | 2 +- .../src/MqttDataWrapper.cpp | 43 +++++++++++++------ 3 files changed, 41 insertions(+), 15 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 6054381b..b459aae0 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -128,7 +128,16 @@ DictPtr MqttStreamingDeviceImpl::onGetAvailableFunc if (!signal.name.empty()) builder.setName(signal.name); if (!signal.unit.empty()) { - builder.setUnit(Unit(signal.unit)); + 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)); } auto signalDsc = builder.build(); signalDict.set(signal.topic, signalDsc); diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/mqtt_streaming_protocol/include/MqttDataWrapper.h index d1aee8e1..8cff6a0d 100644 --- a/mqtt_streaming_protocol/include/MqttDataWrapper.h +++ b/mqtt_streaming_protocol/include/MqttDataWrapper.h @@ -23,7 +23,7 @@ struct Result { struct SignalDescriptor { std::string topic; std::string name; - std::string unit; + std::vector unit; }; class MqttDataWrapper final { diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 8e205fa7..fca11201 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -92,21 +92,35 @@ std::string MqttDataWrapper::serializeSignalDescriptors( rapidjson::Value obj(rapidjson::kObjectType); // topic - std::string topic = buildTopicFromId(signal.getGlobalId().toStdString()); - obj.AddMember("topic", rapidjson::Value(topic.c_str(), allocator), allocator); + { + 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); + { + 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(); - std::string unitStr = ""; - if (unit.assigned()) - unitStr = unit.getSymbol().toStdString(); - obj.AddMember("unit", rapidjson::Value(unitStr.c_str(), allocator), allocator); + { + 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); } @@ -160,8 +174,11 @@ MqttDataWrapper::parseSignalDescriptors(const std::string& topic, const std::str sd.name = v["name"].GetString(); } - if (v.HasMember("unit") && v["unit"].IsString()) { - sd.unit = v["unit"].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)); } From 3220ca521cf7d334e6a8451ea8471d1e14187de8 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 6 Oct 2025 13:35:01 +0200 Subject: [PATCH 28/46] mqtt: --address arg for examples to set MQTT broker IP --- examples/InputArgs.h | 103 ++++++++++++++++++ examples/ref-dev-mqtt-pub/src/CMakeLists.txt | 3 +- .../ref-dev-mqtt-pub/src/ref-dev-mqtt-pub.cpp | 51 +-------- examples/ref-dev-mqtt-sub/src/CMakeLists.txt | 1 + .../ref-dev-mqtt-sub/src/ref-dev-mqtt-sub.cpp | 50 +-------- 5 files changed, 116 insertions(+), 92 deletions(-) create mode 100644 examples/InputArgs.h diff --git a/examples/InputArgs.h b/examples/InputArgs.h new file mode 100644 index 00000000..8435d576 --- /dev/null +++ b/examples/InputArgs.h @@ -0,0 +1,103 @@ +#pragma once +#include +#include +#include +#include +#include + +class InputArgs +{ +public: + void addArg(const std::string& name, const std::string& description, bool hasValue = false) + { + argDescriptions[name] = {description, hasValue}; + } + + void parse(int argc, char* argv[]) + { + parsedArgs.clear(); + argValues.clear(); + positionalArgs.clear(); + + for (int i = 1; i < argc; ++i) + { + std::string arg = argv[i]; + if (arg.rfind("--", 0) == 0) + { + auto eqPos = arg.find('='); + if (eqPos != std::string::npos) + { + std::string key = arg.substr(0, eqPos); + std::string value = arg.substr(eqPos + 1); + argValues[key] = value; + parsedArgs.push_back(key); + } + else if (i + 1 < argc && argDescriptions[arg].hasValue) + { + argValues[arg] = argv[i + 1]; + parsedArgs.push_back(arg); + ++i; + } + else + { + parsedArgs.push_back(arg); + } + } + else + { + positionalArgs.push_back(arg); + } + } + } + + bool hasArg(const std::string& name) const + { + return std::find(parsedArgs.begin(), parsedArgs.end(), name) != parsedArgs.end(); + } + + std::string getArgValue(const std::string& name, const std::string& defaultValue = "") const + { + auto it = argValues.find(name); + if (it != argValues.end()) + return it->second; + return defaultValue; + } + + const std::vector& getPositionalArgs() const + { + return positionalArgs; + } + + 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, descStruct] : argDescriptions) + { + std::cout << " " << name; + if (descStruct.hasValue) + std::cout << " "; + std::cout << " : " << descStruct.description << std::endl; + } + } + +private: + struct ArgDesc + { + std::string description; + bool hasValue; + }; + std::unordered_map argDescriptions; + std::vector parsedArgs; + std::unordered_map argValues; + std::vector positionalArgs; +}; \ No newline at end of file diff --git a/examples/ref-dev-mqtt-pub/src/CMakeLists.txt b/examples/ref-dev-mqtt-pub/src/CMakeLists.txt index 8ccc98a1..cd216504 100644 --- a/examples/ref-dev-mqtt-pub/src/CMakeLists.txt +++ b/examples/ref-dev-mqtt-pub/src/CMakeLists.txt @@ -4,4 +4,5 @@ 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 +target_link_libraries(${EXAMPLE_PROJECT_NAME} PRIVATE daq::opendaq) +target_include_directories(${EXAMPLE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../) \ 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 index 70e43760..dd6cf021 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,57 +1,15 @@ #include +#include "../../InputArgs.h" #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.addArg("--address", "MQTT broker address", true); args.parse(argc, argv); if (args.hasArg("--help") || args.hasUnknownArgs()) { @@ -59,13 +17,14 @@ int main(int argc, char* argv[]) return 0; } + std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); + const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH) .setRootDevice("daqref://device0") .build(); auto refDevice = instance.getRootDevice(); refDevice.setPropertyValue("NumberOfChannels", 4); - const auto channels = refDevice.getChannelsRecursive(); channels[0].setPropertyValue("UseGlobalSampleRate", false); channels[0].setPropertyValue("SampleRate", 10); @@ -80,7 +39,7 @@ int main(int argc, char* argv[]) channels[2].setPropertyValue("Frequency", 4); auto serverConfig = instance.getAvailableServerTypes().get("OpenDAQMQTT").createDefaultConfig(); - serverConfig.setPropertyValue("MqttBrokerAddress", "127.0.0.1"); + serverConfig.setPropertyValue("MqttBrokerAddress", 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-sub/src/CMakeLists.txt b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt index 1fe267a6..c7621cc2 100644 --- a/examples/ref-dev-mqtt-sub/src/CMakeLists.txt +++ b/examples/ref-dev-mqtt-sub/src/CMakeLists.txt @@ -4,3 +4,4 @@ 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_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 ac32f933..77a06654 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,57 +1,15 @@ #include +#include "../../InputArgs.h" #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.addArg("--address", "MQTT broker address", true); // If you want to support --address for sub args.parse(argc, argv); if (args.hasArg("--help") || args.hasUnknownArgs()) { @@ -59,8 +17,10 @@ int main(int argc, char* argv[]) return 0; } + std::string brokerAddress = args.getArgValue("--address", "127.0.0.1"); + const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); - auto brokerDevice = instance.addDevice("daq.mqtt://127.0.0.1"); + auto brokerDevice = instance.addDevice("daq.mqtt://" + brokerAddress); auto availableDeviceNodes = brokerDevice.getAvailableFunctionBlockTypes(); if (availableDeviceNodes.getCount() == 0) { From c41ce8ba57aec5d7e7333bdd57b37760c8a03b15 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 6 Oct 2025 13:51:26 +0200 Subject: [PATCH 29/46] mqtt: README updating --- README.md | 94 +++---------------------------------------------------- 1 file changed, 4 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index e07f2540..5c0643c3 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ cmake --build . ## Testing -There are several example applications in the *"examples"* folder. These examples are based on OpenDAQ SDK and allow testing of *SimpleMQTTModule* functional blocks with each other and with third-party MQTT tools. +There are several example applications in the *"examples"* folder. These examples are based on OpenDAQ SDK and allow testing of *MQTTStreamingModule* client/server sides with each other and with third-party MQTT tools. > ***Note:*** *Using the applications involves using a third-party broker. It must be started before example applications. See a **External MQTT tools** section for more details* @@ -57,94 +57,8 @@ There are several example applications in the *"examples"* folder. These example #### ref-dev-mqtt-pub -The *ref-dev-mqtt-pub* application is a console example which publishes *ref-device Signal* samples via the *MQTTStreamingModule* server. By default it uses the following *MQTTStreamingModule* server settings: -``` -StreamingDataPollingPeriod: 20 -MaxPacketReadCount: 1000 -BrokerAddress: "127.0.0.1" -MqttUsername: "" -MqttPassword: "" -``` - -### External MQTT tools - -It is suggested to use [***Eclipse Mosquitto***](https://github.com/eclipse-mosquitto/mosquitto) as a third-party MQTT tool set. It includes MQTT broker and MQTT publisher/subscriber clients. -Utilities could be installed on **Ubuntu**: - -```shell -sudo apt install mosquitto mosquitto-clients -``` +The *ref-dev-mqtt-pub* application is a console example which publishes *ref-device Signal* samples via the *MQTTStreamingModule* server. -The MQTT broker will be run automatically after installing. For simple testing run a subscriber with the following options: +#### ref-dev-mqtt-sub -```shell -mosquitto_sub -h 127.0.0.1 -t "openDAQ/#" -v -``` -The subscriber will wait for incoming data and then print it. Then run a publisher with the following options: -```shell -mosquitto_pub -h 127.0.0.1 -t "openDAQ/publisher" -m '{"Input0":2, "Input1":1.2, "Input3":3.3}' -``` -This command publishes a message and exits. From the subscriber's side you can see: - -```shell -user@machine:$ mosquitto_sub -h 127.0.0.1 -t "openDAQ/publisher" -v -openDAQ/publisher {"Input0":2, "Input1":1.2, "Input3":3.3} -``` -Now, you can test examples with 3rd-party tools. For example, run *ref-dev-mqtt-pub* and *mosquitto_sub* in different terminals with proper settings. -```shell -user@machine:$ ./ref-dev-mqtt-pub -[tid: 29784][2025-09-15 11:17:02.663] [ModuleManager] [info] Loaded module [v3.31.0 ReferenceDeviceModule] from "libref_device_module-64-3-debug.module.so". -[tid: 29784][2025-09-15 11:17:02.664] [ModuleManager] [info] DEV [daqref] Reference device: "Reference device" -[tid: 29784][2025-09-15 11:17:02.668] [ModuleManager] [info] Loaded module [v3.4.0 OpenDAQMqttStreamingServerModule] from "libmqtt_stream_srv_module-64-3-debug.module.so". -[tid: 29784][2025-09-15 11:17:02.670] [ModuleManager] [info] SRV [OpenDAQMQTT] openDAQ MQTT Streaming server: "Streams data over MQTT" -[tid: 29784][2025-09-15 11:17:03.196] [ReferenceDevice] [info] Properties: NumberOfChannels 2 -[tid: 29784][2025-09-15 11:17:03.201] [/RefDev1/IO/AI/RefCh0] [info] Properties: Waveform Sine, Frequency 10, DC 0, Amplitude 5, NoiseAmplitude 0, ConstantValue 2 -[tid: 29784][2025-09-15 11:17:03.201] [/RefDev1/IO/AI/RefCh0] [info] Properties: SampleRate 1000, ClientSideScaling false -[tid: 29784][2025-09-15 11:17:03.207] [/RefDev1/IO/AI/RefCh1] [info] Properties: Waveform Sine, Frequency 10, DC 0, Amplitude 5, NoiseAmplitude 0, ConstantValue 2 -[tid: 29784][2025-09-15 11:17:03.207] [/RefDev1/IO/AI/RefCh1] [info] Properties: SampleRate 1000, ClientSideScaling false -[tid: 29784][2025-09-15 11:17:03.209] [ReferenceDevice] [info] Properties: AcquisitionLoopTime 20 -[tid: 29784][2025-09-15 11:17:03.216] [Instance] [info] Root device set to daqref://device1 -[tid: 29784][2025-09-15 11:17:03.231] [ReferenceDevice] [info] Properties: NumberOfChannels 2 -[tid: 29784][2025-09-15 11:17:03.235] [/RefDev1/Dev/RefDev0/IO/AI/RefCh0] [info] Properties: Waveform Sine, Frequency 10, DC 0, Amplitude 5, NoiseAmplitude 0, ConstantValue 2 -[tid: 29784][2025-09-15 11:17:03.235] [/RefDev1/Dev/RefDev0/IO/AI/RefCh0] [info] Properties: SampleRate 1000, ClientSideScaling false -[tid: 29784][2025-09-15 11:17:03.240] [/RefDev1/Dev/RefDev0/IO/AI/RefCh1] [info] Properties: Waveform Sine, Frequency 10, DC 0, Amplitude 5, NoiseAmplitude 0, ConstantValue 2 -[tid: 29784][2025-09-15 11:17:03.240] [/RefDev1/Dev/RefDev0/IO/AI/RefCh1] [info] Properties: SampleRate 1000, ClientSideScaling false -[tid: 29784][2025-09-15 11:17:03.242] [ReferenceDevice] [info] Properties: AcquisitionLoopTime 20 -[tid: 29784][2025-09-15 11:17:03.253] [/RefDev1/Dev/RefDev0/IO/AI/ProtectedChannel] [info] Properties: Waveform Sine, Frequency 10, DC 0, Amplitude 5, NoiseAmplitude 0, ConstantValue 2 -[tid: 29784][2025-09-15 11:17:03.253] [/RefDev1/Dev/RefDev0/IO/AI/ProtectedChannel] [info] Properties: SampleRate 1000, ClientSideScaling false -[tid: 29784][2025-09-15 11:17:03.265] [OpenDAQMQTT] [info] MQTT: Trying to connect to MQTT broker (127.0.0.1) -[tid: 29784][2025-09-15 11:17:03.267] [OpenDAQMQTT] [info] Adding the Signal to reader: /RefDev1/IO/AI/RefCh0/Sig/AI0; -[tid: 29784][2025-09-15 11:17:03.268] [OpenDAQMQTT] [info] Signal /RefDev1/IO/AI/RefCh0/Sig/AI0Time doesn't has domain signal assigned, skipping -[tid: 29784][2025-09-15 11:17:03.268] [OpenDAQMQTT] [info] Adding the Signal to reader: /RefDev1/IO/AI/RefCh1/Sig/AI1; -[tid: 29784][2025-09-15 11:17:03.269] [OpenDAQMQTT] [info] Signal /RefDev1/IO/AI/RefCh1/Sig/AI1Time doesn't has domain signal assigned, skipping -[tid: 29784][2025-09-15 11:17:03.269] [OpenDAQMQTT] [info] Signal /RefDev1/Sig/Time doesn't has domain signal assigned, skipping -[tid: 29784][2025-09-15 11:17:03.269] [OpenDAQMQTT] [info] Adding the Signal to reader: /RefDev1/Dev/RefDev0/IO/AI/RefCh0/Sig/AI0; -[tid: 29784][2025-09-15 11:17:03.269] [OpenDAQMQTT] [info] Signal /RefDev1/Dev/RefDev0/IO/AI/RefCh0/Sig/AI0Time doesn't has domain signal assigned, skipping -[tid: 29784][2025-09-15 11:17:03.269] [OpenDAQMQTT] [info] Adding the Signal to reader: /RefDev1/Dev/RefDev0/IO/AI/RefCh1/Sig/AI1; -[tid: 29784][2025-09-15 11:17:03.270] [OpenDAQMQTT] [info] Signal /RefDev1/Dev/RefDev0/IO/AI/RefCh1/Sig/AI1Time doesn't has domain signal assigned, skipping -[tid: 29784][2025-09-15 11:17:03.270] [OpenDAQMQTT] [info] Adding the Signal to reader: /RefDev1/Dev/RefDev0/IO/AI/ProtectedChannel/Sig/AI2; -[tid: 29784][2025-09-15 11:17:03.270] [OpenDAQMQTT] [info] Signal /RefDev1/Dev/RefDev0/IO/AI/ProtectedChannel/Sig/AI2Time doesn't has domain signal assigned, skipping -[tid: 29784][2025-09-15 11:17:03.270] [OpenDAQMQTT] [info] Signal /RefDev1/Dev/RefDev0/Sig/Time doesn't has domain signal assigned, skipping -[tid: 29812][2025-09-15 11:17:03.271] [OpenDAQMQTT] [info] Streaming-to-device read thread started -[tid: 29784][2025-09-15 11:17:03.271] [OpenDAQMQTT] [info] Added Component: /RefDev1/Srv/OpenDAQMQTT; -Press "enter" to exit the application... -[tid: 29811][2025-09-15 11:17:03.276] [OpenDAQMQTT] [info] MQTT: Connection established - -``` -In this case you can see messages on the *mosquitto_sub* side: - -```shell -user@machine:$ mosquitto_sub -h 127.0.0.1 -t "openDAQ/#" -v -openDAQ/RefDev1/$signals ["openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0","openDAQ/RefDev1/IO/AI/RefCh1/Sig/AI1","openDAQ/RefDev1/Dev/RefDev0/IO/AI/RefCh0/Sig/AI0","openDAQ/RefDev1/Dev/RefDev0/IO/AI/RefCh1/Sig/AI1","openDAQ/RefDev1/Dev/RefDev0/IO/AI/ProtectedChannel/Sig/AI2"] -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":1.243449435824274,"timestamp":1757928009227270} -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":0.9369065729286229,"timestamp":1757928009228270} -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":0.6266661678215204,"timestamp":1757928009229270} -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":0.3139525976465657,"timestamp":1757928009230270} -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":-1.6081226496766366e-15,"timestamp":1757928009231270} -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":-0.31395259764656677,"timestamp":1757928009232270} -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":-0.6266661678215214,"timestamp":1757928009233270} -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":-0.9369065729286239,"timestamp":1757928009234270} -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":-1.2434494358242752,"timestamp":1757928009235270} -openDAQ/RefDev1/IO/AI/RefCh0/Sig/AI0 {"value":-1.5450849718747386,"timestamp":1757928009236270} -<...> -``` +The *ref-dev-mqtt-sub* application is a console example which subscribes to an available MQTT openDAQ device and prints signal samples. \ No newline at end of file From 39eadcc6fe6c8200a451b3b7636f3d5ea4b0a7f0 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 9 Oct 2025 13:45:11 +0200 Subject: [PATCH 30/46] mqtt: cleaning up --- CMakeLists.txt | 24 +---- .../mqtt_receiver_fb_impl.h | 3 - .../src/mqtt_receiver_fb_impl.cpp | 12 --- .../mqtt_streaming_server_impl.h | 8 -- .../src/mqtt_streaming_server_impl.cpp | 100 +----------------- 5 files changed, 4 insertions(+), 143 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a1ab0826..70683d6b 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) @@ -56,25 +56,3 @@ 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/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 d542da54..94b4c492 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 @@ -50,10 +50,7 @@ class MqttReceiverFbImpl final : public FunctionBlock void parseMessage(mqtt::MqttMessage& msg); void createDataPacket(const std::string& topic, double value, UInt timestamp); - void configure(); - void initProperties(const PropertyObjectPtr& config); - void propertyChanged(bool configure); void readProperties(); void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); 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 5f749e01..3e5aef62 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -68,13 +68,6 @@ void MqttReceiverFbImpl::initProperties(const PropertyObjectPtr& config) readProperties(); } -void MqttReceiverFbImpl::propertyChanged(bool configure) -{ - readProperties(); - if (configure) - this->configure(); -} - void MqttReceiverFbImpl::readProperties() { auto lock = std::lock_guard(sync); @@ -91,11 +84,6 @@ void MqttReceiverFbImpl::readProperties() } } -void MqttReceiverFbImpl::configure() -{ - -} - void MqttReceiverFbImpl::createDataPacket(const std::string& topic, double value, UInt timestamp) { auto lock = std::lock_guard(sync); 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 3721880c..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 @@ -48,21 +48,13 @@ class MqttStreamingServerImpl : public daq::Server protected: 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 ChannelData& data, SizeT dataAmount); 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 60909e07..7e908d89 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -17,8 +17,6 @@ #include -#include "rapidjson/writer.h" -#include "rapidjson/stringbuffer.h" #include #include @@ -59,8 +57,6 @@ MqttStreamingServerImpl::MqttStreamingServerImpl(const DevicePtr &rootDevice, info.asPtr(true).addServerCapability(serverCapabilityStreaming); - this->context.getOnCoreEvent() += event(&MqttStreamingServerImpl::coreEventCallback); - maxPacketReadCount = config.getPropertyValue(PROPERTY_NAME_MAX_PACKET_READ_COUNT); processingThreadSleepTime = std::chrono::milliseconds( config.getPropertyValue(PROPERTY_NAME_POLLING_PERIOD)); @@ -78,85 +74,6 @@ 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(); @@ -227,8 +144,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) { { @@ -260,7 +177,7 @@ 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() @@ -286,7 +203,6 @@ void MqttStreamingServerImpl::stopServerInternal() 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()) { @@ -448,11 +364,6 @@ void MqttStreamingServerImpl::onStopServer() stopServerInternal(); } -StreamingPtr MqttStreamingServerImpl::onGetStreaming() -{ - return nullptr; -} - void MqttStreamingServerImpl::addReader(SignalPtr signalToRead) { std::scoped_lock lock(readersSync); @@ -465,11 +376,6 @@ void MqttStreamingServerImpl::addReader(SignalPtr signalToRead) .build()); } -void MqttStreamingServerImpl::removeReader(SignalPtr signalToRead) -{ - -} - OPENDAQ_DEFINE_CLASS_FACTORY_WITH_INTERFACE( INTERNAL_FACTORY, MqttStreamingServer, daq::IServer, daq::DevicePtr, rootDevice, From e8317c69532ba7beb5a04463ea681b4d0c8feaa1 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Thu, 9 Oct 2025 14:10:02 +0200 Subject: [PATCH 31/46] mqtt: OpenDAQ version changing --- 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 798a6dd2..830c6743 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 git@github.com:openDAQ/openDAQ.git - GIT_TAG v3.20.4 + GIT_TAG v3.29.0-rc GIT_PROGRESS ON EXCLUDE_FROM_ALL SYSTEM From 6c9bc9136490c84272c98fc5d3c231be51072f1e Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 14 Oct 2025 15:29:52 +0200 Subject: [PATCH 32/46] mqtt: initing properties bug --- mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp | 3 +++ 1 file changed, 3 insertions(+) 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 3e5aef62..7b9ef490 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -60,10 +60,13 @@ void MqttReceiverFbImpl::initProperties(const PropertyObjectPtr& config) { 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(); } From 8da477147e233f30e78f87c2e0028c33797b0b73 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 14 Oct 2025 15:30:16 +0200 Subject: [PATCH 33/46] mqtt: logs --- mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 7b9ef490..7a4e7edf 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -36,7 +36,9 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, for (const auto& topic : subscribedSignals.getKeys()) { subscriber->setMessageArrivedCb(topic, std::bind(&MqttReceiverFbImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); - subscriber->subscribe(topic, 1); + auto ok = subscriber->subscribe(topic, 1); + if (!ok) + LOG_W("Failed to subscribe to the topic: {}", topic); } setComponentStatus(ComponentStatus::Ok); } From 132b22a5ee48828ddd98fe832a26c3551a97ad8e Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 14 Oct 2025 16:13:37 +0200 Subject: [PATCH 34/46] mqtt: removed unused SLL options from MQTT wrapper --- mqtt_streaming_protocol/include/MqttAsyncClient.h | 1 - mqtt_streaming_protocol/src/MqttAsyncClient.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h index ea642c4b..964b47f6 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncClient.h +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -78,7 +78,6 @@ class MqttAsyncClient final { MQTTAsync_connectOptions connOpts; MQTTAsync_disconnectOptions disconnOpts; MQTTAsync_createOptions createOpts; - MQTTAsync_SSLOptions sslOpts = MQTTAsync_SSLOptions_initializer; std::recursive_mutex cbMtx; diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index f8f7a7fa..ab5fd8b2 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -21,7 +21,6 @@ MqttAsyncClient::MqttAsyncClient(std::string serverUrl, std::string clientId, bo connOpts.automaticReconnect = true; connOpts.minRetryInterval = 1; connOpts.maxRetryInterval = 10; - connOpts.ssl = &sslOpts; connOpts.context = this; disconnOpts = MQTTAsync_disconnectOptions_initializer; @@ -30,7 +29,6 @@ MqttAsyncClient::MqttAsyncClient(std::string serverUrl, std::string clientId, bo disconnOpts.context = this; createOpts = MQTTAsync_createOptions_initializer; - sslOpts = MQTTAsync_SSLOptions_initializer; } MqttAsyncClient::~MqttAsyncClient() { From 7aad38d804ce6191f2b430051ab36e4e2296491c Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 17 Oct 2025 19:24:08 +0200 Subject: [PATCH 35/46] mqtt: boost dependencies --- mqtt_streaming_client_module/src/CMakeLists.txt | 5 +++-- mqtt_streaming_protocol/src/CMakeLists.txt | 3 +++ mqtt_streaming_server_module/src/CMakeLists.txt | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mqtt_streaming_client_module/src/CMakeLists.txt b/mqtt_streaming_client_module/src/CMakeLists.txt index 6897fce8..ac250ae6 100644 --- a/mqtt_streaming_client_module/src/CMakeLists.txt +++ b/mqtt_streaming_client_module/src/CMakeLists.txt @@ -36,6 +36,8 @@ source_group("functionalBlock" FILES ${MODULE_HEADERS_DIR}/mqtt_receiver_fb_impl mqtt_receiver_fb_impl.cpp ) +find_package(Boost REQUIRED COMPONENTS algorithm) + add_library(${LIB_NAME} SHARED ${SRC_Include} ${SRC_Srcs} ) @@ -47,13 +49,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/src/CMakeLists.txt b/mqtt_streaming_protocol/src/CMakeLists.txt index 87f3d3b2..be6d8f43 100644 --- a/mqtt_streaming_protocol/src/CMakeLists.txt +++ b/mqtt_streaming_protocol/src/CMakeLists.txt @@ -15,6 +15,8 @@ prepend_include(${INCLUDE_DIR} SRC_PublicHeaders) source_group("include" FILES ${SRC_PublicHeaders}) +find_package(Boost REQUIRED COMPONENTS algorithm) + add_library(${LIB_NAME} STATIC ${SRC_Cpp} ${SRC_PublicHeaders} ) @@ -45,6 +47,7 @@ endif() target_link_libraries(${LIB_NAME} PUBLIC ${PAHO_LIB} daq::opendaq rapidjson + PRIVATE $ ) if (WIN32) 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 29323c03710ae7ddefe995af4b3affc31f872a5d Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 17 Oct 2025 19:41:05 +0200 Subject: [PATCH 36/46] mqtt: ssh -> https --- 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 830c6743..12768c24 100644 --- a/external/opendaq/CMakeLists.txt +++ b/external/opendaq/CMakeLists.txt @@ -2,7 +2,7 @@ set(OPENDAQ_ENABLE_TESTS false) FetchContent_Declare( opendaq - GIT_REPOSITORY git@github.com:openDAQ/openDAQ.git + GIT_REPOSITORY https://github.com/openDAQ/openDAQ.git GIT_TAG v3.29.0-rc GIT_PROGRESS ON EXCLUDE_FROM_ALL From 7275434b45d5c64bc72298c66f5b207dcd15d1f6 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 17 Oct 2025 20:02:47 +0200 Subject: [PATCH 37/46] mqtt: OPENDAQ_MQTT_ENABLE_TESTS cmake flag --- CMakePresets.json | 3 ++- mqtt_streaming_client_module/CMakeLists.txt | 4 +++- mqtt_streaming_protocol/CMakeLists.txt | 4 +++- mqtt_streaming_server_module/CMakeLists.txt | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 67c7e926..888e8b7d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -95,7 +95,8 @@ "name": "full", "hidden": true, "cacheVariables": { - "OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS": "true" + "OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS": "true", + "OPENDAQ_MQTT_ENABLE_TESTS": "true" } }, { diff --git a/mqtt_streaming_client_module/CMakeLists.txt b/mqtt_streaming_client_module/CMakeLists.txt index 571e429e..91f88d29 100644 --- a/mqtt_streaming_client_module/CMakeLists.txt +++ b/mqtt_streaming_client_module/CMakeLists.txt @@ -8,4 +8,6 @@ message(STATUS "${MQTT_CLIENT_MODULE_PRJ_NAME} version: ${MQTT_CLIENT_MODULE_VER project(${MQTT_CLIENT_MODULE_PRJ_NAME} VERSION ${MQTT_CLIENT_MODULE_VERSION} LANGUAGES C CXX) add_subdirectory(src) -add_subdirectory(tests) +if (OPENDAQ_MQTT_ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/mqtt_streaming_protocol/CMakeLists.txt b/mqtt_streaming_protocol/CMakeLists.txt index 4113dc74..ad027558 100644 --- a/mqtt_streaming_protocol/CMakeLists.txt +++ b/mqtt_streaming_protocol/CMakeLists.txt @@ -8,4 +8,6 @@ message(STATUS "${MQTT_STREAMING_PROTOCOL_PRJ_NAME} version: ${MQTT_STREAMING_PR project(${MQTT_STREAMING_PROTOCOL_PRJ_NAME} VERSION ${MQTT_STREAMING_PROTOCOL_VERSION} LANGUAGES C CXX) add_subdirectory(src) -add_subdirectory(tests) +if (OPENDAQ_MQTT_ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/mqtt_streaming_server_module/CMakeLists.txt b/mqtt_streaming_server_module/CMakeLists.txt index fc5b2b2c..d8a37f2e 100644 --- a/mqtt_streaming_server_module/CMakeLists.txt +++ b/mqtt_streaming_server_module/CMakeLists.txt @@ -8,4 +8,6 @@ message(STATUS "${MQTT_SERVER_MODULE_PRJ_NAME} version: ${MQTT_SERVER_MODULE_VER project(${MQTT_SERVER_MODULE_PRJ_NAME} VERSION ${MQTT_SERVER_MODULE_VERSION} LANGUAGES C CXX) add_subdirectory(src) -add_subdirectory(tests) +if (OPENDAQ_MQTT_ENABLE_TESTS) + add_subdirectory(tests) +endif() From 3a54f563034b69bab5abc9efd051d1d18b56882a Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Fri, 17 Oct 2025 20:03:24 +0200 Subject: [PATCH 38/46] mqtt: OPENDAQ_MQTT_ENABLE_EXAMPLE_APPS cmake flag --- CMakePresets.json | 3 ++- examples/CMakeLists.txt | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 888e8b7d..3fbec0a1 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -96,7 +96,8 @@ "hidden": true, "cacheVariables": { "OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS": "true", - "OPENDAQ_MQTT_ENABLE_TESTS": "true" + "OPENDAQ_MQTT_ENABLE_TESTS": "true", + "OPENDAQ_MQTT_ENABLE_EXAMPLE_APPS": "true" } }, { diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d30841eb..3636c2e5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,6 @@ cmake_minimum_required(VERSION 3.16) -add_subdirectory(ref-dev-mqtt-pub) -add_subdirectory(ref-dev-mqtt-sub) \ No newline at end of file +if (OPENDAQ_MQTT_ENABLE_EXAMPLE_APPS) + add_subdirectory(ref-dev-mqtt-pub) + add_subdirectory(ref-dev-mqtt-sub) +endif() From bd5adc7b4eb12a4b04bb39e3abd93019ae05997c Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 20 Oct 2025 15:59:02 +0200 Subject: [PATCH 39/46] mqtt: cmake flags definition --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70683d6b..18f0e7c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,8 @@ if(OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS) set(DAQMODULES_REF_DEVICE_MODULE ON CACHE BOOL "" FORCE) endif() +option(OPENDAQ_MQTT_ENABLE_TESTS "Enable module testing" OFF) +option(OPENDAQ_MQTT_ENABLE_EXAMPLE_APPS "Enable example applications building" OFF) find_package(OpenSSL REQUIRED) if (OPENSSL_FOUND) From ca8816f67e069977296a3da37eb68aa30dc08ed2 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 20 Oct 2025 11:10:36 +0200 Subject: [PATCH 40/46] mqtt: useless mutex lock removing --- mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp | 1 - 1 file changed, 1 deletion(-) 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..7656c1c4 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -159,7 +159,6 @@ FunctionBlockPtr MqttStreamingDeviceImpl::onAddFunctionBlock(const StringPtr& ty { FunctionBlockPtr nestedFunctionBlock; { - auto lock = this->getAcquisitionLock(); if (fbTypes.hasKey(typeId)) { auto fbTypePtr = fbTypes.getOrDefault(typeId); From 18235861a3a0740031f06b5f72c817ac8b3c90a3 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 20 Oct 2025 14:32:35 +0200 Subject: [PATCH 41/46] mqtt: formatting only --- .../src/mqtt_receiver_fb_impl.cpp | 52 ++-- .../src/mqtt_streaming_client_module_impl.cpp | 34 ++- .../src/mqtt_streaming_device_impl.cpp | 84 +++--- .../src/MqttAsyncClient.cpp | 249 ++++++++++-------- .../src/mqtt_streaming_server_impl.cpp | 179 ++++++------- 5 files changed, 314 insertions(+), 284 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 7a4e7edf..aeaa2946 100644 --- a/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_receiver_fb_impl.cpp @@ -1,7 +1,10 @@ -#include +#include "MqttDataWrapper.h" +#include "mqtt_streaming_client_module/constants.h" #include "opendaq/data_packet_ptr.h" #include "opendaq/packet_factory.h" +#include #include +#include #include #include #include @@ -10,9 +13,6 @@ #include #include #include -#include -#include "mqtt_streaming_client_module/constants.h" -#include "MqttDataWrapper.h" BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE @@ -22,8 +22,8 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, const StringPtr& localId, std::shared_ptr subscriber, const PropertyObjectPtr& config) - : FunctionBlock(type, ctx, parent, localId) - , subscriber(subscriber) + : FunctionBlock(type, ctx, parent, localId), + subscriber(subscriber) { initComponentStatus(); @@ -35,7 +35,9 @@ MqttReceiverFbImpl::MqttReceiverFbImpl(const ContextPtr& ctx, for (const auto& topic : subscribedSignals.getKeys()) { - subscriber->setMessageArrivedCb(topic, std::bind(&MqttReceiverFbImpl::onSignalsMessage, this, std::placeholders::_1, std::placeholders::_2)); + 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); @@ -94,7 +96,8 @@ void MqttReceiverFbImpl::createDataPacket(const std::string& topic, double value auto lock = std::lock_guard(sync); auto signalIter = outputSignals.find(topic); auto dSignalIter = outputDomainSignals.find(topic); - if (signalIter == outputSignals.end() || dSignalIter == outputDomainSignals.end()) { + if (signalIter == outputSignals.end() || dSignalIter == outputDomainSignals.end()) + { return; } auto signal = signalIter->second; @@ -115,10 +118,14 @@ 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) { + if (status.ok) + { createDataPacket(topic, data.value, data.timestamp); - } else { - for (const auto& s : status.msg) { + } + else + { + for (const auto& s : status.msg) + { LOG_W("Data parsing: {}", s); } } @@ -134,23 +141,26 @@ void MqttReceiverFbImpl::createSignals() boost::replace_all(signalName, "/", "_"); auto signalDsc = descriptor; - auto getEpoch = []() ->std::string { + 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 }; + 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(); + 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(topic, createAndAddSignal(buildSignalNameFromTopic(topic), signalDsc))).first; - auto refSD = outputDomainSignals.emplace(std::make_pair(topic, createAndAddSignal(buildDomainSignalNameFromTopic(topic), domainSignalDsc, false))).first; + auto refSD = outputDomainSignals + .emplace(std::make_pair(topic, createAndAddSignal(buildDomainSignalNameFromTopic(topic), domainSignalDsc, false))) + .first; refS->second->setDomainSignal(refSD->second); } } 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 acfa55a2..a877ed4c 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 @@ -2,10 +2,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -23,11 +23,11 @@ static const std::regex RegexIpv4Hostname(R"(^(.+://)?([^:/\s]+)(?::(\d+))?(/.*) MqttStreamingClientModule::MqttStreamingClientModule(ContextPtr context) : Module(MODULE_NAME, - daq::VersionInfo(MQTT_STREAM_CLI_MODULE_MAJOR_VERSION, + daq::VersionInfo(MQTT_STREAM_CLI_MODULE_MAJOR_VERSION, MQTT_STREAM_CLI_MODULE_MINOR_VERSION, MQTT_STREAM_CLI_MODULE_PATCH_VERSION), - std::move(context), - MODULE_ID) + std::move(context), + MODULE_ID) { loggerComponent = this->context.getLogger().getOrAddComponent(SHORT_MODULE_NAME); } @@ -47,9 +47,8 @@ DictPtr MqttStreamingClientModule::onGetAvailableDeviceTyp return result; } -DevicePtr MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionString, - const ComponentPtr& parent, - const PropertyObjectPtr& config) +DevicePtr +MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionString, const ComponentPtr& parent, const PropertyObjectPtr& config) { if (!connectionString.assigned()) DAQ_THROW_EXCEPTION(ArgumentNullException); @@ -74,16 +73,13 @@ DevicePtr MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionS std::scoped_lock lock(sync); - device = createWithImplementation( - context, - parent, - configPtr - ); + device = createWithImplementation(context, parent, configPtr); // Set the connection info for the device ServerCapabilityConfigPtr connectionInfo = device.getInfo().getConfigurationConnectionInfo(); - const auto addressInfo = AddressInfoBuilder().setAddress(host) + const auto addressInfo = AddressInfoBuilder() + .setAddress(host) .setReachabilityStatus(AddressReachabilityStatus::Reachable) .setType(hostType) .setConnectionString(connectionString) @@ -100,7 +96,6 @@ DevicePtr MqttStreamingClientModule::onCreateDevice(const StringPtr& connectionS .addAddressInfo(addressInfo) .freeze(); - return device; } @@ -117,7 +112,9 @@ PropertyObjectPtr MqttStreamingClientModule::populateDefaultConfig(const Propert return defConfig; } -void MqttStreamingClientModule::extractConnectionParams(const StringPtr& connectionString, const PropertyObjectPtr& config, std::string& hostType) +void MqttStreamingClientModule::extractConnectionParams(const StringPtr& connectionString, + const PropertyObjectPtr& config, + std::string& hostType) { std::string urlString = connectionString.toStdString(); std::smatch match; @@ -189,7 +186,8 @@ Bool MqttStreamingClientModule::onCompleteServerCapability(const ServerCapabilit const auto addrInfos = source.getAddressInfo(); if (!addrInfos.assigned() || !addrInfos.getCount()) { - LOG_W("Source server capability addressInfo is not available when filling in missing MQTT capability information.") + LOG_W("Source server capability addressInfo is not available when filling in missing MQTT capability " + "information.") return false; } @@ -223,9 +221,7 @@ Bool MqttStreamingClientModule::onCompleteServerCapability(const ServerCapabilit .setConnectionString(connectionString) .build(); - target.addAddressInfo(targetAddrInfo) - .setConnectionString(connectionString) - .addAddress(address); + target.addAddressInfo(targetAddrInfo).setConnectionString(connectionString).addAddress(address); } return true; 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 7656c1c4..1a3bcd53 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -1,28 +1,26 @@ +#include "mqtt_streaming_client_module/constants.h" #include "mqtt_streaming_client_module/mqtt_receiver_fb_impl.h" #include -#include "mqtt_streaming_client_module/constants.h" -#include +#include +#include #include -#include #include +#include +#include #include -#include -#include -#include #include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE - std::atomic MqttStreamingDeviceImpl::localIndex = 0; +std::atomic MqttStreamingDeviceImpl::localIndex = 0; -MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, - const ComponentPtr& parent, - const PropertyObjectPtr& config) - : Device(ctx, parent, getLocalId()) - , connectionStatus(Enumeration("ConnectionStatusType", "Connected", this->context.getTypeManager())) - , subscriber(std::make_shared()) +MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const ComponentPtr& parent, const PropertyObjectPtr& config) + : Device(ctx, parent, getLocalId()), + connectionStatus(Enumeration("ConnectionStatusType", "Connected", this->context.getTypeManager())), + subscriber(std::make_shared()) { this->name = MQTT_DEVICE_NAME; @@ -32,11 +30,12 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, connectionSettings.password = config.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD).asPtr().toStdString(); connectionSettings.clientId = globalId.toStdString(); - connectionString = std::string(DaqMqttDevicePrefix) + "://" + connectionSettings.mqttUrl + ":" + std::to_string(connectionSettings.port); + connectionString = + std::string(DaqMqttDevicePrefix) + "://" + connectionSettings.mqttUrl + ":" + std::to_string(connectionSettings.port); int initTimeout = config.getPropertyValue(PROPERTY_NAME_INIT_DELAY); - initComponentStatus(); + initComponentStatus(); setupMqttSubscriber(); if (!waitForConnection(initTimeout)) @@ -70,12 +69,15 @@ void MqttStreamingDeviceImpl::setupMqttSubscriber() connectedPromise = std::promise(); connectedFuture = connectedPromise.get_future(); - subscriber->setConnectedCb([this] { - bool expected = false; - if (connectedDone.compare_exchange_strong(expected, true)) { - connectedPromise.set_value(true); - } - }); + subscriber->setConnectedCb( + [this] + { + bool expected = false; + if (connectedDone.compare_exchange_strong(expected, true)) + { + connectedPromise.set_value(true); + } + }); LOG_I("MQTT: Trying to connect to MQTT broker ({})", connectionSettings.mqttUrl); subscriber->connect(); @@ -83,9 +85,8 @@ void MqttStreamingDeviceImpl::setupMqttSubscriber() bool MqttStreamingDeviceImpl::waitForConnection(const int timeoutMs) { - bool res = (connectedFuture.wait_for(std::chrono::milliseconds(timeoutMs)) - == std::future_status::ready - && connectedFuture.get() == true); + bool res = + (connectedFuture.wait_for(std::chrono::milliseconds(timeoutMs)) == std::future_status::ready && connectedFuture.get() == true); subscriber->setConnectedCb(nullptr); return res; } @@ -93,11 +94,10 @@ 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) - ); + 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); @@ -108,9 +108,12 @@ 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) { + if (status.ok) + { deviceMap.insert({std::move(data.first), std::move(data.second)}); - } else { + } + else + { for (const auto& s : status.msg) LOG_W("Data error: {}", s); } @@ -123,11 +126,13 @@ DictPtr MqttStreamingDeviceImpl::onGetAvailableFunc { auto defaultConfig = PropertyObject(); auto signalDict = Dict(); - for (const auto& signal : device.second) { + for (const auto& signal : device.second) + { auto builder = DataDescriptorBuilder().setSampleType(SampleType::Float64); if (!signal.name.empty()) builder.setName(signal.name); - if (!signal.unit.empty()) { + if (!signal.unit.empty()) + { std::string symbol{""}; std::string name{""}; std::string quantity{""}; @@ -144,11 +149,7 @@ DictPtr MqttStreamingDeviceImpl::onGetAvailableFunc } defaultConfig.addProperty(DictProperty(PROPERTY_NAME_SIGNAL_LIST, signalDict)); - const auto fbType = FunctionBlockType(device.first, - device.first, - "", - defaultConfig); - + const auto fbType = FunctionBlockType(device.first, device.first, "", defaultConfig); fbTypes.set(fbType.getId(), fbType); } @@ -162,10 +163,17 @@ FunctionBlockPtr MqttStreamingDeviceImpl::onAddFunctionBlock(const StringPtr& ty if (fbTypes.hasKey(typeId)) { auto fbTypePtr = fbTypes.getOrDefault(typeId); - nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, typeId, subscriber, config); + nestedFunctionBlock = createWithImplementation(context, + functionBlocks, + fbTypePtr, + typeId, + subscriber, + config); addNestedFunctionBlock(nestedFunctionBlock); setComponentStatus(ComponentStatus::Ok); - } else { + } + else + { setComponentStatusWithMessage(ComponentStatus::Error, "Function block type is not available: " + typeId.toStdString()); } } diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index ab5fd8b2..2c275090 100644 --- a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp +++ b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp @@ -1,59 +1,64 @@ #include "MqttAsyncClient.h" -namespace mqtt { +namespace mqtt +{ -MqttAsyncClient::MqttAsyncClient() : MqttAsyncClient("", "", false) {} +MqttAsyncClient::MqttAsyncClient() + : MqttAsyncClient("", "", false) +{ +} MqttAsyncClient::MqttAsyncClient(std::string serverUrl, std::string clientId, bool cleanSession) - : clientId(clientId) - , username("") - , password("") - , pendingConnect(false) - , client(nullptr) + : 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.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.onSuccess = (MQTTAsync_onSuccess*)&MqttAsyncClient::onDisconnectSuccess; + disconnOpts.onFailure = (MQTTAsync_onFailure*)&MqttAsyncClient::onDisconnectFailure; disconnOpts.context = this; createOpts = MQTTAsync_createOptions_initializer; } -MqttAsyncClient::~MqttAsyncClient() { - if (client != nullptr) { +MqttAsyncClient::~MqttAsyncClient() +{ + if (client != nullptr) + { MQTTAsync_destroy(&client); } } -bool MqttAsyncClient::connect() { - if (client != nullptr) { +bool MqttAsyncClient::connect() +{ + if (client != nullptr) + { MQTTAsync_destroy(&client); } - if (serverUrl.empty() || clientId.empty()) { + if (serverUrl.empty() || clientId.empty()) + { return false; } - int rc = MQTTAsync_createWithOptions(&client, - serverUrl.c_str(), - clientId.c_str(), - MQTTCLIENT_PERSISTENCE_NONE, - NULL, - &createOpts); + int rc = MQTTAsync_createWithOptions(&client, serverUrl.c_str(), clientId.c_str(), MQTTCLIENT_PERSISTENCE_NONE, NULL, &createOpts); - if (rc != MQTTASYNC_SUCCESS) { + if (rc != MQTTASYNC_SUCCESS) + { return false; } @@ -63,37 +68,42 @@ bool MqttAsyncClient::connect() { &MqttAsyncClient::onMsgArrived, &MqttAsyncClient::onDeliveryCompleted); - if (rc != MQTTASYNC_SUCCESS) { + if (rc != MQTTASYNC_SUCCESS) + { return false; } rc = MQTTAsync_setConnected(client, this, &MqttAsyncClient::onConnected); - if (rc != MQTTASYNC_SUCCESS) { + if (rc != MQTTASYNC_SUCCESS) + { return false; } rc = MQTTAsync_connect(client, &connOpts); - if (rc != MQTTASYNC_SUCCESS) { + if (rc != MQTTASYNC_SUCCESS) + { return false; } pendingConnect = true; return true; } -bool MqttAsyncClient::disconnect() { +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 { +MqttConnectionStatus MqttAsyncClient::isConnected() const +{ if (pendingConnect) return MqttConnectionStatus::pending; - return MQTTAsync_isConnected(client) ? MqttConnectionStatus::connected - : MqttConnectionStatus::not_connected; + return MQTTAsync_isConnected(client) ? MqttConnectionStatus::connected : MqttConnectionStatus::not_connected; } -std::lock_guard MqttAsyncClient::getCbLock() { +std::lock_guard MqttAsyncClient::getCbLock() +{ return std::lock_guard(cbMtx); } @@ -106,29 +116,28 @@ 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) +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) { + if (client == nullptr) + { tmpErr = "MQTTAsync is nullptr"; } - if (topic.empty()) { + if (topic.empty()) + { tmpErr = "topic is empty"; } - if (qos > 2 || qos < 0) { + if (qos > 2 || qos < 0) + { tmpErr = "QoS is wrong"; } - if (!tmpErr.empty()) { - if (err != nullptr) { + if (!tmpErr.empty()) + { + if (err != nullptr) + { *err = std::move(tmpErr); } return false; @@ -137,52 +146,59 @@ bool MqttAsyncClient::publish(const std::string &topic, 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.onSuccess = (MQTTAsync_onSuccess*)&MqttAsyncClient::onSendSuccess; + opts.onFailure = (MQTTAsync_onFailure*)&MqttAsyncClient::onSendFailure; opts.context = this; pubmsg.payload = data; - pubmsg.payloadlen = (int) dataLen; + 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) { + if ((rc = MQTTAsync_sendMessage(client, topic.c_str(), &pubmsg, &opts)) != MQTTASYNC_SUCCESS) + { + if (err != nullptr) + { *err = MQTTAsync_strerror(rc); } } - if (token != nullptr) { + if (token != nullptr) + { *token = opts.token; } return rc == MQTTASYNC_SUCCESS; } -bool MqttAsyncClient::subscribe(std::string topic, int qos) { +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) { + 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) { +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.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; }); + 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) { +bool MqttAsyncClient::unsubscribeAll() +{ + for (auto& sub : subscriptions) + { unsubscribe(sub.topic); } subscriptions.clear(); @@ -190,32 +206,41 @@ bool MqttAsyncClient::unsubscribeAll() { return true; } -void MqttAsyncClient::setServerURL(std::string serverUrl) { - if (serverUrl != "") { +void MqttAsyncClient::setServerURL(std::string serverUrl) +{ + if (serverUrl != "") + { serverUrl = "tcp://" + serverUrl; } this->serverUrl = serverUrl; } -void MqttAsyncClient::setClientId(std::string clientId) { +void MqttAsyncClient::setClientId(std::string clientId) +{ this->clientId = clientId; } -std::string MqttAsyncClient::getServerUrl() const { return serverUrl; } +std::string MqttAsyncClient::getServerUrl() const +{ + return serverUrl; +} -void MqttAsyncClient::onDeliveryCompleted(void *context, MQTTAsync_token token) +void MqttAsyncClient::onDeliveryCompleted(void* context, MQTTAsync_token token) { - if (context != nullptr) { - auto clienttInst = (MqttAsyncClient *) context; + 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; +void MqttAsyncClient::onConnected(void* context, char* cause) +{ + if (context != nullptr) + { + auto clienttInst = (MqttAsyncClient*)context; clienttInst->pendingConnect = false; auto lock = clienttInst->getCbLock(); if (clienttInst->onConnectedCb) @@ -223,30 +248,30 @@ void MqttAsyncClient::onConnected(void *context, char *cause) { } } -void MqttAsyncClient::onConnectionLost(void *context, char *cause) { - (void) cause; +void MqttAsyncClient::onConnectionLost(void* context, char* cause) +{ + (void)cause; // Reconnect procedure here! - if (context != nullptr) { - auto clienttInst = (MqttAsyncClient *) context; + if (context != nullptr) + { + auto clienttInst = (MqttAsyncClient*)context; clienttInst->pendingConnect = false; } } -int MqttAsyncClient::onMsgArrived(void *context, - char *topicName, - int topicLen, - MQTTAsync_message *message) +int MqttAsyncClient::onMsgArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) { - if (context != nullptr && message != nullptr) { - MqttAsyncClient *client = (MqttAsyncClient *) context; + 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()) { + 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.addData((uint8_t*)message->payload, message->payloadlen); msg.setQos(message->qos); msg.setRetained(message->retained != 0); if (client->onMsgArrivedCmnCb) @@ -262,73 +287,87 @@ int MqttAsyncClient::onMsgArrived(void *context, return 1; } -void MqttAsyncClient::onSendSuccess(void *context, MQTTAsync_successData *data) +void MqttAsyncClient::onSendSuccess(void* context, MQTTAsync_successData* data) { - if (context != nullptr && data != nullptr) { - auto clienttInst = (MqttAsyncClient *) context; + 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) +void MqttAsyncClient::onSendFailure(void* context, MQTTAsync_failureData* data) { - if (context != nullptr && data != nullptr) { - auto clienttInst = (MqttAsyncClient *) context; + 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) +void MqttAsyncClient::onConnectSuccess(void* context, MQTTAsync_successData* data) { // TODO : check when this is called - if (context != nullptr) { - auto clienttInst = (MqttAsyncClient *) context; + if (context != nullptr) + { + auto clienttInst = (MqttAsyncClient*)context; clienttInst->pendingConnect = false; } } -void MqttAsyncClient::onConnectFailure(void *context, MQTTAsync_failureData *data) +void MqttAsyncClient::onConnectFailure(void* context, MQTTAsync_failureData* data) { // TODO : check when this is called - if (context != nullptr) { - auto clienttInst = (MqttAsyncClient *) context; + if (context != nullptr) + { + auto clienttInst = (MqttAsyncClient*)context; clienttInst->pendingConnect = false; } } -void MqttAsyncClient::onDisconnectSuccess(void *context, MQTTAsync_successData *data) +void MqttAsyncClient::onDisconnectSuccess(void* context, MQTTAsync_successData* data) { // TODO : check when this is called - if (context != nullptr) { - auto clienttInst = (MqttAsyncClient *) context; + 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) +void MqttAsyncClient::onDisconnectFailure(void* context, MQTTAsync_failureData* data) { // TODO : check when this is called - if (context != nullptr) { - auto clienttInst = (MqttAsyncClient *) context; + 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::onSubscribeSuccess(void* context, MQTTAsync_successData* response) +{ +} -void MqttAsyncClient::onSubscribeFailure(void *context, MQTTAsync_failureData *response) {} +void MqttAsyncClient::onSubscribeFailure(void* context, MQTTAsync_failureData* response) +{ +} -void MqttAsyncClient::onUnsubscribeSuccess(void *context, MQTTAsync_successData *response) {} +void MqttAsyncClient::onUnsubscribeSuccess(void* context, MQTTAsync_successData* response) +{ +} -void MqttAsyncClient::onUnsubscribeFailure(void *context, MQTTAsync_failureData *response) {} +void MqttAsyncClient::onUnsubscribeFailure(void* context, MQTTAsync_failureData* response) +{ +} void MqttAsyncClient::setConnectedCb(std::function cb) { 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 7e908d89..5a32d185 100644 --- a/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp +++ b/mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp @@ -1,65 +1,54 @@ -#include -#include -#include #include -#include -#include -#include -#include +#include +#include +#include #include -#include #include #include +#include +#include +#include +#include +#include #include #include #include - -#include -#include #include +#include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_SERVER_MODULE using namespace daq; -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()) - , logger(context.getLogger()) - , loggerComponent(logger.getOrAddComponent(id)) - , serverStopped(false) - , publisher() - , connectionSettings({DEFAULT_BROKER_ADDRESS, - DEFAULT_PORT, - DEFAULT_USERNAME, - DEFAULT_PASSWORD, - rootDevice.getGlobalId().toStdString()}) +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()), + logger(context.getLogger()), + loggerComponent(logger.getOrAddComponent(id)), + serverStopped(false), + publisher(), + 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)); + fmt::format("Device \"{}\" already has an {} server capability.", info.getName(), SERVER_ID_AND_CAPABILITY)); readMqttSettings(); - ServerCapabilityConfigPtr serverCapabilityStreaming = ServerCapability(SERVER_ID_AND_CAPABILITY, - SERVER_ID_AND_CAPABILITY, - ProtocolType::Streaming) - .setPrefix(MQTT_PREFIX) - .setConnectionType(CONNECTION_TYPE) - .setPort(connectionSettings.port); + ServerCapabilityConfigPtr serverCapabilityStreaming = + ServerCapability(SERVER_ID_AND_CAPABILITY, SERVER_ID_AND_CAPABILITY, ProtocolType::Streaming) + .setPrefix(MQTT_PREFIX) + .setConnectionType(CONNECTION_TYPE) + .setPort(connectionSettings.port); info.asPtr(true).addServerCapability(serverCapabilityStreaming); maxPacketReadCount = config.getPropertyValue(PROPERTY_NAME_MAX_PACKET_READ_COUNT); - processingThreadSleepTime = std::chrono::milliseconds( - config.getPropertyValue(PROPERTY_NAME_POLLING_PERIOD)); + processingThreadSleepTime = std::chrono::milliseconds(config.getPropertyValue(PROPERTY_NAME_POLLING_PERIOD)); buffer.data.resize(maxPacketReadCount); buffer.timestamps.resize(maxPacketReadCount); @@ -92,11 +81,14 @@ void MqttStreamingServerImpl::sendData(const std::string& topic, const ChannelDa return; const auto jsonMessages = prepareJsonMessages(data, readAmount); - if (publisher.isConnected() == mqtt::MqttConnectionStatus::connected) { - for (const auto& jsonMessage : jsonMessages) { + if (publisher.isConnected() == mqtt::MqttConnectionStatus::connected) + { + 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(), &err); + if (!status) + { LOG_W("Failed to publish data to {}; reason - {}", topic, err); } } @@ -107,7 +99,8 @@ std::vector MqttStreamingServerImpl::prepareJsonMessages(const Chan { std::vector result; - for (size_t i = 0; i < dataAmount; ++i) { + for (size_t i = 0; i < dataAmount; ++i) + { result.emplace_back(mqtt::MqttDataWrapper::serializeSampleData({data.data[i], data.timestamps[i]})); } @@ -123,11 +116,15 @@ void MqttStreamingServerImpl::sendTopicList() { 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); - if (!status) { + if (publisher.isConnected() == mqtt::MqttConnectionStatus::connected) + { + bool status = publisher.publish(topic, (void*)topicsMessage.c_str(), topicsMessage.length(), nullptr, 1, nullptr, true); + if (!status) + { LOG_W("Failed to publish topics list to {}", topic); - } else { + } + else + { topicsAreSent = true; } } @@ -163,16 +160,12 @@ void MqttStreamingServerImpl::processingThreadFunc() continue; daq::SizeT readAmount = maxPacketReadCount; reader.readWithDomain(buffer.data.data(), buffer.timestamps.data(), &readAmount); - sendData(mqtt::MqttDataWrapper::buildTopicFromId( - signals[i].getGlobalId().toStdString()), - buffer, - readAmount); + sendData(mqtt::MqttDataWrapper::buildTopicFromId(signals[i].getGlobalId().toStdString()), buffer, readAmount); if (reader.getAvailableCount() > 0) hasPacketsToRead = true; } - } - while(hasPacketsToRead); + } while (hasPacketsToRead); } std::this_thread::sleep_for(processingThreadSleepTime); @@ -232,11 +225,13 @@ void MqttStreamingServerImpl::connectSignalReaders() bool MqttStreamingServerImpl::isSignalCompatible(const SignalPtr& signal) { - if (!signal.getDomainSignal().assigned()) { + if (!signal.getDomainSignal().assigned()) + { LOG_I("Signal {} doesn't has domain signal assigned, skipping", signal.getGlobalId().toStdString()); return false; } - if (!signal.getDescriptor().assigned()) { + if (!signal.getDescriptor().assigned()) + { LOG_I("Signal {} doesn't has descriptor assigned, skipping", signal.getGlobalId().toStdString()); return false; } @@ -246,23 +241,16 @@ bool MqttStreamingServerImpl::isSignalCompatible(const SignalPtr& signal) return false; } if (const auto sampleType = signal.getDescriptor().getSampleType(); - sampleType != SampleType::Float64 && - sampleType != SampleType::Float32 && - sampleType != SampleType::Int8 && - sampleType != SampleType::Int16 && - sampleType != SampleType::Int32 && - sampleType != SampleType::Int64 && - sampleType != SampleType::UInt8 && - sampleType != SampleType::UInt16 && - sampleType != SampleType::UInt32 && + sampleType != SampleType::Float64 && sampleType != SampleType::Float32 && sampleType != SampleType::Int8 && + sampleType != SampleType::Int16 && sampleType != SampleType::Int32 && sampleType != SampleType::Int64 && + sampleType != SampleType::UInt8 && sampleType != SampleType::UInt16 && sampleType != SampleType::UInt32 && sampleType != SampleType::UInt64) { LOG_I("Signal {} has uncompatible sample type, skipping", signal.getGlobalId().toStdString()); return false; } if (const auto domainSampleType = signal.getDomainSignal().getDescriptor().getSampleType(); - domainSampleType != SampleType::Int64 && - domainSampleType != SampleType::UInt64) + domainSampleType != SampleType::Int64 && domainSampleType != SampleType::UInt64) { LOG_I("Signal {} has uncompatible domain signal sample type, skipping", signal.getGlobalId().toStdString()); return false; @@ -289,7 +277,7 @@ void MqttStreamingServerImpl::populateDefaultConfigFromProvider(const ContextPtr PropertyObjectPtr MqttStreamingServerImpl::createDefaultConfig(const ContextPtr& context) { - //auto defaultConfig = MqttStreamingServerHandler::createDefaultConfig(); + // auto defaultConfig = MqttStreamingServerHandler::createDefaultConfig(); auto defaultConfig = PropertyObject(); const auto pollingPeriodProp = IntPropertyBuilder(PROPERTY_NAME_POLLING_PERIOD, DEFAULT_POLLING_PERIOD) @@ -302,35 +290,29 @@ PropertyObjectPtr MqttStreamingServerImpl::createDefaultConfig(const ContextPtr& defaultConfig.addProperty(pollingPeriodProp); 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 " - "packets that can be read in one dequeue call. Should be greater " - "than the amount of packets generated per polling period for best " - "performance.") - .build(); + .setMinValue(1) + .setDescription("Specifies the size of a pre-allocated packet buffer into " + "which packets are dequeued. The size determines the amount of " + "packets that can be read in one dequeue call. Should be greater " + "than the amount of packets generated per polling period for best " + "performance.") + .build(); defaultConfig.addProperty(maxPacketReadCountProp); - const auto url = StringPropertyBuilder(PROPERTY_NAME_MQTT_BROKER_ADDRESS, DEFAULT_BROKER_ADDRESS) - .setDescription("") - .build(); + const auto url = StringPropertyBuilder(PROPERTY_NAME_MQTT_BROKER_ADDRESS, DEFAULT_BROKER_ADDRESS).setDescription("").build(); defaultConfig.addProperty(url); const auto port = IntPropertyBuilder(PROPERTY_NAME_MQTT_BROKER_PORT, DEFAULT_PORT) - .setMinValue(1) - .setMaxValue(65535) - .setDescription("Port is not used") - .build(); + .setMinValue(1) + .setMaxValue(65535) + .setDescription("Port is not used") + .build(); defaultConfig.addProperty(port); - const auto username = StringPropertyBuilder(PROPERTY_NAME_MQTT_USERNAME, DEFAULT_USERNAME) - .setDescription("") - .build(); + const auto username = StringPropertyBuilder(PROPERTY_NAME_MQTT_USERNAME, DEFAULT_USERNAME).setDescription("").build(); defaultConfig.addProperty(username); - const auto password = StringPropertyBuilder(PROPERTY_NAME_MQTT_PASSWORD, DEFAULT_PASSWORD) - .setDescription("") - .build(); + const auto password = StringPropertyBuilder(PROPERTY_NAME_MQTT_PASSWORD, DEFAULT_PASSWORD).setDescription("").build(); defaultConfig.addProperty(password); populateDefaultConfigFromProvider(context, defaultConfig); @@ -352,11 +334,10 @@ PropertyObjectPtr MqttStreamingServerImpl::populateDefaultConfig(const PropertyO ServerTypePtr MqttStreamingServerImpl::createType(const ContextPtr& context) { - return ServerType( - SERVER_ID_AND_CAPABILITY, - "openDAQ MQTT Streaming server", - "Streams data over MQTT", - MqttStreamingServerImpl::createDefaultConfig(context)); + return ServerType(SERVER_ID_AND_CAPABILITY, + "openDAQ MQTT Streaming server", + "Streams data over MQTT", + MqttStreamingServerImpl::createDefaultConfig(context)); } void MqttStreamingServerImpl::onStopServer() @@ -369,18 +350,14 @@ void MqttStreamingServerImpl::addReader(SignalPtr signalToRead) std::scoped_lock lock(readersSync); signals.pushBack(signalToRead); streamReaders.emplace_back(StreamReaderBuilder() - .setSignal(signalToRead) - .setValueReadType(SampleType::Float64) - .setDomainReadType(SampleType::Int64) - .setSkipEvents(true) - .build()); + .setSignal(signalToRead) + .setValueReadType(SampleType::Float64) + .setDomainReadType(SampleType::Int64) + .setSkipEvents(true) + .build()); } OPENDAQ_DEFINE_CLASS_FACTORY_WITH_INTERFACE( - INTERNAL_FACTORY, MqttStreamingServer, daq::IServer, - daq::DevicePtr, rootDevice, - PropertyObjectPtr, config, - const ContextPtr&, context -) + INTERNAL_FACTORY, MqttStreamingServer, daq::IServer, daq::DevicePtr, rootDevice, PropertyObjectPtr, config, const ContextPtr&, context) END_NAMESPACE_OPENDAQ_MQTT_STREAMING_SERVER_MODULE From 9ce2b9bc22edf464947206282bec8a6d24234138 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 20 Oct 2025 15:11:07 +0200 Subject: [PATCH 42/46] mqtt: pthread issue fixing --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 18f0e7c0..9c799cdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,12 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") option(OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS "Enable building example applications" OFF) +if ((CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) AND NOT MSVC) + if (NOT WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") + endif() +endif() + include(CommonUtils) setup_repo(${REPO_OPTION_PREFIX}) From 024af3554c526c44e53406a0e8c41f9b7af59fca Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Mon, 20 Oct 2025 16:59:32 +0200 Subject: [PATCH 43/46] mqtt: enabling PAHO tests according to OPENDAQ_MQTT_ENABLE_TESTS --- external/mqtt/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/external/mqtt/CMakeLists.txt b/external/mqtt/CMakeLists.txt index cea3bc6b..17cbea8e 100644 --- a/external/mqtt/CMakeLists.txt +++ b/external/mqtt/CMakeLists.txt @@ -16,6 +16,7 @@ set(PAHO_WITH_SSL ON CACHE BOOL "Enable SSL support" FORCE) 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) +set(PAHO_ENABLE_TESTING ${OPENDAQ_MQTT_ENABLE_TESTS} CACHE BOOL "" FORCE) # Now add the project add_subdirectory(${paho_mqtt_c_SOURCE_DIR} ${paho_mqtt_c_BINARY_DIR}) From 033c40661ec4a5cbef6ca530a5629d105c793ea6 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 21 Oct 2025 11:43:07 +0200 Subject: [PATCH 44/46] mqtt: disconnecting on device removing --- .../mqtt_streaming_device_impl.h | 2 +- .../src/mqtt_streaming_device_impl.cpp | 10 ++-- .../include/MqttAsyncClient.h | 2 + .../src/MqttAsyncClient.cpp | 46 ++++++++++++++++++- 4 files changed, 55 insertions(+), 5 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 f294d782..05c77b14 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,7 +47,7 @@ class MqttStreamingDeviceImpl : public Device DictPtr onGetAvailableFunctionBlockTypes() override; FunctionBlockPtr onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) override; - void setupMqttSubscriber(); + void initMqttSubscriber(); bool waitForConnection(const int timeoutMs); void receiveSignalTopics(const int timeoutMs); void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, mqtt::MqttMessage& msg); 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 1a3bcd53..5ab1e7ef 100644 --- a/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp +++ b/mqtt_streaming_client_module/src/mqtt_streaming_device_impl.cpp @@ -17,6 +17,8 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_CLIENT_MODULE std::atomic MqttStreamingDeviceImpl::localIndex = 0; +constexpr int MQTT_CLIENT_SYNC_DISCONNECT_TOUT = 3000; + MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const ComponentPtr& parent, const PropertyObjectPtr& config) : Device(ctx, parent, getLocalId()), connectionStatus(Enumeration("ConnectionStatusType", "Connected", this->context.getTypeManager())), @@ -37,7 +39,7 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const Co initComponentStatus(); - setupMqttSubscriber(); + initMqttSubscriber(); if (!waitForConnection(initTimeout)) { LOG_E("MQTT: could not connect to MQTT broker within {} ms", initTimeout); @@ -50,6 +52,9 @@ MqttStreamingDeviceImpl::MqttStreamingDeviceImpl(const ContextPtr& ctx, const Co void MqttStreamingDeviceImpl::removed() { + bool disRes = subscriber->syncDisconnect(MQTT_CLIENT_SYNC_DISCONNECT_TOUT); + if (!disRes) + LOG_E("MQTT: disconnection was unsuccessful"); Device::removed(); } @@ -58,9 +63,8 @@ DeviceInfoPtr MqttStreamingDeviceImpl::onGetInfo() return DeviceInfo(connectionString, MQTT_DEVICE_NAME); } -void MqttStreamingDeviceImpl::setupMqttSubscriber() +void MqttStreamingDeviceImpl::initMqttSubscriber() { - subscriber->disconnect(); subscriber->setServerURL(connectionSettings.mqttUrl); subscriber->setClientId(connectionSettings.clientId); subscriber->setUsernamePasswrod(connectionSettings.username, connectionSettings.password); diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/mqtt_streaming_protocol/include/MqttAsyncClient.h index 964b47f6..d2c32a0c 100644 --- a/mqtt_streaming_protocol/include/MqttAsyncClient.h +++ b/mqtt_streaming_protocol/include/MqttAsyncClient.h @@ -40,6 +40,7 @@ class MqttAsyncClient final { bool connect(); bool disconnect(); + bool syncDisconnect(int timeoutMs); bool publish(const std::string &topic, void *data, @@ -84,6 +85,7 @@ class MqttAsyncClient final { std::function onConnectedCb; std::function onSentCb; std::function onDisconnectCb; + std::function onInternalDisconnectCb; std::function onDeliveryCompletedCb; std::function onMsgArrivedCmnCb; std::unordered_map> onMsgArrivedCbs; diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/mqtt_streaming_protocol/src/MqttAsyncClient.cpp index 2c275090..ca52a250 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 { @@ -90,8 +91,47 @@ bool MqttAsyncClient::connect() bool MqttAsyncClient::disconnect() { + if (client == nullptr) + return true; + // It is only the result of the request to disconnect (queuing) - return MQTTAsync_disconnect(client, &disconnOpts) == MQTTASYNC_SUCCESS; + auto status = MQTTAsync_disconnect(client, &disconnOpts); + bool result = (status == MQTTASYNC_SUCCESS || status == MQTTASYNC_DISCONNECTED); + return result; +} + +bool MqttAsyncClient::syncDisconnect(int timeoutMs) +{ + if (client == nullptr) + return true; + + bool result = disconnect(); + if (result) + { + 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; + } + if (status == std::future_status::ready && disconnectedFuture.get() == true) + { + MQTTAsync_destroy(&client); + return true; + } + } + return false; } MqttConnectionStatus MqttAsyncClient::isConnected() const @@ -336,6 +376,8 @@ void MqttAsyncClient::onDisconnectSuccess(void* context, MQTTAsync_successData* { auto clienttInst = (MqttAsyncClient*)context; auto lock = clienttInst->getCbLock(); + if (clienttInst->onInternalDisconnectCb) + clienttInst->onInternalDisconnectCb(true); if (clienttInst->onDisconnectCb) clienttInst->onDisconnectCb(true); } @@ -348,6 +390,8 @@ void MqttAsyncClient::onDisconnectFailure(void* context, MQTTAsync_failureData* { auto clienttInst = (MqttAsyncClient*)context; auto lock = clienttInst->getCbLock(); + if (clienttInst->onInternalDisconnectCb) + clienttInst->onInternalDisconnectCb(false); if (clienttInst->onDisconnectCb) clienttInst->onDisconnectCb(false); } From 3889cfa2763654d2c631dc0597d4c6484d8f97df Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 21 Oct 2025 15:09:01 +0200 Subject: [PATCH 45/46] mqtt: removing unused boost asio --- mqtt_streaming_server_module/src/CMakeLists.txt | 2 -- mqtt_streaming_server_module/src/mqtt_streaming_server_impl.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/mqtt_streaming_server_module/src/CMakeLists.txt b/mqtt_streaming_server_module/src/CMakeLists.txt index 461706db..b78e3e59 100644 --- a/mqtt_streaming_server_module/src/CMakeLists.txt +++ b/mqtt_streaming_server_module/src/CMakeLists.txt @@ -32,7 +32,6 @@ 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} @@ -42,7 +41,6 @@ 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 $ 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 5a32d185..b48ba788 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 From dd0e0707ddf32af9d87c3b8d9dbea57e749e3089 Mon Sep 17 00:00:00 2001 From: Viacheslau Date: Tue, 21 Oct 2025 15:20:29 +0200 Subject: [PATCH 46/46] mqtt: PAHO_ENABLE_TESTING is always ON --- external/mqtt/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/mqtt/CMakeLists.txt b/external/mqtt/CMakeLists.txt index 17cbea8e..96dca57d 100644 --- a/external/mqtt/CMakeLists.txt +++ b/external/mqtt/CMakeLists.txt @@ -16,7 +16,7 @@ set(PAHO_WITH_SSL ON CACHE BOOL "Enable SSL support" FORCE) 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) -set(PAHO_ENABLE_TESTING ${OPENDAQ_MQTT_ENABLE_TESTS} CACHE BOOL "" FORCE) +set(PAHO_ENABLE_TESTING ON CACHE BOOL "" FORCE) # Now add the project add_subdirectory(${paho_mqtt_c_SOURCE_DIR} ${paho_mqtt_c_BINARY_DIR})