diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ad302b5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: Build and Test + +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + +jobs: + build-and-test: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + generator: Ninja + - os: windows-latest + generator: "Visual Studio 17 2022" + + runs-on: ${{ matrix.os }} + + steps: + - name: Install additional dependencies + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get install -y --no-install-recommends mono-runtime libmono-system-json-microsoft4.0-cil libmono-system-data4.0-cil + + - name: Checkout project repo + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch || github.event.client_payload.branch || github.ref }} + + - name: Configure project with CMake + run: cmake -B build/output -S . -G "${{ matrix.generator }}" -DOPENDAQ_MQTT_ENABLE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug + + - name: Build project with CMake + run: cmake --build build/output --config Debug + + - name: Install and run mosquitto Linux + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get install -y --no-install-recommends mosquitto + + - name: Run project tests with CMake + if: matrix.os == 'ubuntu-latest' + run: ctest --test-dir build/output --output-on-failure -C Debug diff --git a/.gitignore b/.gitignore index 8de9dce..17b3a1f 100644 --- a/.gitignore +++ b/.gitignore @@ -53,7 +53,11 @@ include/* lib/* bin/* test/test_runner +docker/* +build*/* CMakeLists.txt.user .vscode .clang-format +.dockerignore + diff --git a/CMakeLists.txt b/CMakeLists.txt index 63dad17..bf86d18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,10 +61,15 @@ if(OPENDAQ_MQTT_MODULE_ENABLE_SSL) add_compile_definitions(OPENDAQ_MQTT_MODULE_ENABLE_SSL) endif() +set(MQTT_MODULE_VERSION "0.1.0" CACHE STRING "MQTT module version" FORCE) + +if(OPENDAQ_MQTT_ENABLE_TESTS) + enable_testing() +endif() + add_subdirectory(external) -add_subdirectory(helper_utils) -add_subdirectory(mqtt_streaming_protocol) -add_subdirectory(mqtt_streaming_module) +add_subdirectory(shared) +add_subdirectory(modules) if(OPENDAQ_DEVICE_EXAMPLE_ENABLE_EXAMPLE_APPS) message(STATUS "Example applications have been enabled") diff --git a/README.md b/README.md index d198b00..ebaec0e 100644 --- a/README.md +++ b/README.md @@ -2,71 +2,68 @@ ## Description -MQTT module for the [OpenDAQ SDK](https://github.com/openDAQ/openDAQ). The module is designed for software communication via the *MQTT 3.1.1* protocol using an external broker. It allows publishing and subscribing to openDAQ signal data over MQTT. The module consists of five key openDAQ components: the *MQTT root function block* (**rootMqttFb**) and its nested function blocks — the *publisher* (**publisherMqttFb**) with its nested block *JSON decoder* (**jsonDecoderMqttFb**) , the *raw subscriber* (**rawMqttFb**), and the *JSON subscriber* (**jsonMqttFb**). +MQTT module for the [OpenDAQ SDK](https://github.com/openDAQ/openDAQ). The module is designed for software communication via the *MQTT 3.1.1* protocol using an external broker. It allows publishing and subscribing to openDAQ signal data over MQTT. The module consists of five key openDAQ components: the *MQTT client function block* (**MQTTClientFB**) and its nested function blocks — the *publisher* (**MQTTJSONPublisherFB**) and the *subscriber* (**MQTTSubscriberFB**) with its nested block *JSON decoder* (**MQTTJSONDecoderFB**). ### Functional - Connecting to an MQTT broker; -- Publishing openDAQ signals as MQTT messages (*publisher FB*); -- Subscribing to MQTT topics and converting incoming messages into openDAQ signals (*raw FB and JSON FB + JSON decoder FB*); +- Publishing openDAQ signals as MQTT JSON messages (*publisher FB*); +- Subscribing to MQTT topics and converting incoming messages into openDAQ signals (*subscriber FB, JSON decoder FB*); - Support for multiple message types and formats for both publishing and subscribing; - A set of examples and *gtests* for verifying functionality. ### Key components -1) **MQTT root Function Block (rootMqttFb)**: - - **Where**: *mqtt_streaming_module/src/mqtt_root_fb_impl.cpp, include/mqtt_streaming_module/...* +1) **MQTT client Function Block (MQTTClientFB)**: + - **Where**: *mqtt_streaming_module/src/mqtt_client_fb_impl.cpp, include/mqtt_streaming_module/...* - **Purpose**: Represents the MQTT broker as an openDAQ function block - the connection point through which function blocks are created. - **Main properties:** - - *MqttBrokerAddress* (string) - MQTT broker address. It can be an IP address or a hostname. By default, it is set to *"127.0.0.1"*. - - *MqttBrokerPort* (integer) - Port number for the MQTT broker connection. By default, it is set to *1883*. - - *MqttUsername* (string) — Username for MQTT broker authentication. By default, it is empty. - - *MqttPassword* (string) — Password for MQTT broker authentication. By default, it is empty. - - *ConnectTimeout* (integer) — Timeout in milliseconds for the initial connection to the MQTT broker. If the connection fails, an exception is thrown. By default, it is set to *3000 ms*. -2) **Publisher MQTT Function Block (publisherMqttFb)**: + - *BrokerAddress* (string) - MQTT broker address. It can be an IP address or a hostname. By default, it is set to *"127.0.0.1"*. + - *BrokerPort* (integer) - Port number for the MQTT broker connection. By default, it is set to *1883*. + - *Username* (string) — Username for MQTT broker authentication. By default, it is empty. + - *Password* (string) — Password for MQTT broker authentication. By default, it is empty. + - *ConnectionTimeout* (integer) — Timeout in milliseconds for the initial connection to the MQTT broker. If the connection fails, an exception is thrown. By default, it is set to *3000 ms*. +2) **MQTT publisher Function Block (MQTTJSONPublisherFB)**: - **Where**: *include/mqtt_streaming_module/mqtt_publisher_fb_impl.h, src/mqtt_publisher_fb_impl.cpp* - **Purpose**: Publishes openDAQ signal data to MQTT topics. There are **four** general data publishing schemes: 1) One MQTT message per signal / one message per sample / one topic per signal / one timestamp for each sample. Example: *{"AI0": 1.1, "timestamp": 1763716736100000}* 2) One MQTT message per signal / one message containing several samples / one topic per signal / one timestamp per sample (array of samples). Example: *{"AI0": [1.1, 2.2, 3.3], "timestamps": [1763716736100000, 1763716736200000, 1763716736300000]}* - - 3) One MQTT message for several signals (from 1 to N) / one message per sample for each signal / one topic for all signals / separate timestamps for each signal. Example: *[{"AI0": 1.1, "timestamp": 1763716736100000}, {"AI1": 2, "timestamp": 1763716736700000}]* - 4) One MQTT message for all signals / one message per sample containing all signals / one topic for all signals / one shared timestamp for all signals. Example: *{"AI0": 1.1, "AI1": 2, "timestamp": 1763716736100000}* + 3) One MQTT message for all signals / one message per sample containing all signals / one topic for all signals / one shared timestamp for all signals. Example: *{"AI0": 1.1, "AI1": 2, "timestamp": 1763716736100000}* + + 4) One MQTT message for all signals / one message containing several samples for all signals / one topic for all signals / one shared timestamp for all signals (array of samples). Example: *{"AI0": [1.1, 2.2, 3.3], "AI1": [4.1, 4.2, 4.3], "timestamp": [1763716736100000, 1763716736200000, 1763716736300000]}* The schemes are configured through combinations of properties. - **Main properties**: - - *TopicMode* (list) — Selects whether to publish all signals to separate MQTT topics (one per signal, *single-topic mode*) or to a single topic (*multiple-topic mode*), one for all signals. Choose *0* for *single-topic* mode and *1* for *multiple-topic* mode. By default, it is set to *single-topic* mode. - - *MqttQoS* (integer) — MQTT Quality of Service level. It can be *0* (at most once), *1* (at least once), or *2* (exactly once). By default, it is set to *1*. - - *Topic* (string) — Topic name for publishing in multiple-topic mode. If left empty, the Publisher's *Global ID* is used as the topic name. - - *SharedTimestamp* (bool) — Enables the use of a shared timestamp for all signals when publishing in *multiple-topic* mode. By default, it is set to *false*. - - *GroupValues* (bool) — Enables the use of a sample pack for a signal when publishing in *single-topic* mode. By default, it is set to *false*. - - *UseSignalNames* (bool) — Uses signal names as JSON field names instead of Global IDs. By default, it is set to *false*. - - *GroupValuesPackSize* (integer) — Sets the size of the sample pack when publishing grouped values in *single-topic* mode. By default, it is set to *1*. - - *ReaderPeriod* (integer) — Polling period in milliseconds, specifying how often the server collects and publishes the connected signals’ data to an MQTT broker. By default, it is set to *20 ms*. + - *TopicMode* (list) — Selects whether to publish all signals to separate MQTT topics (one per signal, *TopicPerSignal mode*) or to a single topic (*SingleTopic mode*), one for all signals. Choose *0* for *TopicPerSignal* mode and *1* for *SingleTopic* mode. By default, it is set to *TopicPerSignal* mode. + - *QoS* (list) — MQTT Quality of Service level. It can be *0* (at most once), *1* (at least once), or *2* (exactly once). By default, it is set to *1*. + - *Topic* (string) — Topic name for publishing in *SingleTopic* mode. If left empty, the Publisher's *Global ID* is used as the topic name. + - *Topics* (list of strings, read-only) — Contains a list of topics used for publishing data in the *TopicPerSignal* mode. The order in the list is the same as the input ports order. + - *GroupValues* (bool) — Enables the use of a sample pack for a signal. By default, it is set to *false*. + - *SignalValueJSONKey* (list) — Describes how to name a JSON value field. By default it is set to *GlobalID*. + - *SamplesPerMessage* (integer) — Sets the size of the sample pack when publishing grouped values. By default, it is set to *1*. + - *ReaderWaitPeriod* (integer) — Polling period in milliseconds, specifying how often the server calls an internal reader to collect and publish the connected signals’ data to an MQTT broker. By default, it is set to *20 ms*. + - *EnablePreviewSignal* (bool) — Enable the creation of preview signals: one signal in *SingleTopic* mode and one signal per connected input port in *TopicPerSignal* mode. These signals contain the same JSON string data that is published to MQTT topics. + - *Schema* (string, read-only) - Describes the general representation of a JSON data packet according to the current function block settings. To configure the publishing schemes, set the properties as follows: - 1) *TopicMode(0), SharedTimestamp(false), GroupValues(false)*; - 2) *TopicMode(0), SharedTimestamp(false), GroupValues(true), GroupValuesPackSize()*; - 3) *TopicMode(1), SharedTimestamp(false), GroupValues(false)*; - 4) *TopicMode(1), SharedTimestamp(true), GroupValues(false)*; + 1) *TopicMode(0), GroupValues(false)*; + 2) *TopicMode(0), GroupValues(true), SamplesPerMessage()*; + 3) *TopicMode(1), GroupValues(false)*; + 4) *TopicMode(1), GroupValues(true), SamplesPerMessage()*; -3) **Raw MQTT Function Block (rawMqttFb)**: +3) **MQTT subscriber Function Block (MQTTSubscriberFB)**: - - **Where**: *include/mqtt_streaming_module/mqtt_raw_receiver_fb_impl.h, src/mqtt_raw_receiver_fb_impl.cpp* - - **Purpose**: Subscribes to raw MQTT messages and converts them into openDAQ signals (binary data) without any parsing — suitable for binary/unstructured messages or simple numeric values. + - **Where**: *include/mqtt_streaming_module/mqtt_subscriber_fb_impl.h, src/mqtt_subscriber_fb_impl.cpp* + - **Purpose**: Subscribes to raw MQTT messages and converts them into openDAQ signals (binary data) without any parsing — suitable for binary/unstructured messages, simple numeric values or for further processing by nested blocks (**MQTTJSONDecoderFB**). - **Main properties**: - *Topic* (string) — MQTT topic to subscribe to for receiving raw binary data. - - *MqttQoS* (integer) — MQTT Quality of Service level. It can be *0* (at most once), *1* (at least once), or *2* (exactly once). By default, it is set to *1*. - -4) **JSON MQTT Function Block (jsonMqttFb)**: - - **Where**: *include/mqtt_streaming_module/mqtt_json_receiver_fb_impl.h, src/mqtt_json_receiver_fb_impl.cpp* - - **Purpose**: Subscribes to MQTT topics, extracts values and timestamps from MQTT JSON messages via nested *JSON decoder MQTT Function Blocks*. - - **Main properties**: - - *Topic* (string) — MQTT topic to subscribe to for receiving JSON data. - - *MqttQoS* (integer) — MQTT Quality of Service level. It can be *0* (at most once), *1* (at least once), or *2* (exactly once). By default, it is set to *1*. - - *JsonConfigFile* (string) — path to file with **JSON configuration string**. See the *JsonConfig* property for more details. This property could be set only at creation. It is not visible. - - *JsonConfig* (string) — **JSON configuration string** that defines the MQTT topic and the corresponding signals to subscribe to. This property could be set only at creation. It is not visible. A typical string structure: + - *QoS* (list) — MQTT Quality of Service level. It can be *0* (at most once), *1* (at least once), or *2* (exactly once). By default, it is set to *1*. + - *EnablePreviewSignal* (bool) — Enable the creation of a preview signal. This signal contains the raw binary data received from an MQTT topic. + - *MessageIsString* (bool) — Interpret a received message as a string. + - *JSONConfigFile* (string) — path to file with **JSON configuration string**. See the *JSONConfig* property for more details. This property could be set only at creation. It is not visible. + - *JSONConfig* (string) — **JSON configuration string** that defines the MQTT topic and the corresponding signals to subscribe to. This property could be set only at creation. It is not visible. A typical string structure: ```json { "":[ @@ -117,21 +114,20 @@ MQTT module for the [OpenDAQ SDK](https://github.com/openDAQ/openDAQ). The modul ] } ``` - In this example, the *JSON MQTT Function Block* creates 3 nested *jsonDecoderMqttFb*, subscribes to the *"/mirip/UNet3AC2/sensor/data"* topic, and extracts 3 signal samples from each message (one sample per *jsonDecoderMqttFb*). The signals are named *“temp”*, *“humidity”*, and *“tds”*. The *“temp”* signal is created with a domain signal because the *“Timestamp”* field is present. Each domain-signal sample is extracted from the *“ts”* field of the JSON MQTT message. The value of the *“ts”* field (the timestamp field) may be in **ISO8601** format or **Unix epoch time** in seconds, milliseconds, or microseconds. The value of the *“temp”* signal sample is extracted from the *“temp”* field of the JSON message. The unit of the values is “°C”. + In this example, the *MQTT subscriber Function Block* creates 3 nested *MQTT JSON decoder Function Block*, subscribes to the *"/mirip/UNet3AC2/sensor/data"* topic, and extracts 3 signal samples from each message (one sample per *jsonDecoderMqttFb*). The *“temp”* signal is created with a domain signal because the *“Timestamp”* field is present. Each domain-signal sample is extracted from the *“ts”* field of the JSON MQTT message. The value of the *“ts”* field (the timestamp field) may be in **ISO8601** format or **Unix epoch time** in seconds, milliseconds, or microseconds. The value of the *“temp”* signal sample is extracted from the *“temp”* field of the JSON message. The unit of the values is “°C”. Example of JSON MQTT message for this configuration: ```json {"ts":"2025-10-08 20:35:43", "bdn":"SanbonFishTank3", "temp":27.20,"humi":72.40, "tds_value":275.22, "fan_status":"off", "auto_mode":"on", "fan_comp":"26.3", "humi_comp":"55"} ``` -5) **JSON decoder MQTT Function Block (jsonDecoderMqttFb)**: +4) **MQTT JSON decoder Function Block (MQTTJSONDecoderFB)**: - **Where**: *include/mqtt_streaming_module/mqtt_json_decoder_fb_impl.h, src/mqtt_json_decoder_fb_impl.cpp* - **Purpose**: To parse JSON string data to extract a value and a timestamp, and to send data and domain samples based on this data. - **Main properties**: - - *ValueName* (string) — indicates which JSON field contains the sample value. - - *TimestampName* (string) — indicates which JSON field contains the timestamp. - - *Unit* (string) — describes the unit symbol of the decoded signal value. - - *SignalName* (string) — specifies the name to assign to the signal created by a *jsonDecoderMqttFb*. + - *ValueKey* (string) — Specifies the JSON field name from which value data will be extracted. This property is required. It should be contained in the incoming JSON messages. Otherwise, a parsing error will occur. + - *DomainKey* (string) — Specifies the JSON field name from which timestamp will be extracted. This property is optional. If it is set it should be contained in the incoming JSON messages. Otherwise, a parsing error will occur. + - *Unit* (string) — Specifies the unit symbol for the decoded value. This property is optional. --- ## Building MQTTStreamingModule @@ -179,21 +175,19 @@ cmake --build . ## Examples There are 3 example C++ application: - - **custom-mqtt-sub** - demonstrates how to work with the *JSON receiver MQTT FB* and *JSON decoder MQTT FB*. The application creates an *MQTT root FB* and a *JSON MQTT FB* to receive JSON MQTT messages, parse them, and create openDAQ signals to send the parsed data. The application also creates *packet readers* for all FB signals and prints the samples to standard output. The *JsonConfigFile* property of the JSON MQTT FB is set to the value of path whose is provided as a command-line argument when the application starts (see the **Key components** section). Usage: + - **custom-mqtt-sub** - demonstrates how to work with the *MQTT subscriber MQTT FB* and *MQTT JSON decoder MQTT FB*. The application creates an *MQTTClientFB* and a *MQTTSubscriberFB* with nested *MQTTJSONDecoderFB* function blocks to receive JSON MQTT messages, parse them, and create openDAQ signals to send the parsed data. The application also creates *packet readers* for all FB signals and prints the samples to standard output. The *JSONConfigFile* property of the *MQTTSubscriberFB* is set to the value of path whose is provided as a command-line argument when the application starts (see the **Key components** section). Usage: ```bash ./custom-mqtt-sub --address broker.emqx.io examples/custom-mqtt-sub/public-example0.json ``` - - **raw-mqtt-sub** - demonstrates how to work with the *raw MQTT FB*. The application creates an *MQTT root FB* and a *raw MQTT FB* to receive MQTT messages and create openDAQ signals to send the data as binary packets. The application also creates packet readers for all FB signals and prints the binary packets as strings to standard output. The *Topic* property of the raw MQTT FB is filled from the application arguments. Usage: + - **raw-mqtt-sub** - demonstrates how to work with the *MQTT subscriber MQTT FB* in a raw mode (binary data without parsing). The application creates an *MQTTClientFB* and a *MQTTSubscriberFB* to receive MQTT messages and create openDAQ signals to send the data as binary packets. The application also creates packet readers for all FB signals and prints the binary packets as strings to standard output. The *Topic* property of the *MQTTSubscriberFB* is filled from the application arguments. Usage: ```bash ./raw-mqtt-sub --address broker.emqx.io /mirip/UNet3AC2/sensor/data ``` - - **ref-dev-mqtt-pub** - demonstrates how to work with the *publisher MQTT FB*. The application creates an *openDAQ ref-device* with four channels, an *MQTT root FB*, and a *publisher MQTT FB* to publish JSON MQTT messages with the channels’ data. The properties of the *publisher MQTT FB* are set according to the selected mode, which can be specified via the *--mode* option. Posible values are: - - 0 - One MQTT message per signal / one message per sample / one topic per signal / one timestamp for each sample; - - 1 - One MQTT message per signal / one message containing several samples / one topic per signal / one timestamp per sample (array of samples); - - 2 - One MQTT message for several signals (from 1 to N) / one message per sample for each signal / one topic for all signals / separate timestamps for each signal; - - 3 - One MQTT message for all signals / one message per sample containing all signals / one topic for all signals / one shared timestamp for all signals. + - **ref-dev-mqtt-pub** - demonstrates how to work with the *MQTTJSONPublisherFB*. The application creates an *openDAQ ref-device* with four channels, an *MQTTClientFB*, and a *MQTTJSONPublisherFB* to publish JSON MQTT messages with the channels’ data. The properties of the *MQTTJSONPublisherFB* are set according to the selected mode, which can be specified via the *--mode* option, and array size, which can be specified via the *--array* option with size. Posible *--mode* option values are: + - 0 - One MQTT topic per signal; + - 1 - One MQTT message/topic for all signals. ```bash - ./ref-dev-mqtt-pub --address broker.emqx.io --mode 1 + ./ref-dev-mqtt-pub --address broker.emqx.io --mode 1 --array 5 ``` Published messages can be observed using third-party tools (see the **External MQTT tools** section). For all applications, by default, the IP address *127.0.0.1* is used for the broker connection. It can be set via the *--address* option, for example: diff --git a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp index 4b13367..01678b8 100644 --- a/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp +++ b/examples/custom-mqtt-sub/src/custom-mqtt-sub.cpp @@ -46,6 +46,7 @@ std::string to_string(daq::DataPacketPtr packet) case SampleType::Int64: data = std::to_string(*(static_cast(packet.getData()))); break; + case SampleType::String: case SampleType::Binary: data = '\"' + std::string(static_cast(packet.getData()), packet.getDataSize()) + '\"'; break; @@ -123,24 +124,24 @@ int main(int argc, char* argv[]) // Create OpenDAQ instance and add MQTT broker FB const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); - const std::string rootFbName = "RootMqttFb"; - auto rootFbConfig = instance.getAvailableFunctionBlockTypes().get(rootFbName).createDefaultConfig(); - rootFbConfig.setPropertyValue("MqttBrokerAddress", appConfig.brokerAddress); - auto brokerFB = instance.addFunctionBlock(rootFbName, rootFbConfig); + const std::string clientFbName = "MQTTClientFB"; + auto clientFbConfig = instance.getAvailableFunctionBlockTypes().get(clientFbName).createDefaultConfig(); + clientFbConfig.setPropertyValue("BrokerAddress", appConfig.brokerAddress); + auto brokerFB = instance.addFunctionBlock(clientFbName, clientFbConfig); auto availableFbs = brokerFB.getAvailableFunctionBlockTypes(); - const std::string jsonFbName = "JsonSubscriberMqttFb"; - std::cout << "Try to add the " << jsonFbName << std::endl; + const std::string subFbName = "MQTTSubscriberFB"; + std::cout << "Try to add the " << subFbName << std::endl; - auto config = availableFbs.get(jsonFbName).createDefaultConfig(); - config.setPropertyValue("JsonConfigFile", appConfig.configFilePath); + auto config = availableFbs.get(subFbName).createDefaultConfig(); + config.setPropertyValue("JSONConfigFile", appConfig.configFilePath); - // Add the JSON function block to the broker FB - daq::FunctionBlockPtr jsonFb = brokerFB.addFunctionBlock(jsonFbName, config); + // Add the subscriber function block to the broker FB + daq::FunctionBlockPtr subFb = brokerFB.addFunctionBlock(subFbName, config); // Create packet readers for all signals auto signals = List(); - const auto fbs = jsonFb.getFunctionBlocks(); + const auto fbs = subFb.getFunctionBlocks(); for (const auto& fb : fbs) { const auto sig = fb.getSignals(); diff --git a/examples/raw-mqtt-sub/src/raw-mqtt-sub.cpp b/examples/raw-mqtt-sub/src/raw-mqtt-sub.cpp index cb6946b..bc65123 100644 --- a/examples/raw-mqtt-sub/src/raw-mqtt-sub.cpp +++ b/examples/raw-mqtt-sub/src/raw-mqtt-sub.cpp @@ -55,24 +55,26 @@ int main(int argc, char* argv[]) // Create OpenDAQ instance and add MQTT broker FB const InstancePtr instance = InstanceBuilder().addModulePath(MODULE_PATH).build(); - const std::string rootFbName = "RootMqttFb"; - auto rootFbConfig = instance.getAvailableFunctionBlockTypes().get(rootFbName).createDefaultConfig(); - rootFbConfig.setPropertyValue("MqttBrokerAddress", appConfig.brokerAddress); - auto brokerFB = instance.addFunctionBlock(rootFbName, rootFbConfig); + const std::string clientFbName = "MQTTClientFB"; + auto clientFbConfig = instance.getAvailableFunctionBlockTypes().get(clientFbName).createDefaultConfig(); + clientFbConfig.setPropertyValue("BrokerAddress", appConfig.brokerAddress); + auto brokerFB = instance.addFunctionBlock(clientFbName, clientFbConfig); auto availableFbs = brokerFB.getAvailableFunctionBlockTypes(); - const std::string fbName = "RawSubscriberMqttFb"; + const std::string fbName = "MQTTSubscriberFB"; std::cout << "Try to add the " << fbName << std::endl; - // Create RAW function block configuration + // Create subscriber function block configuration auto config = availableFbs.get(fbName).createDefaultConfig(); config.setPropertyValue("Topic", appConfig.topic); + config.setPropertyValue("EnablePreviewSignal", True); + config.setPropertyValue("MessageIsString", True); - // Add the RAW function block to the broker FB - daq::FunctionBlockPtr rawFb = brokerFB.addFunctionBlock(fbName, config); + // Add the subscriber function block to the broker FB + daq::FunctionBlockPtr subFb = brokerFB.addFunctionBlock(fbName, config); // Create packet readers for a signal - const auto signal = rawFb.getSignals()[0]; + const auto signal = subFb.getSignals()[0]; PacketReaderPtr reader = daq::PacketReader(signal); // Start a thread to read packets from the reader 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 e6baf9a..b54579b 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 @@ -6,16 +6,16 @@ using namespace daq; enum class Mode { - ATOMIC_SIGNAL_ATOMIC_SAMPLE = 0, - ATOMIC_SIGNAL_SAMPLE_ARRAY, - SIGNAL_ARRAY_ATOMIC_SAMPLE, - GROUP_SIGNAL_ATOMIC_SAMPLE_SHARED_TS, + TopicPerSignal = 0, + SingleTopic, _COUNT }; struct ConfigStruct { std::string brokerAddress; Mode mode; + bool useArray = false; + size_t arraySize = 0; bool exit = true; int error = 0; }; @@ -27,12 +27,11 @@ ConfigStruct StartUp(int argc, char* argv[]) args.addArg("--help", "Show help message"); args.addArg("--address", "MQTT broker address", true); args.addArg("--mode", "publisher FB mode", true); + args.addArg("--array", "pablish samples as arrays with specified size", true); args.setUsageHelp(APP_NAME " [options]\n" "Available modes:\n" - " 0 - ATOMIC_SIGNAL_ATOMIC_SAMPLE\n" - " 1 - ATOMIC_SIGNAL_SAMPLE_ARRAY\n" - " 2 - SIGNAL_ARRAY_ATOMIC_SAMPLE\n" - " 3 - GROUP_SIGNAL_ATOMIC_SAMPLE_SHARED_TS"); + " 0 - Topic per signal\n" + " 1 - Single topic\n"); args.parse(argc, argv); if (args.hasArg("--help") || args.hasUnknownArgs()) @@ -54,6 +53,19 @@ ConfigStruct StartUp(int argc, char* argv[]) return config; } config.mode = static_cast(mode); + if (args.hasArg("--array")) + { + config.useArray = true; + config.arraySize = std::stoi(args.getArgValue("--array", "0")); + if (config.arraySize == 0) + { + std::cout << "Invalid array size value. It must be greater than 0." << std::endl; + args.printHelp(); + config.error = -1; + config.exit = true; + return config; + } + } return config; } @@ -73,59 +85,38 @@ int main(int argc, char* argv[]) // Configure channels const auto channels = refDevice.getChannelsRecursive(); - channels[0].setPropertyValue("UseGlobalSampleRate", appConfig.mode == Mode::GROUP_SIGNAL_ATOMIC_SAMPLE_SHARED_TS); + channels[0].setPropertyValue("UseGlobalSampleRate", appConfig.mode == Mode::SingleTopic); channels[0].setPropertyValue("SampleRate", 10); channels[0].setPropertyValue("Frequency", 1); channels[0].setPropertyValue("Waveform", 1); - channels[1].setPropertyValue("UseGlobalSampleRate", appConfig.mode == Mode::GROUP_SIGNAL_ATOMIC_SAMPLE_SHARED_TS); + channels[1].setPropertyValue("UseGlobalSampleRate", appConfig.mode == Mode::SingleTopic); channels[1].setPropertyValue("SampleRate", 20); channels[1].setPropertyValue("Frequency", 1); channels[1].setPropertyValue("Waveform", 3); - channels[2].setPropertyValue("UseGlobalSampleRate", appConfig.mode == Mode::GROUP_SIGNAL_ATOMIC_SAMPLE_SHARED_TS); + channels[2].setPropertyValue("UseGlobalSampleRate", appConfig.mode == Mode::SingleTopic); channels[2].setPropertyValue("SampleRate", 50); channels[2].setPropertyValue("Frequency", 4); - channels[3].setPropertyValue("UseGlobalSampleRate", appConfig.mode == Mode::GROUP_SIGNAL_ATOMIC_SAMPLE_SHARED_TS); + channels[3].setPropertyValue("UseGlobalSampleRate", appConfig.mode == Mode::SingleTopic); channels[3].setPropertyValue("SampleRate", 100); channels[3].setPropertyValue("Frequency", 20); // Create and configure MQTT server - const std::string rootFbName = "RootMqttFb"; - auto rootFbConfig = instance.getAvailableFunctionBlockTypes().get(rootFbName).createDefaultConfig(); - rootFbConfig.setPropertyValue("MqttBrokerAddress", appConfig.brokerAddress); - auto brokerFB = instance.addFunctionBlock(rootFbName, rootFbConfig); + const std::string clientFbName = "MQTTClientFB"; + auto clientFbConfig = instance.getAvailableFunctionBlockTypes().get(clientFbName).createDefaultConfig(); + clientFbConfig.setPropertyValue("BrokerAddress", appConfig.brokerAddress); + auto brokerFB = instance.addFunctionBlock(clientFbName, clientFbConfig); auto availableFbs = brokerFB.getAvailableFunctionBlockTypes(); - const std::string fbName = "PublisherMqttFb"; + const std::string fbName = "MQTTJSONPublisherFB"; std::cout << "Try to add the " << fbName << std::endl; auto config = availableFbs.get(fbName).createDefaultConfig(); - config.setPropertyValue("MqttQoS", 1); - config.setPropertyValue("ReaderPeriod", 20); - config.setPropertyValue("UseSignalNames", True); - switch (appConfig.mode) { - case Mode::ATOMIC_SIGNAL_ATOMIC_SAMPLE: - config.setPropertyValue("SharedTimestamp", False); - config.setPropertyValue("TopicMode", 0); - config.setPropertyValue("GroupValues", False); - break; - case Mode::ATOMIC_SIGNAL_SAMPLE_ARRAY: - config.setPropertyValue("SharedTimestamp", False); - config.setPropertyValue("TopicMode", 0); - config.setPropertyValue("GroupValues", True); - config.setPropertyValue("GroupValuesPackSize", 3); - break; - case Mode::SIGNAL_ARRAY_ATOMIC_SAMPLE: - config.setPropertyValue("SharedTimestamp", False); - config.setPropertyValue("TopicMode", 1); - config.setPropertyValue("GroupValues", False); - break; - case Mode::GROUP_SIGNAL_ATOMIC_SAMPLE_SHARED_TS: - config.setPropertyValue("SharedTimestamp", True); - config.setPropertyValue("TopicMode", 1); - config.setPropertyValue("GroupValues", False); - break; - default: - break; - } + config.setPropertyValue("QoS", 1); + config.setPropertyValue("ReaderWaitPeriod", 20); + config.setPropertyValue("SignalValueJSONKey", 2); + config.setPropertyValue("TopicMode", (appConfig.mode == Mode::TopicPerSignal) ? 0 : 1); + config.setPropertyValue("GroupValues", (appConfig.useArray) ? True : False); + config.setPropertyValue("SamplesPerMessage", appConfig.arraySize); + config.setPropertyValue("Topic", "opendaq/test/values"); // Add the publisher function block to the broker device diff --git a/external/opendaq/CMakeLists.txt b/external/opendaq/CMakeLists.txt index cddbe03..08f4999 100644 --- a/external/opendaq/CMakeLists.txt +++ b/external/opendaq/CMakeLists.txt @@ -3,7 +3,7 @@ set(OPENDAQ_ENABLE_TESTS false) FetchContent_Declare( opendaq GIT_REPOSITORY https://github.com/openDAQ/openDAQ.git - GIT_TAG origin/main + GIT_TAG 41396d19a1567ab6aecacfe7e381ea616dfdad6c GIT_PROGRESS ON EXCLUDE_FROM_ALL SYSTEM diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt new file mode 100644 index 0000000..f68db72 --- /dev/null +++ b/modules/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.10) +list(APPEND CMAKE_MESSAGE_CONTEXT modules) + +add_subdirectory(mqtt_streaming_module) \ No newline at end of file diff --git a/mqtt_streaming_module/CMakeLists.txt b/modules/mqtt_streaming_module/CMakeLists.txt similarity index 89% rename from mqtt_streaming_module/CMakeLists.txt rename to modules/mqtt_streaming_module/CMakeLists.txt index 8fa8f18..5ec96cd 100644 --- a/mqtt_streaming_module/CMakeLists.txt +++ b/modules/mqtt_streaming_module/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.10) set_cmake_folder_context(TARGET_FOLDER_NAME) -set(MQTT_MODULE_VERSION ${OPENDAQ_PACKAGE_VERSION}) set(MQTT_MODULE_PRJ_NAME "OpenDaqMqttModule") message(STATUS "${MQTT_MODULE_PRJ_NAME} version: ${MQTT_MODULE_VERSION}") diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_atomic_sample_handler.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_atomic_sample_handler.h similarity index 80% rename from mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_atomic_sample_handler.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_atomic_sample_handler.h index 0359996..96faa42 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_atomic_sample_handler.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_atomic_sample_handler.h @@ -16,6 +16,7 @@ #pragma once +#include #include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE @@ -23,7 +24,7 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE class AtomicSignalAtomicSampleHandler : public HandlerBase { public: - explicit AtomicSignalAtomicSampleHandler(bool useSignalNames); + explicit AtomicSignalAtomicSampleHandler(WeakRefPtr parentFb, SignalValueJSONKey signalNamesMode); MqttData processSignalContexts(std::vector& signalContexts) override; ProcedureStatus validateSignalContexts(const std::vector& signalContexts) const override; @@ -31,15 +32,14 @@ class AtomicSignalAtomicSampleHandler : public HandlerBase { return ProcedureStatus{true, {}}; }; - + ListPtr getTopics(const std::vector& signalContexts) override; + std::string getSchema() override; protected: - bool useSignalNames; - virtual MqttData processSignalContext(SignalContext& signalContext); void processSignalDescriptorChanged(SignalContext& signalCtx, const DataDescriptorPtr& valueSigDesc, const DataDescriptorPtr& domainSigDesc); - MqttDataSample processDataPacket(SignalContext& signalContext, const DataPacketPtr& dataPacket); - std::string toString(const std::string valueFieldName, daq::DataPacketPtr packet); + MqttDataSample processDataPacket(SignalContext& signalContext, const DataPacketPtr& dataPacket, size_t offset); + std::string toString(const std::string valueFieldName, daq::DataPacketPtr packet, size_t offset); std::string buildTopicName(const SignalContext& signalContext); }; diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_sample_arr_handler.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_sample_arr_handler.h similarity index 52% rename from mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_sample_arr_handler.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_sample_arr_handler.h index 4210209..d017957 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_sample_arr_handler.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/atomic_signal_sample_arr_handler.h @@ -16,21 +16,42 @@ #pragma once +#include #include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE class AtomicSignalSampleArrayHandler : public AtomicSignalAtomicSampleHandler { public: - explicit AtomicSignalSampleArrayHandler(bool useSignalNames, size_t packSize); + + struct SignalBuffer + { + std::list data; + size_t dataSize = 0; + size_t offset = 0; + void clear() + { + data.clear(); + dataSize = 0; + offset = 0; + } + }; + explicit AtomicSignalSampleArrayHandler(WeakRefPtr parentFb, SignalValueJSONKey signalNamesMode, size_t packSize); + ProcedureStatus signalListChanged(std::vector& signalContexts) override; + std::string getSchema() override; protected: size_t packSize; + std::unordered_map signalBuffers; MqttData processSignalContext(SignalContext& signalContext) override; - MqttDataSample processDataPackets(SignalContext& signalContext, const std::vector& dataPacket); - std::string toString(const std::string valueFieldName, const std::vector& dataPackets); + MqttDataSample processDataPackets(SignalContext& signalContext); + std::string toString(const std::string valueFieldName, SignalContext& signalContext); + std::pair getSample(SignalContext& signalContext); + void + processSignalDescriptorChanged(SignalContext& signalCtx, const DataDescriptorPtr& valueSigDesc, const DataDescriptorPtr& domainSigDesc); }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/common.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/common.h similarity index 71% rename from mqtt_streaming_module/include/mqtt_streaming_module/common.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/common.h index 9be4ab5..ce81e9a 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/common.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/common.h @@ -20,12 +20,16 @@ #define BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE BEGIN_NAMESPACE_OPENDAQ_MODULE(mqtt_streaming_module) #define END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE END_NAMESPACE_OPENDAQ_MODULE -#if defined(_WIN32) - #ifdef OPENDAQ_MODULE_DLL_IMPORT - #define DAQ_MQTT_STREAM_MODULE_API __declspec(dllexport) +#if !defined(OPENDAQ_MQTT_ENABLE_TESTS) + #define DAQ_MQTT_STREAM_MODULE_API +#else + #if defined(_WIN32) + #if defined(OPENDAQ_MODULE_DLL_IMPORT) + #define DAQ_MQTT_STREAM_MODULE_API __declspec(dllimport) + #else + #define DAQ_MQTT_STREAM_MODULE_API __declspec(dllexport) + #endif #else - #define DAQ_MQTT_STREAM_MODULE_API __declspec(dllimport) + #define DAQ_MQTT_STREAM_MODULE_API __attribute__((visibility("default"))) #endif -#else - #define DAQ_MQTT_STREAM_MODULE_API #endif diff --git a/modules/mqtt_streaming_module/include/mqtt_streaming_module/constants.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/constants.h new file mode 100644 index 0000000..c241b8e --- /dev/null +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/constants.h @@ -0,0 +1,77 @@ +#pragma once + +#include "common.h" + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE + +static const char* MODULE_NAME = "OpenDAQMQTTModule"; +static const char* MODULE_ID = "OpenDAQMQTTModule"; +static const char* SHORT_MODULE_NAME = "MQTTModule"; + +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_TIMEOUT = 3000; // ms + +static constexpr uint32_t DEFAULT_PUB_READ_PERIOD = 20; // ms +static constexpr uint32_t DEFAULT_PUB_QOS = 1; +static constexpr uint32_t DEFAULT_SUB_QOS = 1; +static constexpr uint32_t DEFAULT_PUB_PACK_SIZE = 1; + +static constexpr const char* DEFAULT_VALUE_SIGNAL_LOCAL_ID = "MQTTValueSignal"; +static constexpr const char* DEFAULT_TS_SIGNAL_LOCAL_ID = "MQTTTimestampSignal"; + + +static constexpr const char* PROPERTY_NAME_CLIENT_BROKER_ADDRESS = "BrokerAddress"; +static constexpr const char* PROPERTY_NAME_CLIENT_BROKER_PORT = "BrokerPort"; +static constexpr const char* PROPERTY_NAME_CLIENT_USERNAME = "Username"; +static constexpr const char* PROPERTY_NAME_CLIENT_PASSWORD = "Password"; +static constexpr const char* PROPERTY_NAME_CLIENT_CONNECT_TIMEOUT = "ConnectionTimeout"; + +static constexpr const char* PROPERTY_NAME_SUB_JSON_CONFIG = "JSONConfig"; +static constexpr const char* PROPERTY_NAME_SUB_JSON_CONFIG_FILE = "JSONConfigFile"; +static constexpr const char* PROPERTY_NAME_SUB_QOS = "QoS"; +static constexpr const char* PROPERTY_NAME_SUB_TOPIC = "Topic"; +static constexpr const char* PROPERTY_NAME_SUB_PREVIEW_SIGNAL = "EnablePreviewSignal"; +static constexpr const char* PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING = "MessageIsString"; + +static constexpr const char* PROPERTY_NAME_DEC_VALUE_NAME = "ValueKey"; +static constexpr const char* PROPERTY_NAME_DEC_TS_NAME = "DomainKey"; +static constexpr const char* PROPERTY_NAME_DEC_UNIT = "Unit"; + +static constexpr const char* PROPERTY_NAME_PUB_TOPIC_MODE = "TopicMode"; +static constexpr const char* PROPERTY_NAME_PUB_TOPIC_NAME = "Topic"; +static constexpr const char* PROPERTY_NAME_PUB_GROUP_VALUES = "GroupValues"; +static constexpr const char* PROPERTY_NAME_PUB_VALUE_FIELD_NAME = "SignalValueJSONKey"; +static constexpr const char* PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE = "SamplesPerMessage"; +static constexpr const char* PROPERTY_NAME_PUB_QOS = "QoS"; +static constexpr const char* PROPERTY_NAME_PUB_READ_PERIOD = "ReaderWaitPeriod"; +static constexpr const char* PROPERTY_NAME_PUB_TOPICS = "Topics"; +static constexpr const char* PROPERTY_NAME_PUB_SCHEMA = "Schema"; +static constexpr const char* PROPERTY_NAME_PUB_PREVIEW_SIGNAL = "EnablePreviewSignal"; +static constexpr const char* PUB_PREVIEW_SIGNAL_NAME = "PreviewSignal"; + +static constexpr const char* SUB_FB_NAME = "MQTTSubscriberFB"; +static constexpr const char* PUB_FB_NAME = "MQTTJSONPublisherFB"; +static constexpr const char* CLIENT_FB_NAME = "MQTTClientFB"; +static constexpr const char* JSON_DECODER_FB_NAME = "MQTTJSONDecoderFB"; + +static const char* MQTT_LOCAL_CLIENT_FB_ID_PREFIX = "MQTTClientFB"; +static const char* MQTT_LOCAL_PUB_FB_ID_PREFIX = "MQTTJSONPublisherFB"; +static const char* MQTT_LOCAL_SUB_FB_ID_PREFIX = "MQTTSubscriberFB"; +static const char* MQTT_LOCAL_JSON_DECODER_FB_ID_PREFIX = "MQTTJSONDecoderFB"; + + +static const char* MQTT_CLIENT_FB_CON_STATUS_TYPE = "DAQ_MQTT_ConnectionStatusType"; +static const char* MQTT_PUB_FB_SIG_STATUS_TYPE = "DAQ_MQTT_SignalStatusType"; +static const char* MQTT_PUB_FB_PUB_STATUS_TYPE = "DAQ_MQTT_PublishingStatusType"; +static const char* MQTT_PUB_FB_SET_STATUS_TYPE = "DAQ_MQTT_SettingStatusType"; + + +static const char* MQTT_CLIENT_FB_CON_STATUS_NAME = "ConnectionStatus"; +static const char* MQTT_PUB_FB_SIG_STATUS_NAME = "SignalStatus"; +static const char* MQTT_PUB_FB_PUB_STATUS_NAME = "PublishingStatus"; +static const char* MQTT_PUB_FB_SET_STATUS_NAME = "SettingStatus"; + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/modules/mqtt_streaming_module/include/mqtt_streaming_module/group_signal_shared_ts_arr_handler.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/group_signal_shared_ts_arr_handler.h new file mode 100644 index 0000000..1624c2f --- /dev/null +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/group_signal_shared_ts_arr_handler.h @@ -0,0 +1,189 @@ +/* + * 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 "group_signal_shared_ts_handler.h" + +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE + +class StringDataBuilder +{ +public: + StringDataBuilder(size_t packSize) + : packSize(packSize) + { + reset(); + } + + void append(const SampleType sampleType, void* data, size_t size) + { + switch (sampleType) + { + case SampleType::Float64: + toString::Type>(data, size); + break; + case SampleType::Float32: + toString::Type>(data, size); + break; + case SampleType::UInt64: + toString::Type>(data, size); + break; + case SampleType::Int64: + toString::Type>(data, size); + break; + case SampleType::UInt32: + toString::Type>(data, size); + break; + case SampleType::Int32: + toString::Type>(data, size); + break; + case SampleType::UInt16: + toString::Type>(data, size); + break; + case SampleType::Int16: + toString::Type>(data, size); + break; + case SampleType::UInt8: + toString::Type>(data, size); + break; + case SampleType::Int8: + toString::Type>(data, size); + break; + default: + break; + } + } + + std::string getPack(const std::string& valueFieldName) + { + std::string result; + if (!values.empty()) + { + result = fmt::format("\"{}\" : {}", valueFieldName, values.front()); + values.pop_front(); + } + + return result; + } + + bool empty() const + { + return values.empty(); + } + + void clear() + { + reset(); + values.clear(); + offset = 0; + } + +protected: + const size_t packSize; + std::list values; + size_t offset = 0; + std::ostringstream oss; + + template + void toString(void* data, size_t size) + { + for (size_t dataOffset = 0; dataOffset < size; ++dataOffset) + { + prefix(); + oss << std::to_string(*(static_cast(data) + dataOffset)); + ++offset; + postfix(); + } + } + + void prefix() + { + if (offset > 0) + oss << ", "; + } + + void postfix() + { + if (offset == packSize) + { + offset = 0; + oss << "]"; + values.push_back(std::move(oss).str()); + reset(); + } + } + + void reset() + { + oss.clear(); + oss.str(""); + oss << "["; + } +}; + +class StringTsBuilder : public StringDataBuilder +{ +public: + StringTsBuilder(size_t packSize) + : StringDataBuilder(packSize) + { + } + + void append(const TimestampTickStruct tsStruct, size_t size) + { + for (size_t dataOffset = 0; dataOffset < size; ++dataOffset) + { + prefix(); + oss << std::to_string(tsStruct.tsToTicks(dataOffset)); + ++offset; + postfix(); + } + } + + std::string getPack() + { + return StringDataBuilder::getPack("timestamp"); + } +}; + +class GroupSignalSharedTsArrHandler : public GroupSignalSharedTsHandler +{ +public: + explicit GroupSignalSharedTsArrHandler(WeakRefPtr parentFb, + SignalValueJSONKey signalNamesMode, + std::string topic, + size_t packSize); + ~GroupSignalSharedTsArrHandler() = default; + + MqttData processSignalContexts(std::vector& signalContexts) override; + ProcedureStatus signalListChanged(std::vector& signalContexts) override; + std::string getSchema() override; + +protected: + const size_t packSize; + std::vector dataBuilders; + StringTsBuilder tsBuilder; + + void initBuilders(const size_t size); +}; + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/group_signal_shared_ts_handler.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/group_signal_shared_ts_handler.h similarity index 70% rename from mqtt_streaming_module/include/mqtt_streaming_module/group_signal_shared_ts_handler.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/group_signal_shared_ts_handler.h index a182110..f335b6f 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/group_signal_shared_ts_handler.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/group_signal_shared_ts_handler.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include @@ -28,23 +29,34 @@ struct TimestampTickStruct uint64_t ratioNum; uint64_t ratioDen; uint64_t multiplier; + + uint64_t tsToTicks(size_t offset) const + { + // const uint64_t epochTime = (firstTick + delta * offset) * ratioNum * US_IN_S / ratioDen; // us + uint64_t res = ((firstTick + delta * offset) * ratioNum * multiplier) / ratioDen; + return res; + } }; class GroupSignalSharedTsHandler : public HandlerBase { public: - explicit GroupSignalSharedTsHandler(bool useSignalNames, std::string topic); + explicit GroupSignalSharedTsHandler(WeakRefPtr parentFb, SignalValueJSONKey signalNamesMode, std::string topic); + ~GroupSignalSharedTsHandler(); MqttData processSignalContexts(std::vector& signalContexts) override; ProcedureStatus validateSignalContexts(const std::vector& signalContexts) const override; ProcedureStatus signalListChanged(std::vector& signalContexts) override; + ListPtr getTopics(const std::vector& signalContexts) override; + std::string getSchema() override; protected: - bool useSignalNames; const size_t buffersSize; const std::string topic; std::vector dataBuffers; + bool firstDescriptorChange; daq::MultiReaderPtr reader; + std::mutex sync; template std::string toString(const std::string& valueFieldName, void* data, SizeT offset); @@ -52,9 +64,12 @@ class GroupSignalSharedTsHandler : public HandlerBase std::string tsToString(TimestampTickStruct tsStruct, SizeT offset); std::string buildTopicName(); void createReader(const std::vector& signalContexts); + void createReaderInternal(const std::vector& signalContexts); void allocateBuffers(const std::vector& signalContexts); + void deallocateBuffers(); static std::string messageFromFields(const std::vector& fields); static TimestampTickStruct domainToTs(const MultiReaderStatusPtr status); + bool processEvents(const daq::MultiReaderStatusPtr& status); // true if descriptor changed or invalid reader }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/handler_base.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/handler_base.h similarity index 58% rename from mqtt_streaming_module/include/mqtt_streaming_module/handler_base.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/handler_base.h index 41666e4..c68fe5a 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/handler_base.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/handler_base.h @@ -16,6 +16,7 @@ #pragma once +#include #include "mqtt_streaming_module/common.h" #include #include @@ -26,12 +27,39 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE class HandlerBase { public: + HandlerBase(WeakRefPtr parentFb, SignalValueJSONKey signalNamesMode) + : parentFb(parentFb), + signalNamesMode(signalNamesMode) + { + } virtual ~HandlerBase() = default; virtual MqttData processSignalContexts(std::vector& signalContexts) = 0; - virtual ProcedureStatus validateSignalContexts(const std::vector& signalContexts) const = 0; + virtual ProcedureStatus validateSignalContexts(const std::vector& signalContexts) const + { + ProcedureStatus status{true, {}}; + std::unordered_set globalIdSet; + for (const auto& sigCtx : signalContexts) + { + auto signal = sigCtx.inputPort.getSignal(); + if (!signal.assigned()) + continue; + std::string globalId = signal.getGlobalId(); + if (globalIdSet.find(globalId) != globalIdSet.end()) + { + status.addError(fmt::format("Connected signals have non-unique GlobalIDs (\"{}\").", globalId)); + } + globalIdSet.insert(std::move(globalId)); + } + return status; + } virtual ProcedureStatus signalListChanged(std::vector& signalContexts) = 0; + virtual ListPtr getTopics(const std::vector& signalContexts) = 0; + virtual std::string getSchema() = 0; protected: + WeakRefPtr parentFb; + SignalValueJSONKey signalNamesMode; + static std::pair calculateRatio(const DataDescriptorPtr descriptor) { const auto tickResolution = descriptor.getTickResolution().simplify(); @@ -44,7 +72,7 @@ class HandlerBase return std::pair{num, den}; } - static uint64_t convertToEpoch(const DataPacketPtr domainPacket) + static uint64_t convertToEpoch(const DataPacketPtr domainPacket, size_t offset) { constexpr uint64_t US_IN_S = 1'000'000; // amount microseconds in a second const auto tickResolution = domainPacket.getDataDescriptor().getTickResolution().simplify(); @@ -56,14 +84,14 @@ class HandlerBase uint64_t ts = 0; if (domainPacket.getDataDescriptor().getSampleType() == SampleType::UInt64) - ts = *(static_cast(domainPacket.getData())); + ts = *(static_cast(domainPacket.getData()) + offset); else if (domainPacket.getDataDescriptor().getSampleType() == SampleType::Int64) - ts = *(static_cast(domainPacket.getData())); + ts = *(static_cast(domainPacket.getData()) + offset); ts = ts * num / den; // us return ts; } - static std::string toString(const DataPacketPtr& dataPacket) + static std::string toString(const DataPacketPtr& dataPacket, size_t offset) { auto sampleType = dataPacket.getDataDescriptor().getSampleType(); std::string data("unsupported"); @@ -71,34 +99,34 @@ class HandlerBase switch (sampleType) { case SampleType::Float64: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::Float32: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::UInt64: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::Int64: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::UInt32: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::Int32: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::UInt16: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::Int16: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::UInt8: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::Int8: - data = std::to_string(*(static_cast::Type*>(dataPacket.getData()))); + data = std::to_string(*(static_cast::Type*>(dataPacket.getData()) + offset)); break; case SampleType::String: case SampleType::Binary: @@ -109,6 +137,42 @@ class HandlerBase } return data; } + + static std::string buildValueFieldName(SignalValueJSONKey signalNamesMode, daq::SignalPtr signal) + { + std::string valueFieldName; + if (signalNamesMode == SignalValueJSONKey::LocalID) + { + valueFieldName = signal.getLocalId().toStdString(); + } + else if (signalNamesMode == SignalValueJSONKey::Name) + { + valueFieldName = signal.getName().toStdString(); + } + else + { + valueFieldName = signal.getGlobalId().toStdString(); + } + return valueFieldName; + } + + static std::string buildValueFieldNameForSchema(SignalValueJSONKey signalNamesMode, std::string postfix = "") + { + std::string valueFieldName; + if (signalNamesMode == SignalValueJSONKey::LocalID) + { + valueFieldName = fmt::format("", postfix); + } + else if (signalNamesMode == SignalValueJSONKey::Name) + { + valueFieldName = fmt::format("", postfix); + } + else + { + valueFieldName = fmt::format("", postfix); + } + return valueFieldName; + } }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/handler_factory.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/handler_factory.h similarity index 60% rename from mqtt_streaming_module/include/mqtt_streaming_module/handler_factory.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/handler_factory.h index 8a3b276..af790b7 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/handler_factory.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/handler_factory.h @@ -16,6 +16,7 @@ #pragma once +#include "mqtt_streaming_module/group_signal_shared_ts_arr_handler.h" #include "mqtt_streaming_module/types.h" #include #include @@ -27,27 +28,26 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE class HandlerFactory { public: - static std::unique_ptr create(const PublisherFbConfig config, const std::string& publisherFbGlobalId) + static std::unique_ptr + create(WeakRefPtr parentFb, const PublisherFbConfig config, const std::string& publisherFbGlobalId) { - if (config.sharedTs) - { - return std::make_unique(config.useSignalNames, - config.topicName.empty() ? publisherFbGlobalId : config.topicName); - } - else if (config.topicMode == TopicMode::Single) + if (config.topicMode == TopicMode::Single) { + const auto topic = config.topicName.empty() ? publisherFbGlobalId : config.topicName; if (config.groupValues) - return std::make_unique(config.useSignalNames, config.groupValuesPackSize); + return std::make_unique(parentFb, config.valueFieldName, topic, config.groupValuesPackSize); else - return std::make_unique(config.useSignalNames); + return std::make_unique(parentFb, config.valueFieldName, topic); } - else if (config.topicMode == TopicMode::Multi) + else if (config.topicMode == TopicMode::PerSignal) { - return std::make_unique(config.useSignalNames, - config.topicName.empty() ? publisherFbGlobalId : config.topicName); + if (config.groupValues) + return std::make_unique(parentFb, config.valueFieldName, config.groupValuesPackSize); + else + return std::make_unique(parentFb, config.valueFieldName); } - return std::make_unique(config.useSignalNames); + return std::make_unique(parentFb, config.valueFieldName); } }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/helper.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/helper.h similarity index 100% rename from mqtt_streaming_module/include/mqtt_streaming_module/helper.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/helper.h diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/module_dll.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/module_dll.h similarity index 100% rename from mqtt_streaming_module/include/mqtt_streaming_module/module_dll.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/module_dll.h diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_root_fb_impl.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_client_fb_impl.h similarity index 83% rename from mqtt_streaming_module/include/mqtt_streaming_module/mqtt_root_fb_impl.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_client_fb_impl.h index cc4940a..2cc8e52 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_root_fb_impl.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_client_fb_impl.h @@ -21,22 +21,15 @@ #include #include #include -#include +#include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE -class MqttRootFbImpl : public FunctionBlock +class MqttClientFbImpl : public FunctionBlock { - enum class ConnectionStatus : EnumType - { - Connected = 0, - Reconnecting, - Disconnected - }; - public: - explicit MqttRootFbImpl(const ContextPtr& ctx, + explicit MqttClientFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, const PropertyObjectPtr& config); @@ -45,7 +38,6 @@ class MqttRootFbImpl : public FunctionBlock protected: static std::atomic localIndex; static std::string generateLocalId(); - static std::vector> connectionStatusMap; void removed() override; @@ -61,7 +53,7 @@ class MqttRootFbImpl : public FunctionBlock DictObjectPtr nestedFbTypes; - StatusHelper connectionStatus; + StatusAdaptor connectionStatus; std::shared_ptr subscriber; Mqtt::Utils::Settings::MqttConnectionSettings connectionSettings; @@ -71,6 +63,7 @@ class MqttRootFbImpl : public FunctionBlock std::future connectedFuture; std::atomic connectedDone{false}; std::unordered_map deviceMap; // device name -> signal list JSON + std::mutex componentStatusSync; }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_json_decoder_fb_impl.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_json_decoder_fb_impl.h similarity index 83% rename from mqtt_streaming_module/include/mqtt_streaming_module/mqtt_json_decoder_fb_impl.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_json_decoder_fb_impl.h index acefcf2..b3f684d 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_json_decoder_fb_impl.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_json_decoder_fb_impl.h @@ -16,7 +16,6 @@ #pragma once #include "MqttDataWrapper.h" -#include "mqtt_streaming_module/status_helper.h" #include #include @@ -47,25 +46,20 @@ class MqttJsonDecoderFbImpl final : public FunctionBlock std::string valueFieldName; std::string tsFieldName; std::string unitSymbol; - std::string signalName; - }; - struct ConfigStatus { - bool configValid; - std::string configMsg; - bool waitingData; - bool parsingSucceeded; - std::string parsingMsg; }; + static std::atomic localIndex; - static std::vector> parsingStatusMap; mqtt::MqttDataWrapper jsonDataWorker; SignalConfigPtr outputSignal; SignalConfigPtr outputDomainSignal; + std::atomic waitingData; + std::atomic configValid; + std::string configMsg; + std::atomic parsingSucceeded; + std::string parsingMsg; FbConfig config; - StatusHelper parsingStatus; - ConfigStatus configStatus; static std::string generateLocalId(); diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_publisher_fb_impl.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_publisher_fb_impl.h similarity index 82% rename from mqtt_streaming_module/include/mqtt_streaming_module/mqtt_publisher_fb_impl.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_publisher_fb_impl.h index fd52186..15313d4 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_publisher_fb_impl.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_publisher_fb_impl.h @@ -17,7 +17,6 @@ #pragma once #include "MqttAsyncClient.h" #include "MqttDataWrapper.h" -#include "mqtt_streaming_helper/timer.h" #include "mqtt_streaming_module/handler_base.h" #include "mqtt_streaming_module/status_helper.h" #include @@ -78,36 +77,47 @@ class MqttPublisherFbImpl final : public FunctionBlock mqtt::MqttDataWrapper jsonDataWorker; PublisherFbConfig config; std::vector signalContexts; - std::atomic inputPortCount; + std::unordered_map> signalMap; std::thread readerThread; std::atomic running; std::atomic hasSignalError; + std::atomic signalDescriptorChanged; + std::atomic signalAttributeChanged; std::atomic hasSettingError; + std::atomic hasEmptyTopic; std::vector signalErrors; std::vector settingErrors; std::unique_ptr handler; - std::mutex statusMutex; + std::mutex processingMutex; StatusHelper signalStatus; StatusHelper publishingStatus; StatusHelper settingStatus; - std::atomic skippedMsgCnt; - std::atomic publishedMsgCnt; + std::atomic hasSkippedMsg; std::string lastSkippedReason; - helper::utils::Timer publishingStatusTimer; + SignalConfigPtr commonPreviewSignal; static std::string generateLocalId(); - void updatePublishingStatus(bool force); + void coreEventCallback(ComponentPtr& sender, CoreEventArgsPtr& eventArgs); + void updateComponentStatus(); + void updatePublishingStatus(); + std::string buildPublishingStatusMessage(); void initProperties(const PropertyObjectPtr& config); void readProperties(); void propertyChanged(); - void updateInputPorts(); + void updatePortsAndSignals(bool reassignPorts); + void clearPorts(); + void updateCoreEventCallbacks(); + void clearCoreEventCallbacks(const std::unordered_map>& signalMap); void updateStatuses(); void validateInputPorts(); + void updateTopics(); + void updateSchema(); template retT readProperty(const std::string& propertyName, const retT defaultValue); void runReaderThread(); void readerLoop(); void sendMessages(const MqttData& data); + void removed() override; }; END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_streaming_module_impl.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_streaming_module_impl.h similarity index 100% rename from mqtt_streaming_module/include/mqtt_streaming_module/mqtt_streaming_module_impl.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_streaming_module_impl.h diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_json_receiver_fb_impl.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_subscriber_fb_impl.h similarity index 64% rename from mqtt_streaming_module/include/mqtt_streaming_module/mqtt_json_receiver_fb_impl.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_subscriber_fb_impl.h index 114ea32..7570432 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_json_receiver_fb_impl.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_subscriber_fb_impl.h @@ -18,54 +18,79 @@ #include "MqttAsyncClient.h" #include "MqttDataWrapper.h" #include -#include +#include +#include "mqtt_streaming_module/constants.h" #include BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE -class MqttJsonReceiverFbImpl final : public MqttBaseFb +class MqttSubscriberFbImpl final : public FunctionBlock { - friend class MqttJsonFbHelper; + friend class MqttSubscriberFbHelper; friend class MqttJsonDecoderFbHelper; public: - explicit DAQ_MQTT_STREAM_MODULE_API MqttJsonReceiverFbImpl(const ContextPtr& ctx, + struct CmdResult + { + bool success = false; + std::string msg; + int token = 0; + + CmdResult(bool success = false, const std::string& msg = "", int token = 0) + : success(success), + msg(msg), + token(token) + { + } + }; + + explicit DAQ_MQTT_STREAM_MODULE_API MqttSubscriberFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, const FunctionBlockTypePtr& type, std::shared_ptr subscriber, const PropertyObjectPtr& config = nullptr); - DAQ_MQTT_STREAM_MODULE_API ~MqttJsonReceiverFbImpl() override; + DAQ_MQTT_STREAM_MODULE_API ~MqttSubscriberFbImpl() override; DAQ_MQTT_STREAM_MODULE_API static FunctionBlockTypePtr CreateType(); - DAQ_MQTT_STREAM_MODULE_API std::string getSubscribedTopic() const override; + DAQ_MQTT_STREAM_MODULE_API std::string getSubscribedTopic() const; protected: - mqtt::MqttDataWrapper jsonDataWorker; - std::unordered_map outputSignals; - std::string topicForSubscribing; static std::atomic localIndex; + std::shared_ptr subscriber; + int qos = DEFAULT_SUB_QOS; + mqtt::MqttDataWrapper jsonDataWorker; + std::string topicForSubscribing; DictObjectPtr nestedFbTypes; std::vector nestedFunctionBlocks; + SignalConfigPtr outputSignal; + bool enablePreview; + bool previewIsString; + std::atomic waitingForData; + + DAQ_MQTT_STREAM_MODULE_API void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg); static std::string generateLocalId(); void initNestedFbTypes(); void createSignals(); - void clearSubscribedTopic() override; + void clearSubscribedTopic(); - void processMessage(const mqtt::MqttMessage& msg) override; + void processMessage(const mqtt::MqttMessage& msg); void initProperties(const PropertyObjectPtr& config); - void readProperties() override; + void readProperties(); void readJsonConfig(); std::pair readFileToString(const std::string& filePath); void setJsonConfig(const std::string config); - void propertyChanged() override; + void propertyChanged(); bool setTopic(std::string topic); + CmdResult subscribeToTopic(); + CmdResult unsubscribeFromTopic(); + void removed() override; DictPtr onGetAvailableFunctionBlockTypes() override; FunctionBlockPtr onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) override; void onRemoveFunctionBlock(const FunctionBlockPtr& functionBlock) override; diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/signal_arr_atomic_sample_handler.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/signal_arr_atomic_sample_handler.h similarity index 82% rename from mqtt_streaming_module/include/mqtt_streaming_module/signal_arr_atomic_sample_handler.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/signal_arr_atomic_sample_handler.h index e16b43c..a4bffc0 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/signal_arr_atomic_sample_handler.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/signal_arr_atomic_sample_handler.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include @@ -24,14 +25,15 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE class SignalArrayAtomicSampleHandler : public HandlerBase { public: - explicit SignalArrayAtomicSampleHandler(bool useSignalNames, std::string topic); + explicit SignalArrayAtomicSampleHandler(WeakRefPtr parentFb, SignalValueJSONKey signalNamesMode, std::string topic); MqttData processSignalContexts(std::vector& signalContexts) override; ProcedureStatus validateSignalContexts(const std::vector& signalContexts) const override; ProcedureStatus signalListChanged(std::vector& signalContexts) override; + ListPtr getTopics(const std::vector& signalContexts) override; + std::string getSchema() override; protected: - bool useSignalNames; const std::string topic; std::string toString(const std::string valueFieldName, daq::DataPacketPtr packet); diff --git a/modules/mqtt_streaming_module/include/mqtt_streaming_module/status_adaptor.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/status_adaptor.h new file mode 100644 index 0000000..2c0a4fe --- /dev/null +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/status_adaptor.h @@ -0,0 +1,72 @@ +/* + * 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 + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE + +class StatusAdaptor +{ +public: + StatusAdaptor(const std::string typeName, + const std::string statusName, + ComponentStatusContainerPtr statusContainer, + std::string initState, + TypeManagerPtr typeManager) + : typeName(typeName), + statusName(statusName), + statusContainer(statusContainer), + typeManager(typeManager) + { + currentStatus = Enumeration(typeName, initState, typeManager); + currentMessage = ""; + statusContainer.template asPtr(true).addStatus(statusName, currentStatus); + } + + bool setStatus(const std::string& status, const std::string& message = "") + { + std::scoped_lock lock(statusMutex); + const auto newStatus = Enumeration(typeName, String(status), typeManager); + bool changed = (newStatus != currentStatus || message != currentMessage); + if (changed) + { + currentStatus = newStatus; + currentMessage = message; + statusContainer.template asPtr(true).setStatusWithMessage(statusName, currentStatus, message); + } + return changed; + } + std::string getStatus() + { + std::scoped_lock lock(statusMutex); + return currentStatus.getValue().toStdString(); + } + +private: + const std::string typeName; + const std::string statusName; + std::string currentMessage; + ComponentStatusContainerPtr statusContainer; + EnumerationPtr currentStatus; + TypeManagerPtr typeManager; + std::mutex statusMutex; +}; + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/status_helper.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/status_helper.h similarity index 82% rename from mqtt_streaming_module/include/mqtt_streaming_module/status_helper.h rename to modules/mqtt_streaming_module/include/mqtt_streaming_module/status_helper.h index a417a43..2ce4a96 100644 --- a/mqtt_streaming_module/include/mqtt_streaming_module/status_helper.h +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/status_helper.h @@ -41,6 +41,7 @@ class StatusHelper { addTypesToTypeManager(typeName, statusName, statusMap, typeManager); currentStatus = EnumerationWithIntValue(typeName, static_cast(initState), typeManager); + currentMessage = ""; statusContainer.template asPtr(true).addStatus(statusName, currentStatus); } @@ -59,11 +60,18 @@ class StatusHelper } } - void setStatus(T status, const std::string& message = "") + bool setStatus(T status, const std::string& message = "") { std::scoped_lock lock(statusMutex); - currentStatus = EnumerationWithIntValue(typeName, static_cast(status), typeManager); - statusContainer.template asPtr(true).setStatusWithMessage(statusName, currentStatus, message); + const auto newStatus = EnumerationWithIntValue(typeName, static_cast(status), typeManager); + bool changed = (newStatus != currentStatus || message != currentMessage); + if (changed) + { + currentStatus = newStatus; + currentMessage = message; + statusContainer.template asPtr(true).setStatusWithMessage(statusName, currentStatus, message); + } + return changed; } T getStatus() { @@ -74,6 +82,7 @@ class StatusHelper private: const std::string typeName; const std::string statusName; + std::string currentMessage; ComponentStatusContainerPtr statusContainer; const std::vector>& statusMap; EnumerationPtr currentStatus; diff --git a/modules/mqtt_streaming_module/include/mqtt_streaming_module/types.h b/modules/mqtt_streaming_module/include/mqtt_streaming_module/types.h new file mode 100644 index 0000000..dd52ee3 --- /dev/null +++ b/modules/mqtt_streaming_module/include/mqtt_streaming_module/types.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE + +struct MqttDataSample { + SignalConfigPtr previewSignal; + std::string topic; + std::string message; +}; + +struct MqttData { + std::vector data; + bool needRevalidation = false; + + void merge(MqttData&& other) + { + data.reserve(data.size() + other.data.size()); + data.insert(data.end(), std::make_move_iterator(other.data.begin()), std::make_move_iterator(other.data.end())); + needRevalidation = needRevalidation || other.needRevalidation; + } +}; + +enum class TopicMode { + PerSignal = 0, + Single, + _count +}; + +enum class SignalValueJSONKey { + GlobalID = 0, + LocalID, + Name, + _count +}; + +struct PublisherFbConfig { + TopicMode topicMode; + std::string topicName; + bool groupValues; + SignalValueJSONKey valueFieldName; + size_t groupValuesPackSize; + int qos; + int periodMs; + bool enablePreview; +}; + +struct SignalContext +{ + InputPortConfigPtr inputPort; + SignalConfigPtr previewSignal; + SignalContext(size_t index, InputPortConfigPtr inputPort, SignalConfigPtr previewSignal) + : index(index), inputPort(inputPort), previewSignal(previewSignal) + { + } + size_t getIndex() const + { + return index; + } +private: + size_t index; +}; + +struct ProcedureStatus +{ + bool success; + std::vector messages; + + void addError(const std::string& msg) + { + success = false; + messages.push_back(msg); + } + + void merge(const ProcedureStatus& other) + { + success = success && other.success; + messages.insert(messages.end(), other.messages.begin(), other.messages.end()); + } +}; + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/src/CMakeLists.txt b/modules/mqtt_streaming_module/src/CMakeLists.txt similarity index 80% rename from mqtt_streaming_module/src/CMakeLists.txt rename to modules/mqtt_streaming_module/src/CMakeLists.txt index 136ccd1..25ac9bf 100644 --- a/mqtt_streaming_module/src/CMakeLists.txt +++ b/modules/mqtt_streaming_module/src/CMakeLists.txt @@ -4,11 +4,9 @@ set(MODULE_HEADERS_DIR ../include/${TARGET_FOLDER_NAME}) set(SRC_Include common.h module_dll.h mqtt_streaming_module_impl.h - mqtt_root_fb_impl.h - mqtt_base_fb.h - mqtt_json_receiver_fb_impl.h + mqtt_client_fb_impl.h mqtt_json_decoder_fb_impl.h - mqtt_raw_receiver_fb_impl.h + mqtt_subscriber_fb_impl.h mqtt_publisher_fb_impl.h constants.h helper.h @@ -17,23 +15,24 @@ set(SRC_Include common.h atomic_signal_atomic_sample_handler.h atomic_signal_sample_arr_handler.h group_signal_shared_ts_handler.h + group_signal_shared_ts_arr_handler.h signal_arr_atomic_sample_handler.h types.h status_helper.h + status_adaptor.h ) set(SRC_Srcs module_dll.cpp mqtt_streaming_module_impl.cpp - mqtt_root_fb_impl.cpp - mqtt_base_fb.cpp - mqtt_json_receiver_fb_impl.cpp + mqtt_client_fb_impl.cpp mqtt_json_decoder_fb_impl.cpp - mqtt_raw_receiver_fb_impl.cpp + mqtt_subscriber_fb_impl.cpp mqtt_publisher_fb_impl.cpp helper.cpp atomic_signal_atomic_sample_handler.cpp atomic_signal_sample_arr_handler.cpp group_signal_shared_ts_handler.cpp + group_signal_shared_ts_arr_handler.cpp signal_arr_atomic_sample_handler.cpp ) @@ -44,6 +43,7 @@ source_group("common" FILES ${MODULE_HEADERS_DIR}/common.h ${MODULE_HEADERS_DIR}/helper.h ${MODULE_HEADERS_DIR}/types.h ${MODULE_HEADERS_DIR}/status_helper.h + ${MODULE_HEADERS_DIR}/status_adaptor.h helper.cpp ) @@ -53,18 +53,14 @@ source_group("module" FILES ${MODULE_HEADERS_DIR}/mqtt_streaming_module_impl.h mqtt_streaming_module_impl.cpp ) -source_group("functionalBlock" FILES ${MODULE_HEADERS_DIR}/mqtt_json_receiver_fb_impl.h - mqtt_json_receiver_fb_impl.cpp - ${MODULE_HEADERS_DIR}/mqtt_json_decoder_fb_impl.h +source_group("functionalBlock" FILES ${MODULE_HEADERS_DIR}/mqtt_json_decoder_fb_impl.h mqtt_json_decoder_fb_impl.cpp - ${MODULE_HEADERS_DIR}/mqtt_raw_receiver_fb_impl.h - mqtt_raw_receiver_fb_impl.cpp + ${MODULE_HEADERS_DIR}/mqtt_subscriber_fb_impl.h + mqtt_subscriber_fb_impl.cpp ${MODULE_HEADERS_DIR}/mqtt_publisher_fb_impl.h mqtt_publisher_fb_impl.cpp - ${MODULE_HEADERS_DIR}/mqtt_base_fb.h - mqtt_base_fb.cpp - ${MODULE_HEADERS_DIR}/mqtt_root_fb_impl.h - mqtt_root_fb_impl.cpp + ${MODULE_HEADERS_DIR}/mqtt_client_fb_impl.h + mqtt_client_fb_impl.cpp ) source_group("handlers" FILES ${MODULE_HEADERS_DIR}/handler_base.h @@ -72,10 +68,12 @@ source_group("handlers" FILES ${MODULE_HEADERS_DIR}/handler_base.h ${MODULE_HEADERS_DIR}/atomic_signal_atomic_sample_handler.h ${MODULE_HEADERS_DIR}/atomic_signal_sample_arr_handler.h ${MODULE_HEADERS_DIR}/group_signal_shared_ts_handler.h + ${MODULE_HEADERS_DIR}/group_signal_shared_ts_arr_handler.h ${MODULE_HEADERS_DIR}/signal_arr_atomic_sample_handler.h atomic_signal_atomic_sample_handler.cpp atomic_signal_sample_arr_handler.cpp group_signal_shared_ts_handler.cpp + group_signal_shared_ts_arr_handler.cpp signal_arr_atomic_sample_handler.cpp ) @@ -86,6 +84,12 @@ add_library(${LIB_NAME} SHARED ${SRC_Include} ) add_library(${SDK_TARGET_NAMESPACE}::${LIB_NAME} ALIAS ${LIB_NAME}) +if(OPENDAQ_MQTT_ENABLE_TESTS) + target_compile_definitions(${LIB_NAME} + PRIVATE OPENDAQ_MQTT_ENABLE_TESTS + ) +endif() + if (MSVC) target_compile_options(${LIB_NAME} PRIVATE /bigobj) endif() diff --git a/mqtt_streaming_module/src/atomic_signal_atomic_sample_handler.cpp b/modules/mqtt_streaming_module/src/atomic_signal_atomic_sample_handler.cpp similarity index 80% rename from mqtt_streaming_module/src/atomic_signal_atomic_sample_handler.cpp rename to modules/mqtt_streaming_module/src/atomic_signal_atomic_sample_handler.cpp index 53fdf6b..ea163d8 100644 --- a/mqtt_streaming_module/src/atomic_signal_atomic_sample_handler.cpp +++ b/modules/mqtt_streaming_module/src/atomic_signal_atomic_sample_handler.cpp @@ -8,8 +8,8 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE -AtomicSignalAtomicSampleHandler::AtomicSignalAtomicSampleHandler(bool useSignalNames) - : useSignalNames(useSignalNames) +AtomicSignalAtomicSampleHandler::AtomicSignalAtomicSampleHandler(WeakRefPtr parentFb, SignalValueJSONKey signalNamesMode) + : HandlerBase(parentFb, signalNamesMode) { } @@ -19,8 +19,7 @@ MqttData AtomicSignalAtomicSampleHandler::processSignalContexts(std::vector())); + auto dataPacket = packet.asPtr(); + for (size_t i = 0; i < dataPacket.getSampleCount(); ++i) + messages.data.emplace_back(processDataPacket(signalContext, dataPacket, i)); } packet = conn.dequeue(); @@ -121,13 +125,13 @@ void AtomicSignalAtomicSampleHandler::processSignalDescriptorChanged(SignalConte { } -std::string AtomicSignalAtomicSampleHandler::toString(const std::string valueFieldName, daq::DataPacketPtr packet) +std::string AtomicSignalAtomicSampleHandler::toString(const std::string valueFieldName, daq::DataPacketPtr packet, size_t offset) { std::string result; - std::string data = HandlerBase::toString(packet); + std::string data = HandlerBase::toString(packet, offset); if (auto domainPacket = packet.getDomainPacket(); domainPacket.assigned()) { - uint64_t ts = convertToEpoch(domainPacket); + uint64_t ts = convertToEpoch(domainPacket, offset); result = fmt::format("{{\"{}\" : {}, \"timestamp\": {}}}", valueFieldName, data, ts); } else @@ -143,13 +147,31 @@ std::string AtomicSignalAtomicSampleHandler::buildTopicName(const SignalContext& return signalContext.inputPort.getSignal().getGlobalId().toStdString(); } -MqttDataSample AtomicSignalAtomicSampleHandler::processDataPacket(SignalContext& signalContext, const DataPacketPtr& dataPacket) +MqttDataSample AtomicSignalAtomicSampleHandler::processDataPacket(SignalContext& signalContext, const DataPacketPtr& dataPacket, size_t offset) { const auto signal = signalContext.inputPort.getSignal(); - std::string valueFieldName = useSignalNames ? signal.getName().toStdString() : signal.getGlobalId().toStdString(); - auto msg = toString(valueFieldName, dataPacket); + std::string valueFieldName = buildValueFieldName(signalNamesMode, signal); + auto msg = toString(valueFieldName, dataPacket, offset); std::string topic = buildTopicName(signalContext); - return MqttDataSample{topic, msg}; + return MqttDataSample{signalContext.previewSignal, topic, msg}; } +ListPtr AtomicSignalAtomicSampleHandler::getTopics(const std::vector& signalContexts) +{ + auto res = List(); + for (const auto& sigCtx : signalContexts) + { + if (!sigCtx.inputPort.getConnection().assigned()) + continue; + auto t = buildTopicName(sigCtx); + res.pushBack(String(t)); + } + return res; +} + +std::string AtomicSignalAtomicSampleHandler::getSchema() +{ + return fmt::format("{{\"{}\" : , \"timestamp\": }}", buildValueFieldNameForSchema(signalNamesMode)); +}; + END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/modules/mqtt_streaming_module/src/atomic_signal_sample_arr_handler.cpp b/modules/mqtt_streaming_module/src/atomic_signal_sample_arr_handler.cpp new file mode 100644 index 0000000..65b433b --- /dev/null +++ b/modules/mqtt_streaming_module/src/atomic_signal_sample_arr_handler.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE + +AtomicSignalSampleArrayHandler::AtomicSignalSampleArrayHandler(WeakRefPtr parentFb, SignalValueJSONKey signalNamesMode, size_t packSize) + : AtomicSignalAtomicSampleHandler(parentFb, signalNamesMode), + packSize(packSize > 0 ? packSize : 1) +{ +} + +ProcedureStatus AtomicSignalSampleArrayHandler::signalListChanged(std::vector& signalContexts) +{ + std::set set; + for (const auto& buf : signalBuffers) + set.insert(buf.first); + + for (auto& sigCtx : signalContexts) + { + const auto signal = sigCtx.inputPort.getSignal(); + if (!signal.assigned()) + continue; + auto& buffer = signalBuffers[signal.getGlobalId().toStdString()]; + buffer.clear(); + set.erase(signal.getGlobalId().toStdString()); + } + for (const auto& el : set) + signalBuffers.erase(el); + + return ProcedureStatus{true, {}}; +} + +std::string AtomicSignalSampleArrayHandler::getSchema() +{ + if (packSize == 1) + { + return fmt::format("{{\"{}\" : [], \"timestamp\": []}}", buildValueFieldNameForSchema(signalNamesMode)); + } + else if (packSize == 2) + { + return fmt::format("{{\"{}\" : [, ], \"timestamp\": [, ]}}", buildValueFieldNameForSchema(signalNamesMode)); + } + else + { + return fmt::format("{{\"{}\" : [, ..., ], \"timestamp\": [, ..., ]}}", buildValueFieldNameForSchema(signalNamesMode), packSize - 1, packSize - 1); + } +} + +MqttData AtomicSignalSampleArrayHandler::processSignalContext(SignalContext& signalContext) +{ + MqttData messages; + const auto conn = signalContext.inputPort.getConnection(); + if (!conn.assigned()) + return messages; + + PacketPtr packet = conn.dequeue(); + while (packet.assigned()) + { + if (packet.getType() == PacketType::Event) + { + auto eventPacket = packet.asPtr(true); + LOG_T("Processing {} event", eventPacket.getEventId()) + if (eventPacket.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED) + { + DataDescriptorPtr valueSignalDescriptor = eventPacket.getParameters().get(event_packet_param::DATA_DESCRIPTOR); + DataDescriptorPtr domainSignalDescriptor = eventPacket.getParameters().get(event_packet_param::DOMAIN_DATA_DESCRIPTOR); + processSignalDescriptorChanged(signalContext, valueSignalDescriptor, domainSignalDescriptor); + messages.needRevalidation = true; + break; + } + } + else if (packet.getType() == PacketType::Data) + { + auto dataPacket = packet.asPtr(); + const auto sigGlobalId = signalContext.inputPort.getSignal().getGlobalId().toStdString(); + signalBuffers[sigGlobalId].data.push_back(dataPacket); + signalBuffers[sigGlobalId].dataSize += dataPacket.getSampleCount(); + while (signalBuffers[sigGlobalId].dataSize >= packSize) + messages.data.emplace_back(processDataPackets(signalContext)); + + } + + packet = conn.dequeue(); + } + return messages; +} + +std::pair AtomicSignalSampleArrayHandler::getSample(SignalContext& signalContext) +{ + const auto sigGlobalId = signalContext.inputPort.getSignal().getGlobalId().toStdString(); + if (signalBuffers[sigGlobalId].data.empty()) + return {nullptr, 0}; + auto dataPacket = signalBuffers[sigGlobalId].data.front(); + size_t offset = signalBuffers[sigGlobalId].offset++; + signalBuffers[sigGlobalId].dataSize--; + if (signalBuffers[sigGlobalId].offset == dataPacket.getSampleCount()) + { + signalBuffers[sigGlobalId].data.pop_front(); + signalBuffers[sigGlobalId].offset = 0; + } + return {dataPacket, offset}; +} + +void AtomicSignalSampleArrayHandler::processSignalDescriptorChanged(SignalContext& signalCtx, + const DataDescriptorPtr& valueSigDesc, + const DataDescriptorPtr& domainSigDesc) +{ + signalBuffers[signalCtx.inputPort.getSignal().getGlobalId().toStdString()].clear(); +} + +std::string AtomicSignalSampleArrayHandler::toString(const std::string valueFieldName, SignalContext& signalContext) +{ + std::ostringstream dataOss; + std::ostringstream tsOss; + bool hasDomain = true; + dataOss << "["; + tsOss << "["; + size_t commonCnt = 0; + while (commonCnt < packSize) + { + auto [dataPacket, offset] = getSample(signalContext); + if (commonCnt > 0) + { + dataOss << ", "; + tsOss << ", "; + } + + dataOss << HandlerBase::toString(dataPacket, offset); + + if (auto domainPacket = dataPacket.getDomainPacket(); domainPacket.assigned()) + { + uint64_t ts = convertToEpoch(domainPacket, offset); + tsOss << std::to_string(ts); + } + else + { + hasDomain = false; + } + commonCnt++; + } + dataOss << "]"; + tsOss << "]"; + std::string result; + if (hasDomain) + result = fmt::format("{{\"{}\" : {}, \"timestamp\": {}}}", valueFieldName, dataOss.str(), tsOss.str()); + else + result = fmt::format("{{\"{}\" : {}}}", valueFieldName, dataOss.str()); + + return result; +} + +MqttDataSample AtomicSignalSampleArrayHandler::processDataPackets(SignalContext& signalContext) +{ + if (signalBuffers[signalContext.inputPort.getSignal().getGlobalId().toStdString()].data.empty()) + return MqttDataSample{nullptr, "", ""}; + const auto signal = signalContext.inputPort.getSignal(); + std::string valueFieldName = buildValueFieldName(signalNamesMode, signal); + auto msg = toString(valueFieldName, signalContext); + std::string topic = buildTopicName(signalContext); + return MqttDataSample{signalContext.previewSignal, topic, msg}; +} +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/modules/mqtt_streaming_module/src/group_signal_shared_ts_arr_handler.cpp b/modules/mqtt_streaming_module/src/group_signal_shared_ts_arr_handler.cpp new file mode 100644 index 0000000..9960381 --- /dev/null +++ b/modules/mqtt_streaming_module/src/group_signal_shared_ts_arr_handler.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE + +GroupSignalSharedTsArrHandler::GroupSignalSharedTsArrHandler(WeakRefPtr parentFb, + SignalValueJSONKey signalNamesMode, + std::string topic, + size_t packSize) + : GroupSignalSharedTsHandler(parentFb, signalNamesMode, topic), + packSize(packSize), + tsBuilder(packSize) +{ +} + +MqttData GroupSignalSharedTsArrHandler::processSignalContexts(std::vector& signalContexts) +{ + MqttData messages; + std::scoped_lock lock(sync); + if (!reader.assigned()) + return messages; + + const auto dataAvailable = reader.getAvailableCount(); + SizeT count = std::min(SizeT{buffersSize}, dataAvailable); + auto status = reader.read(dataBuffers.data(), &count); + if (count > 0) + { + const auto tsStruct = domainToTs(status); + for (size_t signalCnt = 0; signalCnt < signalContexts.size() - 1; ++signalCnt) + { + const auto signal = signalContexts[signalCnt].inputPort.getSignal(); + dataBuilders[signalCnt].append(signal.getDescriptor().getSampleType(), dataBuffers[signalCnt], count); + } + tsBuilder.append(tsStruct, count); + } + + messages.needRevalidation = processEvents(status); + + while (!tsBuilder.empty()) + { + std::vector fields; + for (size_t signalCnt = 0; signalCnt < signalContexts.size() - 1; ++signalCnt) + { + std::string valueFieldName = buildValueFieldName(signalNamesMode, signalContexts[signalCnt].inputPort.getSignal()); + fields.emplace_back(dataBuilders[signalCnt].getPack(valueFieldName)); + } + fields.emplace_back(tsBuilder.getPack()); + messages.data.emplace_back(MqttDataSample{signalContexts[0].previewSignal, buildTopicName(), messageFromFields(fields)}); + } + return messages; +} + +ProcedureStatus GroupSignalSharedTsArrHandler::signalListChanged(std::vector& signalContexts) +{ + auto status = GroupSignalSharedTsHandler::signalListChanged(signalContexts); + initBuilders(signalContexts.size() - 1); + return status; +} + +std::string GroupSignalSharedTsArrHandler::getSchema() +{ + if (packSize == 1) + { + return fmt::format("{{\"{}\" : [], ..., \"{}\" : [], \"timestamp\": []}}", + buildValueFieldNameForSchema(signalNamesMode, "_0"), + buildValueFieldNameForSchema(signalNamesMode, "_N")); + } + else if (packSize == 2) + { + return fmt::format("{{\"{}\" : [, ], ..., \"{}\" : [, ], " + "\"timestamp\": [, ]}}", + buildValueFieldNameForSchema(signalNamesMode, "_0"), + buildValueFieldNameForSchema(signalNamesMode, "_N")); + } + else + { + return fmt::format("{{\"{}\" : [, ..., ], ..., \"timestamp\": [, ..., " + "]}}", + buildValueFieldNameForSchema(signalNamesMode), + packSize - 1, + packSize - 1); + } +} + +void GroupSignalSharedTsArrHandler::initBuilders(const size_t size) +{ + if (dataBuilders.empty() || dataBuilders.size() != size) + { + tsBuilder.clear(); + dataBuilders.clear(); + dataBuilders.reserve(size); + for (size_t i = 0; i < size; ++i) + dataBuilders.emplace_back(packSize); + } +} + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/src/group_signal_shared_ts_handler.cpp b/modules/mqtt_streaming_module/src/group_signal_shared_ts_handler.cpp similarity index 78% rename from mqtt_streaming_module/src/group_signal_shared_ts_handler.cpp rename to modules/mqtt_streaming_module/src/group_signal_shared_ts_handler.cpp index be4c5be..759a3f4 100644 --- a/mqtt_streaming_module/src/group_signal_shared_ts_handler.cpp +++ b/modules/mqtt_streaming_module/src/group_signal_shared_ts_handler.cpp @@ -11,22 +11,33 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE -GroupSignalSharedTsHandler::GroupSignalSharedTsHandler(bool useSignalNames, std::string topic) - : useSignalNames(useSignalNames), +GroupSignalSharedTsHandler::GroupSignalSharedTsHandler(WeakRefPtr parentFb, SignalValueJSONKey signalNamesMode, std::string topic) + : HandlerBase(parentFb, signalNamesMode), buffersSize(1000), topic(topic) { } +GroupSignalSharedTsHandler::~GroupSignalSharedTsHandler() +{ + std::scoped_lock lock(sync); + deallocateBuffers(); + if (reader.assigned()) + { + reader.dispose(); + } +} + MqttData GroupSignalSharedTsHandler::processSignalContexts(std::vector& signalContexts) { MqttData messages; + std::scoped_lock lock(sync); if (!reader.assigned()) return messages; auto dataAvailable = reader.getAvailableCount(); auto count = std::min(SizeT{buffersSize}, dataAvailable); auto status = reader.read(dataBuffers.data(), &count); - if (status.getReadStatus() == ReadStatus::Ok && count > 0) + if (count > 0) { const auto tsStruct = domainToTs(status); for (SizeT sampleCnt = 0; sampleCnt < count; ++sampleCnt) @@ -35,17 +46,19 @@ MqttData GroupSignalSharedTsHandler::processSignalContexts(std::vector namesSet; + for (const auto& sigCtx : signalContexts) + { + auto signal = sigCtx.inputPort.getSignal(); + if (!signal.assigned()) + continue; + std::string name = buildValueFieldName(signalNamesMode, signal); + if (namesSet.find(name) != namesSet.end()) + { + std::string key; + if (signalNamesMode == SignalValueJSONKey::GlobalID) + key = "GlobalID"; + else if (signalNamesMode == SignalValueJSONKey::LocalID) + key = "LocalID"; + else + key = "name"; + status.addError( + fmt::format("Connected signals have non-unique {} (\"{}\"). JSON field names cannot be built", key, name)); + } + namesSet.insert(std::move(name)); + } + status.merge(HandlerBase::validateSignalContexts(signalContexts)); return status; } @@ -159,9 +195,32 @@ TimestampTickStruct GroupSignalSharedTsHandler::domainToTs(const MultiReaderStat res.ratioDen = descriptor.getTickResolution().getDenominator(); res.multiplier = 1'000'000; // amount of us in a second res.delta = descriptor.getRule().getParameters().get("delta").getValue(0); + + const uint64_t g = std::gcd(res.multiplier, res.ratioDen); + res.multiplier /= g; + res.ratioDen /= g; return res; } +bool GroupSignalSharedTsHandler::processEvents(const MultiReaderStatusPtr& status) +{ + bool result; + if (status.getReadStatus() == ReadStatus::Event) + { + bool descChanged = std::any_of(status.getEventPackets().begin(), + status.getEventPackets().end(), + [](const auto& evPair) + { + const auto& ev = evPair.second; + return (ev.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED); + }); + result = !firstDescriptorChange && descChanged; + firstDescriptorChange &= !descChanged; + } + result &= !status.getValid(); + return result; +} + ProcedureStatus GroupSignalSharedTsHandler::signalListChanged(std::vector& signalContexts) { ProcedureStatus status{true, {}}; @@ -169,6 +228,17 @@ ProcedureStatus GroupSignalSharedTsHandler::signalListChanged(std::vector GroupSignalSharedTsHandler::getTopics(const std::vector& signalContexts) +{ + auto res = List(String(buildTopicName())); + return res; +} + +std::string GroupSignalSharedTsHandler::getSchema() +{ + return fmt::format("{{\"{}\" : , ..., \"{}\" : , \"timestamp\": }}", buildValueFieldNameForSchema(signalNamesMode, "_0"), buildValueFieldNameForSchema(signalNamesMode, "_N")); +} + std::string GroupSignalSharedTsHandler::toString(const SampleType sampleType, const std::string& valueFieldName, void* data, SizeT offset) { switch (sampleType) @@ -228,9 +298,6 @@ std::string GroupSignalSharedTsHandler::toString(const std::string& valueFieldNa std::string GroupSignalSharedTsHandler::tsToString(TimestampTickStruct tsStruct, SizeT offset) { // const uint64_t epochTime = (firstTick + delta * offset) * ratioNum * US_IN_S / ratioDen; // us - const uint64_t g = std::gcd(tsStruct.multiplier, tsStruct.ratioDen); - tsStruct.multiplier /= g; - tsStruct.ratioDen /= g; return fmt::format("\"timestamp\" : {}", std::to_string(((tsStruct.firstTick + tsStruct.delta * offset) * tsStruct.ratioNum * tsStruct.multiplier) / tsStruct.ratioDen)); @@ -242,13 +309,21 @@ std::string GroupSignalSharedTsHandler::buildTopicName() } void GroupSignalSharedTsHandler::createReader(const std::vector& signalContexts) +{ + std::scoped_lock lock(sync); + createReaderInternal(signalContexts); +} + +void GroupSignalSharedTsHandler::createReaderInternal(const std::vector& signalContexts) { // signalContexts always contain an unconnected input port if (signalContexts.size() <= 1) return; if (reader.assigned()) - reader.release(); + { + reader.dispose(); + } auto multiReaderBuilder = MultiReaderBuilder().setValueReadType(SampleType::Undefined).setDomainReadType(SampleType::UInt64); for (const auto& sContext : signalContexts) @@ -256,8 +331,11 @@ void GroupSignalSharedTsHandler::createReader(const std::vector& if (sContext.inputPort.getSignal().assigned()) multiReaderBuilder.addInputPort(sContext.inputPort); } - + firstDescriptorChange = true; reader = multiReaderBuilder.build(); + const auto parentFb = this->parentFb.getRef(); + if (parentFb.assigned()) + reader.setExternalListener(parentFb.asPtr()); allocateBuffers(signalContexts); } @@ -265,10 +343,7 @@ void GroupSignalSharedTsHandler::allocateBuffers(const std::vector(signalsCount, nullptr); @@ -278,6 +353,16 @@ void GroupSignalSharedTsHandler::allocateBuffers(const std::vector& fields) { std::ostringstream oss; diff --git a/mqtt_streaming_module/src/helper.cpp b/modules/mqtt_streaming_module/src/helper.cpp similarity index 100% rename from mqtt_streaming_module/src/helper.cpp rename to modules/mqtt_streaming_module/src/helper.cpp diff --git a/mqtt_streaming_module/src/module_dll.cpp b/modules/mqtt_streaming_module/src/module_dll.cpp similarity index 100% rename from mqtt_streaming_module/src/module_dll.cpp rename to modules/mqtt_streaming_module/src/module_dll.cpp diff --git a/mqtt_streaming_module/src/mqtt_root_fb_impl.cpp b/modules/mqtt_streaming_module/src/mqtt_client_fb_impl.cpp similarity index 70% rename from mqtt_streaming_module/src/mqtt_root_fb_impl.cpp rename to modules/mqtt_streaming_module/src/mqtt_client_fb_impl.cpp index acc63f0..103f6df 100644 --- a/mqtt_streaming_module/src/mqtt_root_fb_impl.cpp +++ b/modules/mqtt_streaming_module/src/mqtt_client_fb_impl.cpp @@ -1,8 +1,7 @@ #include "mqtt_streaming_module/constants.h" -#include "mqtt_streaming_module/mqtt_json_receiver_fb_impl.h" +#include "mqtt_streaming_module/mqtt_subscriber_fb_impl.h" #include "mqtt_streaming_module/mqtt_publisher_fb_impl.h" -#include "mqtt_streaming_module/mqtt_raw_receiver_fb_impl.h" -#include +#include #include #include @@ -10,21 +9,16 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE constexpr int MQTT_CLIENT_SYNC_DISCONNECT_TOUT = 3000; -std::atomic MqttRootFbImpl::localIndex = 0; -std::vector> MqttRootFbImpl::connectionStatusMap = - {{ConnectionStatus::Connected, "Connected"}, - {ConnectionStatus::Reconnecting, "Reconnecting"}, - {ConnectionStatus::Disconnected, "Disconnected"}}; +std::atomic MqttClientFbImpl::localIndex = 0; -MqttRootFbImpl::MqttRootFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, const PropertyObjectPtr& config) +MqttClientFbImpl::MqttClientFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, const PropertyObjectPtr& config) : FunctionBlock(CreateType(), ctx, parent, generateLocalId()), subscriber(std::make_shared()), connectTimeout(0), - connectionStatus(MQTT_ROOT_FB_CON_STATUS_TYPE, - MQTT_ROOT_FB_CON_STATUS_NAME, + connectionStatus("ConnectionStatusType", + MQTT_CLIENT_FB_CON_STATUS_NAME, statusContainer, - connectionStatusMap, - ConnectionStatus::Disconnected, + "Reconnecting", context.getTypeManager()) { initComponentStatus(); @@ -42,7 +36,7 @@ MqttRootFbImpl::MqttRootFbImpl(const ContextPtr& ctx, const ComponentPtr& parent LOG_I("MQTT: Connection established"); } -void MqttRootFbImpl::removed() +void MqttClientFbImpl::removed() { FunctionBlock::removed(); LOG_I("MQTT: disconnecting from the MQTT broker...", connectionSettings.mqttUrl + ":" + std::to_string(connectionSettings.port)); @@ -54,21 +48,15 @@ void MqttRootFbImpl::removed() else { LOG_I("MQTT: disconnection was successful"); - connectionStatus.setStatus(ConnectionStatus::Disconnected); } } -void MqttRootFbImpl::initNestedFbTypes() +void MqttClientFbImpl::initNestedFbTypes() { nestedFbTypes = Dict(); - // Add a function block type for manual JSON configuration + // Add a MQTT subscriber function block type { - const auto fbType = MqttJsonReceiverFbImpl::CreateType(); - nestedFbTypes.set(fbType.getId(), fbType); - } - // Add a function block type for raw MQTT messages - { - const auto fbType = MqttRawReceiverFbImpl::CreateType(); + const auto fbType = MqttSubscriberFbImpl::CreateType(); nestedFbTypes.set(fbType.getId(), fbType); } @@ -79,7 +67,7 @@ void MqttRootFbImpl::initNestedFbTypes() } } -void MqttRootFbImpl::initMqttSubscriber() +void MqttClientFbImpl::initMqttSubscriber() { const auto serverUrl = connectionSettings.mqttUrl + ((connectionSettings.port > 0) ? ":" + std::to_string(connectionSettings.port) : ""); subscriber->setServerURL(serverUrl); @@ -96,8 +84,10 @@ void MqttRootFbImpl::initMqttSubscriber() bool expected = false; if (connectedDone.compare_exchange_strong(expected, true)) { - connectionStatus.setStatus(ConnectionStatus::Connected); + connectionStatus.setStatus("Connected"); connectedPromise.set_value(true); + std::scoped_lock lock(componentStatusSync); + setComponentStatus(ComponentStatus::Ok); } }); @@ -105,16 +95,18 @@ void MqttRootFbImpl::initMqttSubscriber() subscriber->connect(); } -void MqttRootFbImpl::initConnectionStatus() +void MqttClientFbImpl::initConnectionStatus() { subscriber->setConnectionLostCb( [this](std::string msg) { - connectionStatus.setStatus(ConnectionStatus::Reconnecting, msg); + connectionStatus.setStatus("Reconnecting", msg); + std::scoped_lock lock(componentStatusSync); + setComponentStatusWithMessage(ComponentStatus::Error, "Connection lost"); }); } -void MqttRootFbImpl::initProperties(const PropertyObjectPtr& config) +void MqttClientFbImpl::initProperties(const PropertyObjectPtr& config) { for (const auto& prop : config.getAllProperties()) { @@ -151,48 +143,46 @@ void MqttRootFbImpl::initProperties(const PropertyObjectPtr& config) readProperties(); } -void MqttRootFbImpl::readProperties() +void MqttClientFbImpl::readProperties() { - connectionSettings.mqttUrl = objPtr.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS).asPtr().toStdString(); - connectionSettings.port = objPtr.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT); - connectionSettings.username = objPtr.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME).asPtr().toStdString(); - connectionSettings.password = objPtr.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD).asPtr().toStdString(); + connectionSettings.mqttUrl = objPtr.getPropertyValue(PROPERTY_NAME_CLIENT_BROKER_ADDRESS).asPtr().toStdString(); + connectionSettings.port = objPtr.getPropertyValue(PROPERTY_NAME_CLIENT_BROKER_PORT); + connectionSettings.username = objPtr.getPropertyValue(PROPERTY_NAME_CLIENT_USERNAME).asPtr().toStdString(); + connectionSettings.password = objPtr.getPropertyValue(PROPERTY_NAME_CLIENT_PASSWORD).asPtr().toStdString(); connectionSettings.clientId = globalId.toStdString(); - connectTimeout = objPtr.getPropertyValue(PROPERTY_NAME_CONNECT_TIMEOUT); + connectTimeout = objPtr.getPropertyValue(PROPERTY_NAME_CLIENT_CONNECT_TIMEOUT); } -bool MqttRootFbImpl::waitForConnection(const int timeoutMs) +bool MqttClientFbImpl::waitForConnection(const int timeoutMs) { bool res = (connectedFuture.wait_for(std::chrono::milliseconds(timeoutMs)) == std::future_status::ready && connectedFuture.get() == true); subscriber->setConnectedCb( [this] { - connectionStatus.setStatus(ConnectionStatus::Connected); + connectionStatus.setStatus("Connected"); + std::scoped_lock lock(componentStatusSync); + setComponentStatus(ComponentStatus::Ok); }); return res; } -DictPtr MqttRootFbImpl::onGetAvailableFunctionBlockTypes() +DictPtr MqttClientFbImpl::onGetAvailableFunctionBlockTypes() { return nestedFbTypes; } -FunctionBlockPtr MqttRootFbImpl::onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) +FunctionBlockPtr MqttClientFbImpl::onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) { FunctionBlockPtr nestedFunctionBlock; { if (nestedFbTypes.hasKey(typeId)) { auto fbTypePtr = nestedFbTypes.getOrDefault(typeId); - if (fbTypePtr.getName() == RAW_FB_NAME) - { - nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, subscriber, config); - } - else if (fbTypePtr.getName() == JSON_FB_NAME) + if (fbTypePtr.getName() == SUB_FB_NAME) { - nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, subscriber, config); + nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, subscriber, config); } else if (fbTypePtr.getName() == PUB_FB_NAME) { @@ -217,43 +207,43 @@ FunctionBlockPtr MqttRootFbImpl::onAddFunctionBlock(const StringPtr& typeId, con return nestedFunctionBlock; } -std::string MqttRootFbImpl::generateLocalId() +std::string MqttClientFbImpl::generateLocalId() { - return std::string(MQTT_LOCAL_ROOT_FB_ID_PREFIX + std::to_string(localIndex++)); + return std::string(MQTT_LOCAL_CLIENT_FB_ID_PREFIX + std::to_string(localIndex++)); } -FunctionBlockTypePtr MqttRootFbImpl::CreateType() +FunctionBlockTypePtr MqttClientFbImpl::CreateType() { auto config = PropertyObject(); { auto builder = - StringPropertyBuilder(PROPERTY_NAME_MQTT_BROKER_ADDRESS, DEFAULT_BROKER_ADDRESS) + StringPropertyBuilder(PROPERTY_NAME_CLIENT_BROKER_ADDRESS, DEFAULT_BROKER_ADDRESS) .setDescription(fmt::format("MQTT broker address. It can be an IP address or a hostname. By default it is set to \"{}\".", DEFAULT_BROKER_ADDRESS)); config.addProperty(builder.build()); } { auto builder = - StringPropertyBuilder(PROPERTY_NAME_MQTT_USERNAME, DEFAULT_USERNAME) + StringPropertyBuilder(PROPERTY_NAME_CLIENT_USERNAME, DEFAULT_USERNAME) .setDescription(fmt::format("Username for MQTT broker authentication. By default it is set to \"{}\".", DEFAULT_USERNAME)); config.addProperty(builder.build()); } { auto builder = - StringPropertyBuilder(PROPERTY_NAME_MQTT_PASSWORD, DEFAULT_PASSWORD) + StringPropertyBuilder(PROPERTY_NAME_CLIENT_PASSWORD, DEFAULT_PASSWORD) .setDescription(fmt::format("Password for MQTT broker authentication. By default it is set to \"{}\".", DEFAULT_PASSWORD)); config.addProperty(builder.build()); } { auto builder = - IntPropertyBuilder(PROPERTY_NAME_MQTT_BROKER_PORT, DEFAULT_PORT) + IntPropertyBuilder(PROPERTY_NAME_CLIENT_BROKER_PORT, DEFAULT_PORT) .setMinValue(1) .setMaxValue(65535) .setDescription(fmt::format("Port number for MQTT broker connection. By default it is set to {}.", DEFAULT_PORT)); config.addProperty(builder.build()); } { - auto builder = IntPropertyBuilder(PROPERTY_NAME_CONNECT_TIMEOUT, DEFAULT_INIT_TIMEOUT) + auto builder = IntPropertyBuilder(PROPERTY_NAME_CLIENT_CONNECT_TIMEOUT, DEFAULT_INIT_TIMEOUT) .setMinValue(0) .setUnit(Unit("ms")) .setDescription(fmt::format("Timeout in milliseconds for the initial connection to the MQTT broker. If the " @@ -261,8 +251,8 @@ FunctionBlockTypePtr MqttRootFbImpl::CreateType() DEFAULT_INIT_TIMEOUT)); config.addProperty(builder.build()); } - const auto fbType = FunctionBlockType(ROOT_FB_NAME, - ROOT_FB_NAME, + const auto fbType = FunctionBlockType(CLIENT_FB_NAME, + CLIENT_FB_NAME, "The MQTT function block allows connecting to MQTT broker. It may contain nested " "publisher/subscriber FBs.", config); diff --git a/mqtt_streaming_module/src/mqtt_json_decoder_fb_impl.cpp b/modules/mqtt_streaming_module/src/mqtt_json_decoder_fb_impl.cpp similarity index 65% rename from mqtt_streaming_module/src/mqtt_json_decoder_fb_impl.cpp rename to modules/mqtt_streaming_module/src/mqtt_json_decoder_fb_impl.cpp index 0399a1c..6842835 100644 --- a/mqtt_streaming_module/src/mqtt_json_decoder_fb_impl.cpp +++ b/modules/mqtt_streaming_module/src/mqtt_json_decoder_fb_impl.cpp @@ -7,24 +7,12 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE std::atomic MqttJsonDecoderFbImpl::localIndex = 0; -std::vector> MqttJsonDecoderFbImpl::parsingStatusMap = - {{ParsingStatus::InvalidParamaters, "InvalidParamaters"}, - {ParsingStatus::ParsingFailed, "ParsingFailed"}, - {ParsingStatus::WaitingForData, "WaitingForData"}, - {ParsingStatus::ParsingSuccedeed, "ParsingSuccedeed"}}; - MqttJsonDecoderFbImpl::MqttJsonDecoderFbImpl(const ContextPtr& ctx, const ComponentPtr& parent, const FunctionBlockTypePtr& type, const PropertyObjectPtr& config) : FunctionBlock(type, ctx, parent, generateLocalId()), - jsonDataWorker(loggerComponent), - parsingStatus(MQTT_FB_PARSING_STATUS_TYPE, - MQTT_FB_PARSING_STATUS_NAME, - statusContainer, - parsingStatusMap, - ParsingStatus::WaitingForData, - context.getTypeManager()) + jsonDataWorker(loggerComponent) { initComponentStatus(); if (config.assigned()) @@ -39,29 +27,33 @@ FunctionBlockTypePtr MqttJsonDecoderFbImpl::CreateType() { auto defaultConfig = PropertyObject(); { - auto builder = StringPropertyBuilder(PROPERTY_NAME_VALUE_NAME, String("")).setDescription(""); - defaultConfig.addProperty(builder.build()); - } - - { - auto builder = StringPropertyBuilder(PROPERTY_NAME_TS_NAME, String("")).setDescription(""); + auto builder = + StringPropertyBuilder(PROPERTY_NAME_DEC_VALUE_NAME, String("")) + .setDescription("Specifies the JSON field name from which value data will be extracted. This property is required. It " + "should be contained in the incoming JSON messages. Otherwise, a parsing error will occur."); defaultConfig.addProperty(builder.build()); } { - auto builder = StringPropertyBuilder(PROPERTY_NAME_SIGNAL_NAME, String(DEFAULT_SIGNAL_NAME)).setDescription(""); + auto builder = + StringPropertyBuilder(PROPERTY_NAME_DEC_TS_NAME, String("")) + .setDescription( + "Specifies the JSON field name from which timestamp will be extracted. This property is " + "optional. If it is set it should be contained in the incoming JSON messages. Otherwise, a parsing error will occur."); defaultConfig.addProperty(builder.build()); } { - auto builder = StringPropertyBuilder(PROPERTY_NAME_UNIT, String("")).setDescription(""); + auto builder = StringPropertyBuilder(PROPERTY_NAME_DEC_UNIT, String("")) + .setDescription("Specifies the unit symbol for the decoded value. This property is optional."); defaultConfig.addProperty(builder.build()); } - const auto fbType = FunctionBlockType(JSON_DECODER_FB_NAME, - JSON_DECODER_FB_NAME, - "", - defaultConfig); + const auto fbType = + FunctionBlockType(JSON_DECODER_FB_NAME, + JSON_DECODER_FB_NAME, + "The JSON decoder Function Block extracts data from a JSON string and builds a signal based on that data.", + defaultConfig); return fbType; } @@ -96,23 +88,20 @@ void MqttJsonDecoderFbImpl::initProperties(const PropertyObjectPtr& config) void MqttJsonDecoderFbImpl::readProperties() { auto lock = this->getRecursiveConfigLock(); - configStatus.configValid = true; - configStatus.configMsg.clear(); - config.valueFieldName = readProperty(PROPERTY_NAME_VALUE_NAME, ""); + configValid = true; + configMsg.clear(); + config.valueFieldName = readProperty(PROPERTY_NAME_DEC_VALUE_NAME, ""); if (config.valueFieldName.empty()) { - configStatus.configMsg = fmt::format("\"{}\" property is empty!", PROPERTY_NAME_VALUE_NAME); - configStatus.configValid = false; + configMsg = fmt::format("\"{}\" property is empty!", PROPERTY_NAME_DEC_VALUE_NAME); + configValid = false; } - config.tsFieldName = readProperty(PROPERTY_NAME_TS_NAME, ""); - config.unitSymbol = readProperty(PROPERTY_NAME_UNIT, ""); - config.signalName = readProperty(PROPERTY_NAME_SIGNAL_NAME, DEFAULT_SIGNAL_NAME); - if (config.signalName.empty()) - config.signalName = DEFAULT_SIGNAL_NAME; + config.tsFieldName = readProperty(PROPERTY_NAME_DEC_TS_NAME, ""); + config.unitSymbol = readProperty(PROPERTY_NAME_DEC_UNIT, ""); jsonDataWorker.setValueFieldName(config.valueFieldName); jsonDataWorker.setTimestampFieldName(config.tsFieldName); - configStatus.waitingData = true; + waitingData = configValid.load(); updateStatuses(); } @@ -147,46 +136,42 @@ void MqttJsonDecoderFbImpl::propertyChanged() void MqttJsonDecoderFbImpl::updateStatuses() { - if (configStatus.configValid == false) + if (configValid == false) { - setComponentStatusWithMessage(ComponentStatus::Error, "Configuration is invalid!"); - parsingStatus.setStatus(ParsingStatus::InvalidParamaters, configStatus.configMsg); + setComponentStatusWithMessage(ComponentStatus::Error, "Configuration is invalid! " + configMsg); } - else if (configStatus.waitingData) + else if (waitingData) { - setComponentStatus(ComponentStatus::Ok); - parsingStatus.setStatus(ParsingStatus::WaitingForData); + setComponentStatusWithMessage(ComponentStatus::Ok, "Waiting for data"); } - else if (configStatus.parsingSucceeded) + else if (parsingSucceeded) { - setComponentStatus(ComponentStatus::Ok); - parsingStatus.setStatus(ParsingStatus::ParsingSuccedeed); + setComponentStatusWithMessage(ComponentStatus::Ok, "Parsing succeeded"); } else { - if (statusContainer.getStatus("ComponentStatus") == ComponentStatus::Ok) - { - setComponentStatus(ComponentStatus::Warning); - } - parsingStatus.setStatus(ParsingStatus::ParsingFailed, configStatus.parsingMsg); + setComponentStatusWithMessage(ComponentStatus::Error, "Parsing failed: " + parsingMsg); } } void MqttJsonDecoderFbImpl::processMessage(const std::string& json) { - auto lock = this->getRecursiveConfigLock(); - configStatus.waitingData = false; - auto status = jsonDataWorker.createAndSendDataPacket(json); - configStatus.parsingSucceeded = status.success; - if (status.success) + if (configValid) { - configStatus.parsingMsg.clear(); - } - else - { - configStatus.parsingMsg = status.msg; + auto lock = this->getRecursiveConfigLock(); + waitingData = false; + auto status = jsonDataWorker.createAndSendDataPacket(json); + parsingSucceeded = status.success; + if (status.success) + { + parsingMsg.clear(); + } + else + { + parsingMsg = status.msg; + } + updateStatuses(); } - updateStatuses(); } void MqttJsonDecoderFbImpl::createSignal() @@ -199,7 +184,8 @@ void MqttJsonDecoderFbImpl::createSignal() if (config.unitSymbol != "") dataDescBdr.setUnit(Unit(config.unitSymbol)); - outputSignal = createAndAddSignal(config.signalName, dataDescBdr.build()); + outputSignal = createAndAddSignal(DEFAULT_VALUE_SIGNAL_LOCAL_ID, dataDescBdr.build()); + outputSignal.setName(config.valueFieldName); if (config.tsFieldName != "") { outputSignal.setDomainSignal(createDomainSignal()); @@ -211,9 +197,9 @@ void MqttJsonDecoderFbImpl::createSignal() void MqttJsonDecoderFbImpl::reconfigureSignal(const FbConfig& prevConfig) { auto lock = this->getRecursiveConfigLock(); - if (prevConfig.signalName != config.signalName) + if (prevConfig.valueFieldName != config.valueFieldName) { - outputSignal.setName(config.signalName); + outputSignal.setName(config.valueFieldName); } if (prevConfig.valueFieldName != config.valueFieldName || prevConfig.unitSymbol != config.unitSymbol) @@ -260,7 +246,7 @@ SignalConfigPtr MqttJsonDecoderFbImpl::createDomainSignal() .setOrigin(getEpoch()) .setName("Time") .build(); - outputDomainSignal = createAndAddSignal("mqttTimestampSignal", domainSignalDsc, false); + outputDomainSignal = createAndAddSignal(DEFAULT_TS_SIGNAL_LOCAL_ID, domainSignalDsc, false); return outputDomainSignal; } diff --git a/modules/mqtt_streaming_module/src/mqtt_publisher_fb_impl.cpp b/modules/mqtt_streaming_module/src/mqtt_publisher_fb_impl.cpp new file mode 100644 index 0000000..b0d0b27 --- /dev/null +++ b/modules/mqtt_streaming_module/src/mqtt_publisher_fb_impl.cpp @@ -0,0 +1,664 @@ +#include "mqtt_streaming_module/constants.h" +#include "mqtt_streaming_module/handler_factory.h" +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE + +std::atomic MqttPublisherFbImpl::localIndex = 0; + +MqttPublisherFbImpl::MqttPublisherFbImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const FunctionBlockTypePtr& type, + std::shared_ptr mqttClient, + const PropertyObjectPtr& config) + : FunctionBlock(type, ctx, parent, generateLocalId()), + mqttClient(mqttClient), + jsonDataWorker(loggerComponent), + running(true), + hasSignalError(false), + signalDescriptorChanged(false), + signalAttributeChanged(false), + hasSettingError(false), + signalStatus(MQTT_PUB_FB_SIG_STATUS_TYPE, + MQTT_PUB_FB_SIG_STATUS_NAME, + statusContainer, + signalStatusMap, + SignalStatus::NotConnected, + context.getTypeManager()), + publishingStatus(MQTT_PUB_FB_PUB_STATUS_TYPE, + MQTT_PUB_FB_PUB_STATUS_NAME, + statusContainer, + publishingStatusMap, + PublishingStatus::Ok, + context.getTypeManager()), + settingStatus(MQTT_PUB_FB_SET_STATUS_TYPE, + MQTT_PUB_FB_SET_STATUS_NAME, + statusContainer, + settingStatusMap, + SettingStatus::Valid, + context.getTypeManager()), + hasSkippedMsg(false) +{ + initComponentStatus(); + if (config.assigned()) + initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); + else + initProperties(type.createDefaultConfig()); + + handler = HandlerFactory::create(this->template getWeakRefInternal(), this->config, globalId.toStdString()); + updatePortsAndSignals(true); + validateInputPorts(); + updateSchema(); + updateStatuses(); + runReaderThread(); +} + +MqttPublisherFbImpl::~MqttPublisherFbImpl() +{ + if (readerThread.joinable()) + { + running = false; + readerThread.join(); + } +} + +void MqttPublisherFbImpl::removed() +{ + if (readerThread.joinable()) + { + running = false; + readerThread.join(); + } + clearCoreEventCallbacks(signalMap); + signalMap.clear(); + { + auto lock = this->getRecursiveConfigLock(); + auto lockProcessing = std::scoped_lock(processingMutex); + handler = nullptr; + } + FunctionBlock::removed(); +} + +FunctionBlockTypePtr MqttPublisherFbImpl::CreateType() +{ + auto defaultConfig = PropertyObject(); + { + auto builder = + SelectionPropertyBuilder(PROPERTY_NAME_PUB_TOPIC_MODE, List("TopicPerSignal", "SingleTopic"), 0) + .setDescription( + "Selects whether to publish all signals to separate MQTT topics (one per signal, TopicPerSignal mode) or to a single " + "topic (SingleTopic mode), one for all signals. Choose 0 for TopicPerSignal mode, 1 for SingleTopic mode. By " + "default it is set to TopicPerSignal mode."); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = + StringPropertyBuilder(PROPERTY_NAME_PUB_TOPIC_NAME, "") + .setDescription( + "Topic name for publishing in SingleTopic mode. If left empty, the Publisher's Global ID is used as the topic name.") + .setVisible(EvalValue(std::string("$") + PROPERTY_NAME_PUB_TOPIC_MODE + " == 1")); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = SelectionPropertyBuilder(PROPERTY_NAME_PUB_VALUE_FIELD_NAME, List("GlobalID", "LocalID", "Name"), 0) + .setDescription("Describes how to name a JSON value field. By default it is set to GlobalID."); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = + BoolPropertyBuilder(PROPERTY_NAME_PUB_GROUP_VALUES, False) + .setDescription( + "Enables the use of a sample pack for a signal. By default it is set to false."); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = IntPropertyBuilder(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE, DEFAULT_PUB_PACK_SIZE) + .setMinValue(1) + .setVisible(EvalValue(std::string("($") + PROPERTY_NAME_PUB_GROUP_VALUES + ")")) + .setDescription(fmt::format("Set the size of the sample pack when publishing grouped values. " + "By default it is set to {}.", + DEFAULT_PUB_PACK_SIZE)); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = + SelectionPropertyBuilder(PROPERTY_NAME_PUB_QOS, List(0, 1, 2), DEFAULT_PUB_QOS) + .setDescription( + fmt::format("MQTT Quality of Service level for published messages. It can be 0 (at most once), 1 (at least once), or 2 " + "(exactly once). By default it is set to {}.", + DEFAULT_PUB_QOS)); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = BoolPropertyBuilder(PROPERTY_NAME_PUB_PREVIEW_SIGNAL, False) + .setDescription("Enables previewing of the publishing data in the function block's output signal. " + "By default it is set to false."); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = + IntPropertyBuilder(PROPERTY_NAME_PUB_READ_PERIOD, DEFAULT_PUB_READ_PERIOD) + .setMinValue(0) + .setUnit(Unit("ms")) + .setDescription(fmt::format("Polling period in milliseconds, which specifies how often the server calls an internal reader to " + "collect and publish the connected signals’ data to an MQTT broker. By default it is set to {} ms.", + DEFAULT_PUB_READ_PERIOD)); + defaultConfig.addProperty(builder.build()); + } + const auto fbType = FunctionBlockType(PUB_FB_NAME, + PUB_FB_NAME, + "The Publisher function block allows converting openDAQ signal samples into JSON messages and " + "publishing them to MQTT topics in different ways.", + defaultConfig); + return fbType; +} + +PublisherFbConfig MqttPublisherFbImpl::getFbConfig() const +{ + return config; +} + +void MqttPublisherFbImpl::onConnected(const InputPortPtr& inputPort) +{ + auto lock = this->getRecursiveConfigLock(); + auto lockProcessing = std::scoped_lock(processingMutex); + + updatePortsAndSignals(true); + LOG_T("Connected to port {}", inputPort.getLocalId()); + validateInputPorts(); + updateTopics(); + updateStatuses(); +} + +void MqttPublisherFbImpl::onDisconnected(const InputPortPtr& inputPort) +{ + auto lock = this->getRecursiveConfigLock(); + auto lockProcessing = std::scoped_lock(processingMutex); + + updatePortsAndSignals(true); + LOG_T("Disconnected from port {}", inputPort.getLocalId()); + validateInputPorts(); + updateTopics(); + updateStatuses(); +} + +void MqttPublisherFbImpl::propertyChanged() +{ + auto lock = this->getRecursiveConfigLock(); + auto lockProcessing = std::scoped_lock(processingMutex); + + readProperties(); + handler = HandlerFactory::create(this->template getWeakRefInternal(), this->config, globalId.toStdString()); + updatePortsAndSignals(false); + validateInputPorts(); + updateTopics(); + updateSchema(); + updateStatuses(); +} + +void MqttPublisherFbImpl::updatePortsAndSignals(bool reassignPorts) +{ + if (reassignPorts) + { + clearPorts(); + } + if (config.enablePreview) + { + if (config.topicMode == TopicMode::Single && !commonPreviewSignal.assigned()) + { + const auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::String).build(); + commonPreviewSignal = createAndAddSignal(fmt::format("{}{}", PUB_PREVIEW_SIGNAL_NAME, "Common"), signalDsc); + } + } + for (auto it = signalContexts.begin(); it != signalContexts.end();) + { + it->inputPort.setListener(this->template borrowPtr()); + if (it->inputPort.getSignal().assigned()) + { + if (config.enablePreview) + { + if (config.topicMode == TopicMode::Single) + { + if (it->previewSignal != commonPreviewSignal) + { + if (it->previewSignal.assigned()) + removeSignal(it->previewSignal); + it->previewSignal = commonPreviewSignal; + } + } + else if (config.topicMode == TopicMode::PerSignal) + { + if (!it->previewSignal.assigned() || (commonPreviewSignal.assigned() && commonPreviewSignal == it->previewSignal)) + { + const auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::String).build(); + it->previewSignal = createAndAddSignal(fmt::format("{}{}", PUB_PREVIEW_SIGNAL_NAME, size_t(it->getIndex())), signalDsc); + } + } + } + else + { + if (it->previewSignal.assigned()) + { + if (!commonPreviewSignal.assigned() || commonPreviewSignal != it->previewSignal) + { + removeSignal(it->previewSignal); + } + it->previewSignal = nullptr; + } + } + } + ++it; + } + if (commonPreviewSignal.assigned() && (!config.enablePreview || config.topicMode == TopicMode::PerSignal)) + { + removeSignal(commonPreviewSignal); + commonPreviewSignal = nullptr; + } + + updateCoreEventCallbacks(); +} + +void MqttPublisherFbImpl::clearPorts() +{ + for (auto it = signalContexts.begin(); it != signalContexts.end();) + { + if (!it->inputPort.getSignal().assigned()) + { + + if (it->previewSignal.assigned() && (!commonPreviewSignal.assigned() || commonPreviewSignal != it->previewSignal)) + { + removeSignal(it->previewSignal); + } + removeInputPort(it->inputPort); + it = signalContexts.erase(it); + } + else + { + ++it; + } + } + size_t inputPortCount = 0; + auto it = signalContexts.crbegin(); + if (it != signalContexts.crend()) + inputPortCount = it->getIndex() + 1; + + const auto inputPort = createAndAddInputPort(fmt::format("Input{}", size_t(inputPortCount)), PacketReadyNotification::SameThread); + signalContexts.emplace_back(size_t(inputPortCount), inputPort, nullptr); + +} + +void MqttPublisherFbImpl::updateCoreEventCallbacks() +{ + auto signalMapCopy = std::move(signalMap); + signalMap.clear(); + for (auto it = signalContexts.cbegin(); it != signalContexts.cend();) + { + auto sig = it->inputPort.getSignal(); + if (sig.assigned()) + { + if (signalMapCopy.find(sig.getGlobalId().toStdString()) == signalMapCopy.end()) + { + sig.asPtr().getOnComponentCoreEvent() += event(&MqttPublisherFbImpl::coreEventCallback); + signalMap.insert(std::pair(sig.getGlobalId().toStdString(), WeakRefPtr(sig))); + } + else + { + signalMap.insert(std::move(signalMapCopy.extract(sig.getGlobalId().toStdString()))); + } + } + ++it; + } + clearCoreEventCallbacks(signalMapCopy); +} + +void MqttPublisherFbImpl::clearCoreEventCallbacks(const std::unordered_map>& signalMap) +{ + for (const auto& [_, weakSig] : signalMap) + { + auto sig = weakSig.getRef(); + if (sig.assigned()) + sig.asPtr().getOnComponentCoreEvent() -= event(&MqttPublisherFbImpl::coreEventCallback); + } +} + +void MqttPublisherFbImpl::updateStatuses() +{ + auto buildErrorString = [this](const std::vector& errors) + { + std::string allMessages; + for (const auto& msg : errors) + { + allMessages += msg + "; "; + } + return allMessages; + }; + + if (hasSignalError) + { + signalStatus.setStatus(SignalStatus::Invalid, buildErrorString(signalErrors)); + } + else if (signalContexts.size() == 1) // no one input port is connected + { + signalStatus.setStatus(SignalStatus::NotConnected); + } + else + { + signalStatus.setStatus(SignalStatus::Valid); + } + + if (hasSettingError) + { + settingStatus.setStatus(SettingStatus::Invalid, buildErrorString(settingErrors)); + } + else + { + settingStatus.setStatus(SettingStatus::Valid); + } + + if (hasSkippedMsg) + { + publishingStatus.setStatus(PublishingStatus::SampleSkipped, buildPublishingStatusMessage()); + } + else + { + publishingStatus.setStatus(PublishingStatus::Ok); + } + updateComponentStatus(); +} + +void MqttPublisherFbImpl::validateInputPorts() +{ + hasSkippedMsg = false; + if (signalContexts.size() == 1) // no one input port is connected + { + hasSignalError = false; + } + else + { + const auto status = handler->validateSignalContexts(signalContexts); + hasSignalError = !status.success; + signalErrors = std::move(status.messages); + if (status.success) + handler->signalListChanged(signalContexts); + } +} + +void MqttPublisherFbImpl::updateTopics() +{ + const auto topics = handler->getTopics(signalContexts); + objPtr.getProperty(PROPERTY_NAME_PUB_TOPICS).asPtr().setValueProtected(topics); +} + +void MqttPublisherFbImpl::updateSchema() +{ + const auto schema = handler->getSchema(); + objPtr.getProperty(PROPERTY_NAME_PUB_SCHEMA).asPtr().setValueProtected(String(schema)); +} + +void MqttPublisherFbImpl::initProperties(const PropertyObjectPtr& config) +{ + for (const auto& prop : config.getAllProperties()) + { + const auto propName = prop.getName(); + if (!objPtr.hasProperty(propName)) + { + if (const auto internalProp = prop.asPtrOrNull(true); internalProp.assigned()) + { + objPtr.addProperty(internalProp.clone()); + objPtr.setPropertyValue(propName, prop.getValue()); + objPtr.getOnPropertyValueWrite(prop.getName()) += [this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) + { propertyChanged(); }; + } + } + else + { + objPtr.setPropertyValue(propName, prop.getValue()); + } + + if (propName == PROPERTY_NAME_PUB_TOPIC_NAME) + { + auto builder = ListPropertyBuilder(PROPERTY_NAME_PUB_TOPICS, List()) + .setReadOnly(true) + .setVisible(EvalValue(std::string("$") + PROPERTY_NAME_PUB_TOPIC_MODE + " == 0")) + .setDescription("List of currently used MQTT topics for publishing in TopicPerSignal mode."); + + objPtr.addProperty(builder.build()); + } + } + { + auto builder = + StringPropertyBuilder(PROPERTY_NAME_PUB_SCHEMA, "").setReadOnly(true).setDescription("Publishing JSON schema."); + + objPtr.addProperty(builder.build()); + } + readProperties(); +} + +void MqttPublisherFbImpl::readProperties() +{ + int tmpTopicMode = readProperty(PROPERTY_NAME_PUB_TOPIC_MODE, 0); + + config.groupValues = readProperty(PROPERTY_NAME_PUB_GROUP_VALUES, false); + int tmpValueFieldName = (readProperty(PROPERTY_NAME_PUB_VALUE_FIELD_NAME, 0)); + config.groupValuesPackSize = readProperty(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE, DEFAULT_PUB_PACK_SIZE); + config.qos = readProperty(PROPERTY_NAME_PUB_QOS, DEFAULT_PUB_QOS); + config.periodMs = readProperty(PROPERTY_NAME_PUB_READ_PERIOD, DEFAULT_PUB_READ_PERIOD); + config.topicName = readProperty(PROPERTY_NAME_PUB_TOPIC_NAME, globalId.toStdString()); + config.enablePreview = readProperty(PROPERTY_NAME_PUB_PREVIEW_SIGNAL, false); + settingErrors.clear(); + hasSettingError = false; + + if (tmpValueFieldName < static_cast(SignalValueJSONKey::_count) && tmpValueFieldName >= 0) + { + config.valueFieldName = static_cast(tmpValueFieldName); + } + else + { + config.valueFieldName = SignalValueJSONKey::GlobalID; + hasSettingError = true; + settingErrors.push_back(fmt::format("{} property has invalid value.", PROPERTY_NAME_PUB_VALUE_FIELD_NAME)); + } + + if (tmpTopicMode < static_cast(TopicMode::_count) && tmpTopicMode >= 0) + { + config.topicMode = static_cast(tmpTopicMode); + } + else + { + config.topicMode = TopicMode::PerSignal; + hasSettingError = true; + settingErrors.push_back("Topic mode has invalid value."); + } + + if (config.topicName.empty()) + { + config.topicName = globalId.toStdString(); + hasEmptyTopic = (config.topicMode == TopicMode::Single); + } + else + { + hasEmptyTopic = false; + } + + if (config.topicMode == TopicMode::Single) + { + auto result = mqtt::MqttDataWrapper::validateTopic(config.topicName, loggerComponent); + hasSettingError = !result.success; + settingErrors.push_back(std::move(result.msg)); + } + if (config.qos < 0 || config.qos > 2) + { + config.qos = DEFAULT_PUB_QOS; + hasSettingError = true; + settingErrors.push_back("QoS level must be 0, 1, or 2."); + } + if (config.periodMs < 0) + { + config.periodMs = DEFAULT_PUB_READ_PERIOD; + hasSettingError = true; + settingErrors.push_back("Reader period must be non-negative."); + } +} + +template +retT MqttPublisherFbImpl::readProperty(const std::string& propertyName, const retT defaultValue) +{ + retT returnValue{}; + if (objPtr.hasProperty(propertyName)) + { + auto property = objPtr.getPropertyValue(propertyName).asPtrOrNull(); + if (property.assigned()) + { + returnValue = property.getValue(defaultValue); + } + } + return returnValue; +} + +void MqttPublisherFbImpl::runReaderThread() +{ + LOGP_D("Using separate thread for rendering") + + readerThread = std::thread([this] { readerLoop(); }); +} + +void MqttPublisherFbImpl::readerLoop() +{ + while (running) + { + { + MqttData msgs; + auto lockProcessing = std::scoped_lock(processingMutex); + if ((hasSignalError && signalDescriptorChanged) || signalAttributeChanged) + { + validateInputPorts(); + updateStatuses(); + signalDescriptorChanged = false; + signalAttributeChanged = false; + } + if (hasSignalError == false && hasSettingError == false) + { + msgs = handler->processSignalContexts(signalContexts); + if (msgs.needRevalidation) + { + validateInputPorts(); + updateStatuses(); + } + } + sendMessages(msgs); + updatePublishingStatus(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(config.periodMs)); + } +} + +void MqttPublisherFbImpl::sendMessages(const MqttData& data) +{ + for (const auto& [signal, topic, msg] : data.data) + { + if (signal.assigned()) + { + const auto outputPacket = BinaryDataPacket(nullptr, signal.getDescriptor(), msg.size()); + memcpy(outputPacket.getData(), msg.data(), msg.size()); + signal.sendPacket(outputPacket); + } + auto status = mqttClient->publish(topic, (void*)msg.c_str(), msg.length(), config.qos); + if (!status.success) + { + hasSkippedMsg = true; + lastSkippedReason = std::move(status.msg); + } + } +} + +std::string MqttPublisherFbImpl::generateLocalId() +{ + return std::string(MQTT_LOCAL_PUB_FB_ID_PREFIX + std::to_string(localIndex++)); +} + +inline void MqttPublisherFbImpl::coreEventCallback(ComponentPtr& sender, CoreEventArgsPtr& eventArgs) +{ + const auto sig = sender.asPtrOrNull(); + if (sig == nullptr) + return; + auto lockProcessing = std::scoped_lock(processingMutex); + const auto it = signalMap.find(sig.getGlobalId()); + if (it != signalMap.end()) + { + if (eventArgs.getEventId() == static_cast(CoreEventId::DataDescriptorChanged) && hasSignalError) + signalDescriptorChanged = true; + else if (eventArgs.getEventId() == static_cast(CoreEventId::AttributeChanged) && eventArgs.getParameters().hasKey("Name")) + signalAttributeChanged = true; + } +}; + +void MqttPublisherFbImpl::updateComponentStatus() +{ + std::string componentMsg; + if (hasSignalError) + { + componentMsg.append("Signal status is invalid: "); + for (const auto& m : signalErrors) + componentMsg.append(m + "; "); + } + else if (signalContexts.size() == 1) // no one input port is connected + { + componentMsg.append("No input ports are connected! "); + } + + if (hasSettingError) + { + componentMsg.append("Settings are invalid: "); + for (const auto& m : settingErrors) + componentMsg.append(m + "; "); + } + + if (hasEmptyTopic) + { + componentMsg.append("Topic property is empty! Using FB Global ID as topic name. "); + } + + if (hasSkippedMsg) + { + componentMsg.append("Publishing warning: "); + componentMsg.append(buildPublishingStatusMessage()); + } + + if (hasSignalError || hasSettingError) + { + setComponentStatusWithMessage(ComponentStatus::Error, componentMsg); + } + else if (signalContexts.size() == 1 || hasEmptyTopic || hasSkippedMsg) + { + setComponentStatusWithMessage(ComponentStatus::Warning, componentMsg); + } + else + { + setComponentStatus(ComponentStatus::Ok); + } +} + +void MqttPublisherFbImpl::updatePublishingStatus() +{ + bool changed = false; + if (hasSkippedMsg) + { + changed = publishingStatus.setStatus(PublishingStatus::SampleSkipped, buildPublishingStatusMessage()); + } + else + { + changed = publishingStatus.setStatus(PublishingStatus::Ok); + } + if (changed) + updateComponentStatus(); +} + +inline std::string MqttPublisherFbImpl::buildPublishingStatusMessage() +{ + return fmt::format("Some sample were not published! Last reason is \"{}\"", lastSkippedReason); +} +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/src/mqtt_streaming_module_impl.cpp b/modules/mqtt_streaming_module/src/mqtt_streaming_module_impl.cpp similarity index 90% rename from mqtt_streaming_module/src/mqtt_streaming_module_impl.cpp rename to modules/mqtt_streaming_module/src/mqtt_streaming_module_impl.cpp index e73122c..1842636 100644 --- a/mqtt_streaming_module/src/mqtt_streaming_module_impl.cpp +++ b/modules/mqtt_streaming_module/src/mqtt_streaming_module_impl.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -13,8 +13,6 @@ #include #include -#include - BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE MqttStreamingModule::MqttStreamingModule(ContextPtr context) @@ -46,7 +44,7 @@ MqttStreamingModule::onCreateFunctionBlock(const StringPtr& id, if (!context.assigned()) DAQ_THROW_EXCEPTION(InvalidParameterException, "Context is not available."); - FunctionBlockPtr fb = createWithImplementation(context, parent, config); + FunctionBlockPtr fb = createWithImplementation(context, parent, config); LOG_I("MQTT function block (GlobalId: {}) created", fb.getGlobalId()); @@ -55,7 +53,7 @@ MqttStreamingModule::onCreateFunctionBlock(const StringPtr& id, FunctionBlockTypePtr MqttStreamingModule::createFbType() { - return MqttRootFbImpl::CreateType(); + return MqttClientFbImpl::CreateType(); } END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/modules/mqtt_streaming_module/src/mqtt_subscriber_fb_impl.cpp b/modules/mqtt_streaming_module/src/mqtt_subscriber_fb_impl.cpp new file mode 100644 index 0000000..527b6fa --- /dev/null +++ b/modules/mqtt_streaming_module/src/mqtt_subscriber_fb_impl.cpp @@ -0,0 +1,526 @@ +#include "mqtt_streaming_module/constants.h" +#include +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE + +constexpr int MQTT_FB_UNSUBSCRIBE_TOUT = 3000; + +std::atomic MqttSubscriberFbImpl::localIndex = 0; + +MqttSubscriberFbImpl::MqttSubscriberFbImpl(const ContextPtr& ctx, + const ComponentPtr& parent, + const FunctionBlockTypePtr& type, + std::shared_ptr subscriber, + const PropertyObjectPtr& config) + : FunctionBlock(type, ctx, parent, generateLocalId()), + subscriber(subscriber), + jsonDataWorker(loggerComponent), + waitingForData(false) +{ + initComponentStatus(); + initNestedFbTypes(); + if (config.assigned()) + initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); + else + initProperties(type.createDefaultConfig()); + if (topicForSubscribing.empty()) + { + readJsonConfig(); + } + createSignals(); + subscribeToTopic(); +} + +MqttSubscriberFbImpl::~MqttSubscriberFbImpl() +{ + unsubscribeFromTopic(); +} + +void MqttSubscriberFbImpl::removed() +{ + FunctionBlock::removed(); + unsubscribeFromTopic(); +} + +void MqttSubscriberFbImpl::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg) +{ + processMessage(msg); +} + +void MqttSubscriberFbImpl::initProperties(const PropertyObjectPtr& config) +{ + for (const auto& prop : config.getAllProperties()) + { + const auto propName = prop.getName(); + if (propName == PROPERTY_NAME_SUB_JSON_CONFIG || propName == PROPERTY_NAME_SUB_JSON_CONFIG_FILE) + { + if (!objPtr.hasProperty(propName)) + { + auto propClone = PropertyBuilder(prop.getName()) + .setValueType(prop.getValueType()) + .setDescription(prop.getDescription()) + .setDefaultValue(prop.getValue()) + .setVisible(false) + .setReadOnly(true) + .build(); + objPtr.addProperty(propClone); + } + } + else + { + if (!objPtr.hasProperty(propName)) + { + if (const auto internalProp = prop.asPtrOrNull(true); internalProp.assigned()) + { + objPtr.addProperty(internalProp.clone()); + objPtr.setPropertyValue(propName, prop.getValue()); + objPtr.getOnPropertyValueWrite(prop.getName()) += [this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) + { propertyChanged(); }; + } + } + else + { + objPtr.setPropertyValue(propName, prop.getValue()); + } + } + } + readProperties(); +} + +FunctionBlockTypePtr MqttSubscriberFbImpl::CreateType() +{ + auto defaultConfig = PropertyObject(); + { + auto builder = + StringPropertyBuilder(PROPERTY_NAME_SUB_TOPIC, String("")).setDescription("An MQTT topic to subscribe to for receiving data."); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = + SelectionPropertyBuilder(PROPERTY_NAME_PUB_QOS, List(0, 1, 2), DEFAULT_PUB_QOS) + .setDescription( + fmt::format("MQTT Quality of Service level for subscribing. It can be 0 (at most once), 1 (at least once), or 2 " + "(exactly once). By default it is set to {}.", + DEFAULT_SUB_QOS)); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = BoolPropertyBuilder(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, False) + .setDescription("Enables previewing of the subscribed signal data in the function block's output signal. " + "By default it is set to false."); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = BoolPropertyBuilder(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING, False) + .setVisible(EvalValue(std::string("$") + PROPERTY_NAME_SUB_PREVIEW_SIGNAL)) + .setDescription("Specifies whether the preview signal data type is string. " + "By default it is set to false."); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = + StringPropertyBuilder(PROPERTY_NAME_SUB_JSON_CONFIG, String("")) + .setDescription("JSON configuration string that defines an MQTT topic and corresponding signals to subscribe to."); + defaultConfig.addProperty(builder.build()); + } + { + auto builder = StringPropertyBuilder(PROPERTY_NAME_SUB_JSON_CONFIG_FILE, String("")) + .setDescription("Path to file where the JSON configuration string is stored."); + defaultConfig.addProperty(builder.build()); + } + const auto fbType = + FunctionBlockType(SUB_FB_NAME, + SUB_FB_NAME, + "The subscriber MQTT function block allows subscribing to an MQTT topic and converting MQTT payloads into " + "openDAQ signal binary data samples.", + defaultConfig); + return fbType; +} + +std::string MqttSubscriberFbImpl::generateLocalId() +{ + return std::string(MQTT_LOCAL_SUB_FB_ID_PREFIX + std::to_string(localIndex++)); +} + +void MqttSubscriberFbImpl::initNestedFbTypes() +{ + nestedFbTypes = Dict(); + // Add a function block type for manual JSON configuration + { + const auto fbType = MqttJsonDecoderFbImpl::CreateType(); + nestedFbTypes.set(fbType.getId(), fbType); + } +} + +void MqttSubscriberFbImpl::readProperties() +{ + auto lock = this->getRecursiveConfigLock(); + topicForSubscribing.clear(); + std::string topic; + if (objPtr.hasProperty(PROPERTY_NAME_SUB_TOPIC)) + { + auto topicStr = objPtr.getPropertyValue(PROPERTY_NAME_SUB_TOPIC).asPtrOrNull(); + if (topicStr.assigned()) + topic = topicStr.toStdString(); + } + setTopic(topic); + + if (objPtr.hasProperty(PROPERTY_NAME_SUB_QOS)) + { + auto qosProp = objPtr.getPropertyValue(PROPERTY_NAME_SUB_QOS).asPtrOrNull(); + if (qosProp.assigned()) + { + const auto qos = qosProp.getValue(DEFAULT_SUB_QOS); + this->qos = (qos < 0 || qos > 2) ? DEFAULT_SUB_QOS : qos; + } + } + if (objPtr.hasProperty(PROPERTY_NAME_SUB_PREVIEW_SIGNAL)) + { + auto previewProp = objPtr.getPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL).asPtrOrNull(); + if (previewProp.assigned()) + { + this->enablePreview = previewProp.getValue(False); + } + } + + if (objPtr.hasProperty(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING)) + { + auto isStringProp = objPtr.getPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING).asPtrOrNull(); + if (isStringProp.assigned()) + { + this->previewIsString = isStringProp.getValue(False); + } + } +} + +void MqttSubscriberFbImpl::readJsonConfig() +{ + bool hasJsonConfig = false; + if (objPtr.hasProperty(PROPERTY_NAME_SUB_JSON_CONFIG)) + { + const auto signalConfig = objPtr.getPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG).asPtrOrNull(); + if (signalConfig.assigned()) + { + if (!signalConfig.toStdString().empty()) + { + hasJsonConfig = true; + setJsonConfig(signalConfig.toStdString()); + } + } + } + if (hasJsonConfig == false && objPtr.hasProperty(PROPERTY_NAME_SUB_JSON_CONFIG_FILE)) + { + const auto configPath = objPtr.getPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG_FILE).asPtrOrNull(); + if (configPath.assigned()) + { + if (!configPath.toStdString().empty()) + { + auto res = readFileToString(configPath.toStdString()); + if (res.first) + { + hasJsonConfig = true; + setJsonConfig(res.second); + } + else + { + auto msg = fmt::format("Failed to read JSON config from file: {}", configPath.toStdString()); + LOG_W("{}", msg); + setComponentStatusWithMessage(ComponentStatus::Error, msg); + } + } + } + } +} + +std::pair MqttSubscriberFbImpl::readFileToString(const std::string& filePath) +{ + std::ifstream file(filePath); + if (!file) + return std::pair(false, ""); + + std::ostringstream buffer; + buffer << file.rdbuf(); // Read the entire file buffer + return std::pair(true, buffer.str()); +} + +void MqttSubscriberFbImpl::setJsonConfig(const std::string config) +{ + jsonDataWorker.setConfig(config); + auto result = jsonDataWorker.isJsonValid(); + if (result.success) + { + auto topic = jsonDataWorker.extractTopic(); + result.success = setTopic(topic); + if (result.success) + { + { + auto event = objPtr.getOnPropertyValueWrite(PROPERTY_NAME_SUB_TOPIC); + event.mute(); + objPtr.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, String(topic)); + event.unmute(); + } + if (const auto signalDscs = jsonDataWorker.extractDescription(); !signalDscs.empty()) + { + auto fbConfig = MqttJsonDecoderFbImpl::CreateType().createDefaultConfig(); + for (const auto& [signalName, descriptor] : signalDscs) + { + LOG_I("Creating a decoder FB for the signal \"{}\":", signalName); + fbConfig.setPropertyValue(PROPERTY_NAME_DEC_VALUE_NAME, descriptor.valueFieldName); + fbConfig.setPropertyValue(PROPERTY_NAME_DEC_TS_NAME, descriptor.tsFieldName); + if (descriptor.unit.assigned()) + fbConfig.setPropertyValue(PROPERTY_NAME_DEC_UNIT, descriptor.unit.getSymbol()); + MqttSubscriberFbImpl::onAddFunctionBlock(JSON_DECODER_FB_NAME, fbConfig); + } + } + } + else + { + result.msg = "Failed to set topic from JSON config."; + } + } + if (!result.success) + { + result.msg = fmt::format("JSON config is wrong! {}", result.msg); + LOG_W("{}", result.msg); + setComponentStatusWithMessage(ComponentStatus::Error, result.msg); + } +} + +void MqttSubscriberFbImpl::propertyChanged() +{ + auto lock = this->getRecursiveConfigLock(); + auto result = unsubscribeFromTopic(); + if (result.success == false) + { + LOG_W("Failed to unsubscribe from the previous topic before subscribing to a new one; reason: {}", result.msg); + return; + } + readProperties(); + if (enablePreview) + { + if (!outputSignal.assigned()) + { + createSignals(); + } + else if ((outputSignal.getDescriptor().getSampleType() == SampleType::String) != previewIsString) + { + outputSignal.setDescriptor(DataDescriptorBuilderCopy(outputSignal.getDescriptor()) + .setSampleType(previewIsString ? SampleType::String : SampleType::Binary) + .build()); + } + } + else + { + if (outputSignal.assigned()) + { + removeSignal(outputSignal); + outputSignal = nullptr; + } + } + result = subscribeToTopic(); +} + +bool MqttSubscriberFbImpl::setTopic(std::string topic) +{ + const auto validationStatus = mqtt::MqttDataWrapper::validateTopic(topic, loggerComponent); + if (validationStatus.success) + { + LOG_I("An MQTT topic: {}", topic); + topicForSubscribing = std::move(topic); + setComponentStatusWithMessage(ComponentStatus::Ok, "Waiting for data for the topic: " + topicForSubscribing); + waitingForData = true; + } + else + { + setComponentStatusWithMessage(ComponentStatus::Error, "Invalid topic name: " + validationStatus.msg); + waitingForData = false; + } + return validationStatus.success; +} + +DictPtr MqttSubscriberFbImpl::onGetAvailableFunctionBlockTypes() +{ + return nestedFbTypes; +} + +FunctionBlockPtr MqttSubscriberFbImpl::onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) +{ + + FunctionBlockPtr nestedFunctionBlock; + if (nestedFbTypes.hasKey(typeId)) + { + auto fbTypePtr = nestedFbTypes.getOrDefault(typeId); + if (fbTypePtr.getName() == JSON_DECODER_FB_NAME) + { + nestedFunctionBlock = + createWithImplementation(context, functionBlocks, fbTypePtr, config); + } + } + if (nestedFunctionBlock.assigned()) + { + addNestedFunctionBlock(nestedFunctionBlock); + { + auto lock = this->getAcquisitionLock2(); + nestedFunctionBlocks.push_back(nestedFunctionBlock); + } + } + else + { + DAQ_THROW_EXCEPTION(NotFoundException, "Function block type is not available: " + typeId.toStdString()); + } + return nestedFunctionBlock; +} + +void MqttSubscriberFbImpl::onRemoveFunctionBlock(const FunctionBlockPtr& functionBlock) +{ + { + auto lock = this->getAcquisitionLock2(); + auto it = std::find_if(nestedFunctionBlocks.begin(), + nestedFunctionBlocks.end(), + [&functionBlock](const FunctionBlockPtr& fb) { return fb.getObject() == functionBlock.getObject(); }); + + if (it != nestedFunctionBlocks.end()) + { + nestedFunctionBlocks.erase(it); + } + } + FunctionBlockImpl::onRemoveFunctionBlock(functionBlock); +} + +void MqttSubscriberFbImpl::processMessage(const mqtt::MqttMessage& msg) +{ + if (topicForSubscribing == msg.getTopic()) + { + if (waitingForData) + { + setComponentStatusWithMessage(ComponentStatus::Ok, "Data has been received"); + waitingForData = false; + } + + std::string jsonObjStr(msg.getData().begin(), msg.getData().end()); + auto acqlock = this->getAcquisitionLock2(); + + if (enablePreview) + { + const auto outputPacket = BinaryDataPacket(nullptr, outputSignal.getDescriptor(), msg.getData().size()); + memcpy(outputPacket.getData(), msg.getData().data(), msg.getData().size()); + outputSignal.sendPacket(outputPacket); + } + + for (const auto& fb : nestedFunctionBlocks) + { + if (fb.assigned()) + { + auto decoderFb = reinterpret_cast(*fb); + decoderFb->processMessage(jsonObjStr); + } + } + } +} + +void MqttSubscriberFbImpl::createSignals() +{ + auto lock = this->getRecursiveConfigLock(); // ??? + if (enablePreview) + { + const auto signalDsc = DataDescriptorBuilder().setSampleType(previewIsString ? SampleType::String : SampleType::Binary).build(); + outputSignal = createAndAddSignal(DEFAULT_VALUE_SIGNAL_LOCAL_ID, signalDsc); + } +} + +std::string MqttSubscriberFbImpl::getSubscribedTopic() const +{ + return topicForSubscribing; +} + +void MqttSubscriberFbImpl::clearSubscribedTopic() +{ + topicForSubscribing.clear(); +} + +MqttSubscriberFbImpl::CmdResult MqttSubscriberFbImpl::subscribeToTopic() +{ + CmdResult result{false}; + if (subscriber) + { + auto lambda = [this](const mqtt::MqttAsyncClient& client, mqtt::MqttMessage& msg) { this->onSignalsMessage(client, msg); }; + const auto topic = getSubscribedTopic(); + if (!topic.empty()) + { + LOG_I("Trying to subscribe to the topic : {}", topic); + subscriber->setMessageArrivedCb(topic, lambda); + if (auto subRes = subscriber->subscribe(topic, qos); subRes.success == false) + { + LOG_W("Failed to subscribe to the topic: \"{}\"; reason: {}", topic, subRes.msg); + setComponentStatusWithMessage(ComponentStatus::Error, "Some topics failed to subscribe! The reason: " + subRes.msg); + waitingForData = false; + result = {false, "Failed to subscribe to the topic: \"" + topic + "\"; reason: " + subRes.msg}; + } + else + { + // subscriber->subscribe(...) is asynchronous. It puts command in queue and returns immediately. + LOG_D("Trying to subscribe to the topic: {}", topic); + setComponentStatusWithMessage(ComponentStatus::Ok, "Waiting for data for the topic: " + topicForSubscribing); + waitingForData = true; + result = {true, "", result.token}; + } + } + else + { + result = {false, "Couldn't subscribe to an empty topic"}; + LOG_W("{}", result.msg); + } + } + else + { + const std::string msg = "MQTT subscriber client is not set!"; + setComponentStatusWithMessage(ComponentStatus::Error, msg); + result = {false, msg}; + } + return result; +} + +MqttSubscriberFbImpl::CmdResult MqttSubscriberFbImpl::unsubscribeFromTopic() +{ + CmdResult result{true}; + if (subscriber) + { + const auto topic = getSubscribedTopic(); + if (!topic.empty()) + { + subscriber->setMessageArrivedCb(topic, nullptr); + mqtt::CmdResult unsubRes = subscriber->unsubscribe(topic); + if (unsubRes.success) + unsubRes = subscriber->waitForCompletion(unsubRes.token, MQTT_FB_UNSUBSCRIBE_TOUT); + + if (unsubRes.success) + { + clearSubscribedTopic(); + LOG_I("The topic \'{}\' has been unsubscribed successfully", topic); + result = {true}; + } + else + { + const auto msg = fmt::format("Failed to unsubscribe from the topic \'{}\'; reason: {}", topic, unsubRes.msg); + LOG_W("{}", msg); + setComponentStatusWithMessage(ComponentStatus::Error, msg); + result = {false, msg}; + } + } + } + else + { + const std::string msg = "MQTT subscriber client is not set!"; + setComponentStatusWithMessage(ComponentStatus::Error, msg); + result = {false, msg}; + } + return result; +} + +END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/src/signal_arr_atomic_sample_handler.cpp b/modules/mqtt_streaming_module/src/signal_arr_atomic_sample_handler.cpp similarity index 84% rename from mqtt_streaming_module/src/signal_arr_atomic_sample_handler.cpp rename to modules/mqtt_streaming_module/src/signal_arr_atomic_sample_handler.cpp index 44882e8..6e3a31d 100644 --- a/mqtt_streaming_module/src/signal_arr_atomic_sample_handler.cpp +++ b/modules/mqtt_streaming_module/src/signal_arr_atomic_sample_handler.cpp @@ -10,8 +10,8 @@ BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE -SignalArrayAtomicSampleHandler::SignalArrayAtomicSampleHandler(bool useSignalNames, std::string topic) - : useSignalNames(useSignalNames), +SignalArrayAtomicSampleHandler::SignalArrayAtomicSampleHandler(WeakRefPtr parentFb, SignalValueJSONKey signalNamesMode, std::string topic) + : HandlerBase(parentFb, signalNamesMode), topic(topic) { } @@ -41,7 +41,7 @@ MqttData SignalArrayAtomicSampleHandler::processSignalContexts(std::vector allowedSampleTypes{SampleType::Float64, SampleType::Float32, - SampleType::Float32, - SampleType::Float64, SampleType::UInt8, SampleType::Int8, SampleType::UInt16, @@ -114,6 +112,7 @@ ProcedureStatus SignalArrayAtomicSampleHandler::validateSignalContexts(const std } } } + status.merge(HandlerBase::validateSignalContexts(signalContexts)); return status; } @@ -122,13 +121,24 @@ ProcedureStatus SignalArrayAtomicSampleHandler::signalListChanged(std::vector SignalArrayAtomicSampleHandler::getTopics(const std::vector& signalContexts) +{ + auto res = List(String(buildTopicName())); + return res; +} + +std::string SignalArrayAtomicSampleHandler::getSchema() +{ + return fmt::format("[{{\"{}\" : , \"timestamp\": }}, ..., {{\"{}\" : , \"timestamp\": }}]", buildValueFieldNameForSchema(signalNamesMode, "_0"), buildValueFieldNameForSchema(signalNamesMode, "_N")); +} + std::string SignalArrayAtomicSampleHandler::toString(const std::string valueFieldName, daq::DataPacketPtr packet) { std::string result; - std::string data = HandlerBase::toString(packet); + std::string data = HandlerBase::toString(packet, 0); if (auto domainPacket = packet.getDomainPacket(); domainPacket.assigned()) { - uint64_t ts = convertToEpoch(domainPacket); + uint64_t ts = convertToEpoch(domainPacket, 0); result = fmt::format("{{\"{}\" : {}, \"timestamp\": {}}}", valueFieldName, data, ts); } else diff --git a/mqtt_streaming_module/tests/CMakeLists.txt b/modules/mqtt_streaming_module/tests/CMakeLists.txt similarity index 89% rename from mqtt_streaming_module/tests/CMakeLists.txt rename to modules/mqtt_streaming_module/tests/CMakeLists.txt index 85c143d..cc6d46d 100644 --- a/mqtt_streaming_module/tests/CMakeLists.txt +++ b/modules/mqtt_streaming_module/tests/CMakeLists.txt @@ -2,9 +2,8 @@ set(MODULE_NAME mqtt_stream_module) set(TEST_APP test_${MODULE_NAME}) set(TEST_SOURCES test_mqtt_streaming_module.cpp - test_mqtt_root_fb.cpp - test_mqtt_raw_fb.cpp - test_mqtt_json_fb.cpp + test_mqtt_client_fb.cpp + test_mqtt_subscriber_fb.cpp test_mqtt_json_decoder_fb.cpp test_mqtt_publisher_fb.cpp test_daq_test_helper.h diff --git a/mqtt_streaming_module/tests/data/public-example0.json b/modules/mqtt_streaming_module/tests/data/public-example0.json similarity index 100% rename from mqtt_streaming_module/tests/data/public-example0.json rename to modules/mqtt_streaming_module/tests/data/public-example0.json diff --git a/mqtt_streaming_module/tests/data/public-example1.json b/modules/mqtt_streaming_module/tests/data/public-example1.json similarity index 100% rename from mqtt_streaming_module/tests/data/public-example1.json rename to modules/mqtt_streaming_module/tests/data/public-example1.json diff --git a/mqtt_streaming_module/tests/data/public-example2.json b/modules/mqtt_streaming_module/tests/data/public-example2.json similarity index 100% rename from mqtt_streaming_module/tests/data/public-example2.json rename to modules/mqtt_streaming_module/tests/data/public-example2.json diff --git a/mqtt_streaming_module/tests/data/public-example3.json b/modules/mqtt_streaming_module/tests/data/public-example3.json similarity index 100% rename from mqtt_streaming_module/tests/data/public-example3.json rename to modules/mqtt_streaming_module/tests/data/public-example3.json diff --git a/mqtt_streaming_module/tests/test_app.cpp b/modules/mqtt_streaming_module/tests/test_app.cpp similarity index 100% rename from mqtt_streaming_module/tests/test_app.cpp rename to modules/mqtt_streaming_module/tests/test_app.cpp diff --git a/mqtt_streaming_module/tests/test_daq_test_helper.h b/modules/mqtt_streaming_module/tests/test_daq_test_helper.h similarity index 57% rename from mqtt_streaming_module/tests/test_daq_test_helper.h rename to modules/mqtt_streaming_module/tests/test_daq_test_helper.h index d6e2f07..a3b1eec 100644 --- a/mqtt_streaming_module/tests/test_daq_test_helper.h +++ b/modules/mqtt_streaming_module/tests/test_daq_test_helper.h @@ -9,8 +9,8 @@ class DaqTestHelper { public: daq::InstancePtr daqInstance; - daq::FunctionBlockPtr rootMqttFb; - daq::FunctionBlockPtr jsonMqttFb; + daq::FunctionBlockPtr clientMqttFb; + daq::FunctionBlockPtr subMqttFb; void StartUp(std::string url = DEFAULT_BROKER_ADDRESS, uint16_t port = DEFAULT_PORT) { @@ -25,21 +25,21 @@ class DaqTestHelper return daqInstance; } - daq::FunctionBlockPtr DaqAddRootMqttFb(std::string url = DEFAULT_BROKER_ADDRESS, uint16_t port = DEFAULT_PORT) + daq::FunctionBlockPtr DaqAddClientMqttFb(std::string url = DEFAULT_BROKER_ADDRESS, uint16_t port = DEFAULT_PORT) { auto config = DaqMqttFbConfig(url, port); - rootMqttFb = daqInstance.addFunctionBlock(ROOT_FB_NAME, config); - return rootMqttFb; + clientMqttFb = daqInstance.addFunctionBlock(CLIENT_FB_NAME, config); + return clientMqttFb; } daq::FunctionBlockPtr DaqMqttFbInit(std::string url = DEFAULT_BROKER_ADDRESS, uint16_t port = DEFAULT_PORT) { - if (!rootMqttFb.assigned()) + if (!clientMqttFb.assigned()) { auto config = DaqMqttFbConfig(url, port); - rootMqttFb = daqInstance.addFunctionBlock(ROOT_FB_NAME, config); + clientMqttFb = daqInstance.addFunctionBlock(CLIENT_FB_NAME, config); } - return rootMqttFb; + return clientMqttFb; } daq::PropertyObjectPtr DaqMqttFbConfig(std::string url = DEFAULT_BROKER_ADDRESS, uint16_t port = DEFAULT_PORT) @@ -47,9 +47,9 @@ class DaqTestHelper daq::ModulePtr module; createModule(&module, daq::NullContext()); - auto config = module.getAvailableFunctionBlockTypes().get(daq::modules::mqtt_streaming_module::ROOT_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS, url); - config.setPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT, port); + auto config = module.getAvailableFunctionBlockTypes().get(daq::modules::mqtt_streaming_module::CLIENT_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_CLIENT_BROKER_ADDRESS, url); + config.setPropertyValue(PROPERTY_NAME_CLIENT_BROKER_PORT, port); return config; } @@ -60,11 +60,11 @@ class DaqTestHelper return module; } - daq::FunctionBlockPtr AddJsonFb(std::string topic = "") + daq::FunctionBlockPtr AddSubFb(std::string topic = "") { - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_TOPIC, daq::String(topic)); - jsonMqttFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config); - return jsonMqttFb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, daq::String(topic)); + subMqttFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config); + return subMqttFb; } }; diff --git a/mqtt_streaming_module/tests/test_data.h b/modules/mqtt_streaming_module/tests/test_data.h similarity index 100% rename from mqtt_streaming_module/tests/test_data.h rename to modules/mqtt_streaming_module/tests/test_data.h diff --git a/mqtt_streaming_module/tests/test_mqtt_root_fb.cpp b/modules/mqtt_streaming_module/tests/test_mqtt_client_fb.cpp similarity index 56% rename from mqtt_streaming_module/tests/test_mqtt_root_fb.cpp rename to modules/mqtt_streaming_module/tests/test_mqtt_client_fb.cpp index ef51020..96e642c 100644 --- a/mqtt_streaming_module/tests/test_mqtt_root_fb.cpp +++ b/modules/mqtt_streaming_module/tests/test_mqtt_client_fb.cpp @@ -22,36 +22,36 @@ TEST_F(MqttFbTest, DefaultMqttFbConfig) ASSERT_NO_THROW(types = module.getAvailableFunctionBlockTypes()); ASSERT_EQ(types.getCount(), 1u); - ASSERT_TRUE(types.hasKey(ROOT_FB_NAME)); - auto defaultConfig = types.get(ROOT_FB_NAME).createDefaultConfig(); + ASSERT_TRUE(types.hasKey(CLIENT_FB_NAME)); + auto defaultConfig = types.get(CLIENT_FB_NAME).createDefaultConfig(); ASSERT_TRUE(defaultConfig.assigned()); ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 5u); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_BROKER_PORT)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_USERNAME)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_MQTT_PASSWORD)); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_CONNECT_TIMEOUT)); - - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_ADDRESS).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_BROKER_PORT).getValueType(), CoreType::ctInt); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_USERNAME).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_MQTT_PASSWORD).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_CONNECT_TIMEOUT).getValueType(), CoreType::ctInt); - - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_ADDRESS), DEFAULT_BROKER_ADDRESS); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_BROKER_PORT), DEFAULT_PORT); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_USERNAME), DEFAULT_USERNAME); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_MQTT_PASSWORD), DEFAULT_PASSWORD); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CONNECT_TIMEOUT), DEFAULT_INIT_TIMEOUT); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_CLIENT_BROKER_ADDRESS)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_CLIENT_BROKER_PORT)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_CLIENT_USERNAME)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_CLIENT_PASSWORD)); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_CLIENT_CONNECT_TIMEOUT)); + + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_CLIENT_BROKER_ADDRESS).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_CLIENT_BROKER_PORT).getValueType(), CoreType::ctInt); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_CLIENT_USERNAME).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_CLIENT_PASSWORD).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_CLIENT_CONNECT_TIMEOUT).getValueType(), CoreType::ctInt); + + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CLIENT_BROKER_ADDRESS), DEFAULT_BROKER_ADDRESS); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CLIENT_BROKER_PORT), DEFAULT_PORT); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CLIENT_USERNAME), DEFAULT_USERNAME); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CLIENT_PASSWORD), DEFAULT_PASSWORD); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_CLIENT_CONNECT_TIMEOUT), DEFAULT_INIT_TIMEOUT); } TEST_F(MqttFbTest, CreatingMqttFbWithDefaultConfig) { const auto instance = Instance(); daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = instance.addFunctionBlock(ROOT_FB_NAME)); + ASSERT_NO_THROW(fb = instance.addFunctionBlock(CLIENT_FB_NAME)); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); @@ -60,7 +60,7 @@ TEST_F(MqttFbTest, CreatingMqttFbWithDefaultConfig) daq::FunctionBlockPtr fbFromList; for (const auto& fbInst : fbs) { - contain = (fbInst.getName() == std::string(MQTT_LOCAL_ROOT_FB_ID_PREFIX) + std::to_string(0)); + contain = (fbInst.getName() == std::string(MQTT_LOCAL_CLIENT_FB_ID_PREFIX) + std::to_string(0)); if (contain) { fbFromList = fbInst; @@ -77,7 +77,7 @@ TEST_F(MqttFbTest, CreatingMqttFbWithCustomConfig) const auto instance = Instance(); daq::FunctionBlockPtr fb; auto config = DaqMqttFbConfig(); - ASSERT_NO_THROW(fb = instance.addFunctionBlock(ROOT_FB_NAME, config)); + ASSERT_NO_THROW(fb = instance.addFunctionBlock(CLIENT_FB_NAME, config)); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); } @@ -87,7 +87,7 @@ TEST_F(MqttFbTest, CreatingMqttFbWithEmptyConfig) const auto instance = Instance(); daq::FunctionBlockPtr fb; auto config = PropertyObject(); - ASSERT_NO_THROW(fb = instance.addFunctionBlock(ROOT_FB_NAME, config)); + ASSERT_NO_THROW(fb = instance.addFunctionBlock(CLIENT_FB_NAME, config)); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); } @@ -97,21 +97,21 @@ TEST_F(MqttFbTest, CreatingMqttFbWithPartialConfig) const auto instance = Instance(); daq::FunctionBlockPtr fb; auto config = PropertyObject(); - config.addProperty(IntProperty(PROPERTY_NAME_CONNECT_TIMEOUT, 1000)); - ASSERT_NO_THROW(fb = instance.addFunctionBlock(ROOT_FB_NAME, config)); + config.addProperty(IntProperty(PROPERTY_NAME_CLIENT_CONNECT_TIMEOUT, 1000)); + ASSERT_NO_THROW(fb = instance.addFunctionBlock(CLIENT_FB_NAME, config)); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); } -TEST_F(MqttFbTest, CreatingSeveralMqttFbs) +TEST_F(MqttFbTest, DISABLED_CreatingSeveralMqttFbs) { const auto instance = Instance(); daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = instance.addFunctionBlock(ROOT_FB_NAME, DaqMqttFbConfig("127.0.0.1", 1883))); + ASSERT_NO_THROW(fb = instance.addFunctionBlock(CLIENT_FB_NAME, DaqMqttFbConfig("127.0.0.1", 1883))); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); daq::FunctionBlockPtr anotherFb; - ASSERT_NO_THROW(anotherFb = instance.addFunctionBlock(ROOT_FB_NAME, DaqMqttFbConfig("127.0.0.1", 1884))); + ASSERT_NO_THROW(anotherFb = instance.addFunctionBlock(CLIENT_FB_NAME, DaqMqttFbConfig("127.0.0.1", 1884))); ASSERT_EQ(anotherFb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", instance.getContext().getTypeManager())); ASSERT_EQ(instance.getFunctionBlocks().getCount(), 2u); @@ -122,7 +122,7 @@ TEST_F(MqttFbTest, RemovingMqttFb) const auto instance = Instance(); daq::FunctionBlockPtr fb; auto config = DaqMqttFbConfig(); - ASSERT_NO_THROW(fb = instance.addFunctionBlock(ROOT_FB_NAME, config)); + ASSERT_NO_THROW(fb = instance.addFunctionBlock(CLIENT_FB_NAME, config)); ASSERT_NO_THROW(instance.removeFunctionBlock(fb)); } @@ -130,9 +130,8 @@ TEST_F(MqttFbTest, CheckMqttFbFunctionalBlocks) { StartUp(); daq::DictPtr fbTypes; - ASSERT_NO_THROW(fbTypes = rootMqttFb.getAvailableFunctionBlockTypes()); - ASSERT_GE(fbTypes.getCount(), 3); - ASSERT_TRUE(fbTypes.hasKey(RAW_FB_NAME)); - ASSERT_TRUE(fbTypes.hasKey(JSON_FB_NAME)); + ASSERT_NO_THROW(fbTypes = clientMqttFb.getAvailableFunctionBlockTypes()); + ASSERT_GE(fbTypes.getCount(), 2); + ASSERT_TRUE(fbTypes.hasKey(SUB_FB_NAME)); ASSERT_TRUE(fbTypes.hasKey(PUB_FB_NAME)); } diff --git a/mqtt_streaming_module/tests/test_mqtt_json_decoder_fb.cpp b/modules/mqtt_streaming_module/tests/test_mqtt_json_decoder_fb.cpp similarity index 51% rename from mqtt_streaming_module/tests/test_mqtt_json_decoder_fb.cpp rename to modules/mqtt_streaming_module/tests/test_mqtt_json_decoder_fb.cpp index 290a73b..3acbfd0 100644 --- a/mqtt_streaming_module/tests/test_mqtt_json_decoder_fb.cpp +++ b/modules/mqtt_streaming_module/tests/test_mqtt_json_decoder_fb.cpp @@ -1,6 +1,6 @@ #include "MqttAsyncClientWrapper.h" #include "mqtt_streaming_helper/timer.h" -#include "mqtt_streaming_module/mqtt_json_receiver_fb_impl.h" +#include "mqtt_streaming_module/mqtt_subscriber_fb_impl.h" #include "mqtt_streaming_module/mqtt_json_decoder_fb_impl.h" #include "test_daq_test_helper.h" #include "test_data.h" @@ -16,347 +16,413 @@ using namespace daq; using namespace daq::modules::mqtt_streaming_module; +namespace +{ template -struct is_pair : std::false_type {}; +struct is_pair : std::false_type +{ +}; template -struct is_pair> : std::true_type {}; +struct is_pair> : std::true_type +{ +}; bool almostEqual(double a, double b, double relEpsilon = 1e-9, double absEpsilon = 1e-12) { return std::fabs(a - b) <= std::max(absEpsilon, relEpsilon * std::max(std::fabs(a), std::fabs(b))); } - -std::string doubleToString(double value, int precision = 12) +template +bool almostEqual(const std::vector& a, const std::vector& b, double relEpsilon = 1e-9, double absEpsilon = 1e-12) { - std::ostringstream out; - out << std::fixed << std::setprecision(precision) << value; - return out.str(); + if (a.size() != b.size()) + return false; + for (size_t i = 0; i < a.size(); ++i) + { + if (!almostEqual(a[i], b[i], relEpsilon, absEpsilon)) + return false; + } + return true; } -namespace daq::modules::mqtt_streaming_module -{ -class MqttJsonDecoderFbHelper : public DaqTestHelper +template +bool equal(const std::vector& a, const std::vector& b) { -public: - daq::FunctionBlockPtr decoderObj; - - void onSignalsMessage(const mqtt::MqttMessage& msg) + if (a.size() != b.size()) + return false; + if constexpr (std::is_same_v || std::is_same_v) { - mqtt::MqttAsyncClient unused; - auto fb = reinterpret_cast(*jsonMqttFb); - fb->onSignalsMessage(unused, msg); + return almostEqual(a, b); } - - void CreateJsonFbFromConfig(const std::string& jsonConfig) + else { - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_SIGNAL_LIST, String(""))); - const auto fbType = FunctionBlockType(JSON_FB_NAME, JSON_FB_NAME, "", config); - config.setPropertyValue(PROPERTY_NAME_SIGNAL_LIST, jsonConfig); - jsonMqttFb = new MqttJsonReceiverFbImpl(NullContext(), nullptr, fbType, nullptr, config); + return a == b; } +} - void CreateJsonFb(const std::string& topic) +template +bool equal(T a, T b) +{ + if constexpr (std::is_same_v || std::is_same_v) { - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, String(""))); - const auto fbType = FunctionBlockType(JSON_FB_NAME, JSON_FB_NAME, "", config); - config.setPropertyValue(PROPERTY_NAME_TOPIC, topic); - jsonMqttFb = new MqttJsonReceiverFbImpl(NullContext(), nullptr, fbType, nullptr, config); + return almostEqual(a, b); } - - void CreateDecoderFB(std::string topic, std::string valueF, std::string tsF, std::string sigName = "", std::string unitSymbol = "") + else { - CreateJsonFb(topic); - AddDecoderFb(valueF, tsF, sigName, unitSymbol); + return a == b; } +} - daq::FunctionBlockPtr AddDecoderFb(std::string valueF, std::string tsF, std::string sigName = "", std::string unitSymbol = "") - { - daq::StringPtr typeId = daq::String(JSON_DECODER_FB_NAME); - auto config = jsonMqttFb.getAvailableFunctionBlockTypes().get(JSON_DECODER_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_VALUE_NAME, valueF); - config.setPropertyValue(PROPERTY_NAME_TS_NAME, tsF); - config.setPropertyValue(PROPERTY_NAME_SIGNAL_NAME, sigName); - config.setPropertyValue(PROPERTY_NAME_UNIT, unitSymbol); - decoderObj = jsonMqttFb.addFunctionBlock(typeId, config); - return decoderObj; - } +template +bool equalTs(T a, T b) +{ + const auto convertedA = mqtt::utils::numericToMicroseconds(a); + const auto convertedB = mqtt::utils::numericToMicroseconds(b); + return (convertedA == convertedB && convertedA != 0); +} - auto getSignals() +template +bool equalTs(const std::vector& a, const std::vector& b) +{ + if (a.size() != b.size()) + return false; + for (size_t i = 0; i < a.size(); ++i) { - return decoderObj.getSignals(); + if (!equalTs(a[i], b[i])) + return false; } + return true; +} - std::string buildTopicName(const std::string& postfix = "") +bool equalTs(const std::string& a, uint64_t b) +{ + return (mqtt::utils::toUnixTicks(a) == b && b != 0); +} + +bool equalTs(uint64_t b, const std::string& a) +{ + return equalTs(a, b); +} + +bool equalTs(const std::vector& a, const std::vector& b) +{ + if (a.size() != b.size()) + return false; + for (size_t i = 0; i < a.size(); ++i) { - return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + postfix; + if (!equalTs(a[i], b[i])) + return false; } + return true; +} - std::string buildClientId() +bool equalTs(const std::vector& b, const std::vector& a) +{ + return equalTs(a, b); +} + +std::string doubleToString(double value, int precision = 12) +{ + std::ostringstream out; + out << std::fixed << std::setprecision(precision) << value; + return out.str(); +} + +template +void merge(const std::vector>& input, std::vector, std::vector>>& output) +{ + std::vector data; + std::vector ts; + for (const auto& [sData, sTs] : input) { - return std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + "_ClientId"; + data.push_back(sData); + ts.push_back(sTs); } + output.emplace_back(std::pair(std::move(data), std::move(ts))); +} - template std::string replacePlaceholder(const std::string& jsonTemplate, const std::string& ph, const T& value) +template +std::string replacePlaceholder(const std::string& jsonTemplate, const std::string& ph, const T& value) +{ + std::string result = jsonTemplate; + size_t pos = result.find(ph); + if (pos != std::string::npos) { - std::string result = jsonTemplate; - size_t pos = result.find(ph); - if (pos != std::string::npos) + std::string replacement; + if constexpr (std::is_same_v) { - std::string replacement; - if constexpr (std::is_same_v) - { - replacement = '"' + value + '"'; - } - else if constexpr (std::is_same_v || std::is_same_v) - { - replacement = doubleToString(value); - } - else + replacement = '"' + value + '"'; + } + else if constexpr (std::is_same_v || std::is_same_v) + { + replacement = doubleToString(value); + } + else if constexpr (mqtt::is_std_vector_v) + { + replacement = "["; + for (size_t i = 0; i < value.size(); ++i) { - replacement = std::to_string(value); + if (i > 0) + replacement += ", "; + if constexpr (std::is_same_v, double> || std::is_same_v, float>) + replacement += doubleToString(value[i]); + else if constexpr (std::is_same_v, std::string>) + replacement += '"' + value[i] + '"'; + else + replacement += std::to_string(value[i]); } - result.replace(pos, ph.length(), replacement); + replacement += "]"; } - return result; + else + { + replacement = std::to_string(value); + } + result.replace(pos, ph.length(), replacement); } + return result; +} - template std::vector replacePlaceholders(const std::vector>& data, const std::string& jsonTemplate) +template +std::vector replacePlaceholders(const std::vector>& data, const std::string& jsonTemplate) +{ + std::vector result; + for (const auto& [value, ts] : data) { - std::vector result; - for (const auto& [value, ts] : data) + auto str = replacePlaceholder(jsonTemplate, "", value); + str = replacePlaceholder(str, "", ts); + result.push_back(str); + } + return result; +} + +std::string extractFieldName(std::string jsonTemplate, const std::string& valuePh) +{ + std::string result; + size_t pos = jsonTemplate.find(valuePh); + if (pos == std::string::npos) + return ""; + size_t posEnd = jsonTemplate.rfind("\"", pos); + if (posEnd == std::string::npos) + return ""; + size_t posStart = jsonTemplate.rfind("\"", posEnd - 1); + if (posStart == std::string::npos) + return ""; + ++posStart; + result = jsonTemplate.substr(posStart, posEnd - posStart); + return result; +} + +template +bool compareData(const std::vector>& data0, const std::vector>& data1, bool compareTs = true) +{ + if (data0.size() != data1.size()) + return false; + for (std::size_t i = 0; i < data0.size(); ++i) + { + const auto& [value0, ts0] = data0[i]; + const auto& [value1, ts1] = data1[i]; + if (!equal(value0, value1)) + return false; + if (compareTs) { - auto str = replacePlaceholder(jsonTemplate, "", value); - str = replacePlaceholder(str, "", ts); - result.push_back(str); + if (!equalTs(ts0, ts1)) + return false; } - return result; } + return true; +} - std::string extractFieldName(std::string jsonTemplate, const std::string& valuePh) - { - std::string result; - size_t pos = jsonTemplate.find(valuePh); - if (pos == std::string::npos) - return ""; - size_t posEnd = jsonTemplate.rfind("\"", pos); - if (posEnd == std::string::npos) - return ""; - size_t posStart = jsonTemplate.rfind("\"", posEnd - 1); - if (posStart == std::string::npos) - return ""; - ++posStart; - result = jsonTemplate.substr(posStart, posEnd - posStart); - return result; +template +bool compareData(const std::vector>& data0, const std::vector& data1) +{ + if (data0.size() != data1.size()) + return false; + for (std::size_t i = 0; i < data0.size(); ++i) + { + const auto& [value0, _] = data0[i]; + const auto& value1 = data1[i]; + + if (!equal(value0, value1)) + return false; } + return true; +} - template - std::vector> - transferData(const std::vector>& data, const std::string& jsonConfigTemplate, const std::string& jsonDataTemplate) +template +bool copyData(T& destination, const DataPacketPtr source) +{ + auto checkType = [](SampleType type) -> bool { - return transferData>(data, jsonConfigTemplate, jsonDataTemplate); - } + switch (type) + { + case SampleType::Float32: + case SampleType::Float64: + case SampleType::UInt8: + case SampleType::Int8: + case SampleType::UInt16: + case SampleType::Int16: + case SampleType::UInt32: + case SampleType::Int32: + case SampleType::UInt64: + case SampleType::Int64: + case SampleType::RangeInt64: + case SampleType::ComplexFloat32: + case SampleType::ComplexFloat64: + return true; + case SampleType::String: + case SampleType::Binary: + case SampleType::Struct: + case SampleType::Invalid: + case SampleType::Null: + case SampleType::_count: + return false; + } + return true; + }; - template - std::vector transferDataWithoutDomain(const std::vector>& data, - const std::string& jsonConfigTemplate, - const std::string& jsonDataTemplate) + const auto dataType = source.getDataDescriptor().getSampleType(); + if (checkType(dataType) && getSampleSize(dataType) != sizeof(mqtt::sample_type_t)) + return false; + if constexpr (std::is_same_v) { - return transferData(data, jsonConfigTemplate, jsonDataTemplate); + destination = std::string(static_cast(source.getData()), source.getDataSize()); } - - template - std::vector> transferData(const std::vector>& data, const std::string& jsonDataTemplate) + else if constexpr (mqtt::is_std_vector_v) { - return transferData>(data, jsonDataTemplate); + destination.resize(source.getSampleCount()); + memcpy(destination.data(), source.getRawData(), source.getSampleCount() * getSampleSize(dataType)); } - - template - std::vector transferDataWithoutDomain(const std::vector>& data, const std::string& jsonDataTemplate) + else { - return transferData(data, jsonDataTemplate); + memcpy(&destination, source.getData(), sizeof(destination)); } + return true; +} - template - bool compareData(const std::vector>& data0, const std::vector>& data1, bool compareTs = true) +template +std::vector read(PacketReaderPtr reader, const SignalPtr signal, int timeoutMs = 1000) +{ + std::vector result; + + auto timer = helper::utils::Timer(timeoutMs); + while (!reader.getEmpty() || !timer.expired()) { - if (data0.size() != data1.size()) - return false; - for (std::size_t i = 0; i < data0.size(); ++i) + if (reader.getEmpty()) { - const auto& [value0, ts0] = data0[i]; - const auto& [value1, ts1] = data1[i]; - if constexpr (std::is_same_v) - { - if (!almostEqual(static_cast(value0), static_cast(value1))) - return false; - } - else - { - if (value0 != value1) - return false; - } - if (compareTs) - { - if constexpr (std::is_same_v) - { - if (mqtt::utils::numericToMicroseconds(ts0) != ts1 && ts1 != 0) - return false; - } - else if constexpr (std::is_same_v) - { - if (mqtt::utils::toUnixTicks(ts0) != ts1 && ts1 != 0) - return false; - } - else - { - return false; - } - } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + continue; + } + auto packet = reader.read(); + if (packet.getType() == PacketType::Event) + { + continue; } - return true; - } - template - bool compareData(const std::vector>& data0, const std::vector& data1) - { - if (data0.size() != data1.size()) - return false; - for (std::size_t i = 0; i < data0.size(); ++i) + if (packet.getType() == PacketType::Data) { - const auto& [value0, ts0] = data0[i]; - const auto& value1 = data1[i]; - if constexpr (std::is_same_v) + const auto dataPacket = packet.asPtr(); + if constexpr (is_pair::value) { - if (!almostEqual(static_cast(value0), static_cast(value1))) - return false; + T dataToReceiveEntry; + bool ok = true; + ok &= copyData(dataToReceiveEntry.first, dataPacket); + if (signal.getDomainSignal().assigned()) + ok &= copyData(dataToReceiveEntry.second, dataPacket.getDomainPacket()); + if (!ok) + break; + result.push_back(dataToReceiveEntry); } else { - if (value0 != value1) - return false; + T dataToReceiveEntry; + bool ok = copyData(dataToReceiveEntry, dataPacket); + if (!ok) + break; + result.push_back(dataToReceiveEntry); } } - return true; } - template - bool copyData(T& destination, const DataPacketPtr source) + return result; +} +} // namespace + +namespace daq::modules::mqtt_streaming_module +{ +class MqttJsonDecoderFbHelper : public DaqTestHelper +{ +public: + daq::FunctionBlockPtr decoderObj; + + void onSignalsMessage(const mqtt::MqttMessage& msg) { - auto checkType = [](SampleType type) -> bool - { - switch (type) - { - case SampleType::Float32: - case SampleType::Float64: - case SampleType::UInt8: - case SampleType::Int8: - case SampleType::UInt16: - case SampleType::Int16: - case SampleType::UInt32: - case SampleType::Int32: - case SampleType::UInt64: - case SampleType::Int64: - case SampleType::RangeInt64: - case SampleType::ComplexFloat32: - case SampleType::ComplexFloat64: - return true; - case SampleType::String: - case SampleType::Binary: - case SampleType::Struct: - case SampleType::Invalid: - case SampleType::Null: - case SampleType::_count: - return false; - } - return true; - }; + mqtt::MqttAsyncClient unused; + auto fb = reinterpret_cast(*subMqttFb); + fb->onSignalsMessage(unused, msg); + } - const auto dataType = source.getDataDescriptor().getSampleType(); - if (checkType(dataType) && getSampleSize(dataType) != sizeof(destination)) - return false; - if constexpr (std::is_same_v) - { - destination = std::string(static_cast(source.getData()), source.getDataSize()); - } - else - { - memcpy(&destination, source.getData(), sizeof(destination)); - } - return true; + void CreateJsonFb(const std::string& topic) + { + auto config = PropertyObject(); + config.addProperty(StringProperty(PROPERTY_NAME_SUB_TOPIC, String(""))); + const auto fbType = FunctionBlockType(SUB_FB_NAME, SUB_FB_NAME, "", config); + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, topic); + subMqttFb = new MqttSubscriberFbImpl(NullContext(), nullptr, fbType, nullptr, config); } - template - std::vector read(PacketReaderPtr reader, const SignalPtr signal, int timeoutMs = 1000) + void CreateDecoderFB(std::string topic, std::string valueF, std::string tsF, std::string unitSymbol = "") { - std::vector result; + CreateJsonFb(topic); + AddDecoderFb(valueF, tsF, unitSymbol); + } - auto timer = helper::utils::Timer(timeoutMs); - while (!reader.getEmpty() || !timer.expired()) - { - if (reader.getEmpty()) - { - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - continue; - } - auto packet = reader.read(); - if (packet.getType() == PacketType::Event) - { - continue; - } + daq::FunctionBlockPtr AddDecoderFb(std::string valueF, std::string tsF, std::string unitSymbol = "") + { + daq::StringPtr typeId = daq::String(JSON_DECODER_FB_NAME); + auto config = subMqttFb.getAvailableFunctionBlockTypes().get(JSON_DECODER_FB_NAME).createDefaultConfig(); - if (packet.getType() == PacketType::Data) - { - const auto dataPacket = packet.asPtr(); - if constexpr (is_pair::value) - { - T dataToReceiveEntry; - bool ok = true; - ok &= copyData(dataToReceiveEntry.first, dataPacket); - if (signal.getDomainSignal().assigned()) - ok &= copyData(dataToReceiveEntry.second, dataPacket.getDomainPacket()); - if (!ok) - break; - result.push_back(dataToReceiveEntry); - - } - else - { - T dataToReceiveEntry; - bool ok = copyData(dataToReceiveEntry, dataPacket); - if (!ok) - break; - result.push_back(dataToReceiveEntry); - } - } - } + config.setPropertyValue(PROPERTY_NAME_DEC_VALUE_NAME, valueF); + config.setPropertyValue(PROPERTY_NAME_DEC_TS_NAME, tsF); + config.setPropertyValue(PROPERTY_NAME_DEC_UNIT, unitSymbol); + decoderObj = subMqttFb.addFunctionBlock(typeId, config); + return decoderObj; + } - return result; + auto getSignals() + { + return decoderObj.getSignals(); } -private: - template std::vector transferData(const std::vector>& data, const std::string& jsonConfigTemplate, const std::string& jsonDataTemplate) + std::string buildTopicName(const std::string& postfix = "") { - const auto topic = buildTopicName(); - const auto jsonConfig = replacePlaceholder(jsonConfigTemplate, "", topic); - CreateJsonFbFromConfig(jsonConfig); + return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + postfix; + } - auto signal = getSignals()[0]; - auto reader = daq::PacketReader(signal); + std::string buildClientId() + { + return std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + "_ClientId"; + } - auto msgs = replacePlaceholders(data, jsonDataTemplate); - for (const auto& str : msgs) - { - onSignalsMessage({topic, std::vector(str.begin(), str.end()), 1, 0}); - } + template + std::vector> transferData(const std::vector>& data, const std::string& jsonDataTemplate) + { + return transferData>(data, jsonDataTemplate); + } - std::vector dataToReceive = read(reader, signal, 0); - return dataToReceive; + template + std::vector, std::vector>> + transferData(const std::vector, std::vector>>& data, const std::string& jsonDataTemplate) + { + return transferData, std::vector, std::pair, std::vector>>(data, jsonDataTemplate); + } + + template + std::vector transferDataWithoutDomain(const std::vector>& data, const std::string& jsonDataTemplate) + { + return transferData(data, jsonDataTemplate); } +private: template std::vector transferData(const std::vector>& data, const std::string& jsonDataTemplate) { const auto topic = buildTopicName(); @@ -456,54 +522,46 @@ class MqttJsonFbUnitPTest : public ::testing::TestWithParam fbTypes; daq::FunctionBlockTypePtr fbt; daq::PropertyObjectPtr defaultConfig; - ASSERT_NO_THROW(fbTypes = jsonMqttFb.getAvailableFunctionBlockTypes()); + ASSERT_NO_THROW(fbTypes = subMqttFb.getAvailableFunctionBlockTypes()); ASSERT_NO_THROW(fbt = fbTypes.get(JSON_DECODER_FB_NAME)); ASSERT_NO_THROW(defaultConfig = fbt.createDefaultConfig()); ASSERT_TRUE(defaultConfig.assigned()); - ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 4u); + ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 3u); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_VALUE_NAME)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_VALUE_NAME).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_VALUE_NAME).asPtr().getLength(), 0u); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_DEC_VALUE_NAME)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_DEC_VALUE_NAME).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_DEC_VALUE_NAME).asPtr().getLength(), 0u); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_TS_NAME)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_TS_NAME).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_TS_NAME).asPtr().getLength(), 0u); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_DEC_TS_NAME)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_DEC_TS_NAME).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_DEC_TS_NAME).asPtr().getLength(), 0u); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SIGNAL_NAME)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SIGNAL_NAME).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SIGNAL_NAME).asPtr().toStdString(), std::string(DEFAULT_SIGNAL_NAME)); - - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_UNIT)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_UNIT).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_UNIT).asPtr().getLength(), 0u); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_DEC_UNIT)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_DEC_UNIT).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_DEC_UNIT).asPtr().getLength(), 0u); } TEST_F(MqttJsonDecoderFbTest, Config) { StartUp(); - AddJsonFb(buildTopicName()); - auto config = jsonMqttFb.getAvailableFunctionBlockTypes().get(JSON_DECODER_FB_NAME).createDefaultConfig(); + AddSubFb(buildTopicName()); + auto config = subMqttFb.getAvailableFunctionBlockTypes().get(JSON_DECODER_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_VALUE_NAME, "value"); - config.setPropertyValue(PROPERTY_NAME_TS_NAME, "timestamp"); - config.setPropertyValue(PROPERTY_NAME_SIGNAL_NAME, "signalName_0"); - config.setPropertyValue(PROPERTY_NAME_UNIT, "ppm"); + config.setPropertyValue(PROPERTY_NAME_DEC_VALUE_NAME, "value"); + config.setPropertyValue(PROPERTY_NAME_DEC_TS_NAME, "timestamp"); + config.setPropertyValue(PROPERTY_NAME_DEC_UNIT, "ppm"); daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = jsonMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); + ASSERT_NO_THROW(fb = subMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); EXPECT_EQ(fb.getSignals().getCount(), 1u); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::WaitingForData), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(fb.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Waiting for data"), std::string::npos); const auto allProperties = fb.getAllProperties(); ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount()); @@ -518,49 +576,40 @@ TEST_F(MqttJsonDecoderFbTest, Config) TEST_F(MqttJsonDecoderFbTest, CreationWithDefaultConfig) { StartUp(); - AddJsonFb(buildTopicName()); + AddSubFb(buildTopicName()); daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = jsonMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME)); + ASSERT_NO_THROW(fb = subMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME)); EXPECT_EQ(fb.getSignals().getCount(), 1u); EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::InvalidParamaters), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(fb.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Configuration is invalid"), std::string::npos); } TEST_F(MqttJsonDecoderFbTest, CreationWithPartialConfig) { StartUp(); - AddJsonFb(buildTopicName()); + AddSubFb(buildTopicName()); { daq::FunctionBlockPtr fb; auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_VALUE_NAME, String("value"))); - ASSERT_NO_THROW(fb = jsonMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); + config.addProperty(StringProperty(PROPERTY_NAME_DEC_VALUE_NAME, String("value"))); + ASSERT_NO_THROW(fb = subMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); EXPECT_EQ(fb.getSignals().getCount(), 1u); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::WaitingForData), - daqInstance.getContext().getTypeManager())); - jsonMqttFb.removeFunctionBlock(fb); + EXPECT_NE(fb.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Waiting for data"), std::string::npos); + subMqttFb.removeFunctionBlock(fb); } { daq::FunctionBlockPtr fb; auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TS_NAME, String("ts"))); - ASSERT_NO_THROW(fb = jsonMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); + config.addProperty(StringProperty(PROPERTY_NAME_DEC_TS_NAME, String("ts"))); + ASSERT_NO_THROW(fb = subMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); EXPECT_EQ(fb.getSignals().getCount(), 1u); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::InvalidParamaters), - daqInstance.getContext().getTypeManager())); - jsonMqttFb.removeFunctionBlock(fb); + EXPECT_NE(fb.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Configuration is invalid"), std::string::npos); + subMqttFb.removeFunctionBlock(fb); } } @@ -572,10 +621,7 @@ TEST_P(MqttJsonFbDoubleDataPTest, DataTransferOneSignalDouble) ASSERT_TRUE(compareData(dataToSend, dataToReceive)); ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); - ASSERT_EQ(decoderObj.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } TEST_P(MqttJsonFbDoubleDataPTest, DataTransferOneSignalDoubleWithoutDomain) @@ -586,16 +632,88 @@ TEST_P(MqttJsonFbDoubleDataPTest, DataTransferOneSignalDoubleWithoutDomain) ASSERT_TRUE(compareData(dataToSend, dataToReceive)); ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); - ASSERT_EQ(decoderObj.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } INSTANTIATE_TEST_SUITE_P(DataTransferOneSignalDouble, MqttJsonFbDoubleDataPTest, ::testing::Values(DATA_DOUBLE_INT_0, DATA_DOUBLE_INT_1, DATA_DOUBLE_INT_2)); +TEST_F(MqttJsonDecoderFbTest, DataTransferOneSignalDoubleArray) +{ + std::vector, std::vector>> dataToSend; + merge(DATA_DOUBLE_INT_0, dataToSend); + merge(DATA_DOUBLE_INT_1, dataToSend); + merge(DATA_DOUBLE_INT_2, dataToSend); + + auto dataToReceive = transferData(dataToSend, VALID_JSON_DATA_0); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); + ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); +} + +TEST_F(MqttJsonDecoderFbTest, DataTransferOneSignalDoubleArrayWithoutDomain) +{ + std::vector, std::vector>> dataToSend; + merge(DATA_DOUBLE_INT_0, dataToSend); + merge(DATA_DOUBLE_INT_1, dataToSend); + merge(DATA_DOUBLE_INT_2, dataToSend); + + auto dataToReceive = transferDataWithoutDomain(dataToSend, VALID_JSON_DATA_1); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); + ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); +} + +TEST_F(MqttJsonDecoderFbTest, DataTransferOneSignalIntArray) +{ + std::vector, std::vector>> dataToSend; + merge(DATA_INT_INT_0, dataToSend); + merge(DATA_INT_INT_1, dataToSend); + merge(DATA_INT_INT_2, dataToSend); + + auto dataToReceive = transferData(dataToSend, VALID_JSON_DATA_0); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); + ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); +} + +TEST_F(MqttJsonDecoderFbTest, DataTransferOneSignalIntArrayWithoutDomain) +{ + std::vector, std::vector>> dataToSend; + merge(DATA_INT_INT_0, dataToSend); + merge(DATA_INT_INT_1, dataToSend); + merge(DATA_INT_INT_2, dataToSend); + + auto dataToReceive = transferDataWithoutDomain(dataToSend, VALID_JSON_DATA_1); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); + ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); +} + +TEST_F(MqttJsonDecoderFbTest, DataTransferOneSignalDoubleArrayDomainString) +{ + std::vector, std::vector>> dataToSend; + merge(DATA_DOUBLE_STR_0, dataToSend); + merge(DATA_DOUBLE_STR_1, dataToSend); + merge(DATA_DOUBLE_STR_2, dataToSend); + + auto dataToReceive = transferData<>(dataToSend, VALID_JSON_DATA_0); + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_TRUE(compareData(dataToSend, dataToReceive)); + ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); +} + TEST_P(MqttJsonFbIntDataPTest, DataTransferOneSignalInt) { const auto dataToSend = GetParam(); @@ -604,10 +722,7 @@ TEST_P(MqttJsonFbIntDataPTest, DataTransferOneSignalInt) ASSERT_TRUE(compareData(dataToSend, dataToReceive)); ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); - ASSERT_EQ(decoderObj.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } TEST_P(MqttJsonFbIntDataPTest, DataTransferOneSignalIntWithoutDomain) @@ -618,10 +733,7 @@ TEST_P(MqttJsonFbIntDataPTest, DataTransferOneSignalIntWithoutDomain) ASSERT_TRUE(compareData(dataToSend, dataToReceive)); ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); - ASSERT_EQ(decoderObj.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } INSTANTIATE_TEST_SUITE_P(DataTransferOneSignalInt, @@ -636,10 +748,7 @@ TEST_P(MqttJsonFbStringDataPTest, DataTransferOneSignalString) ASSERT_TRUE(compareData(dataToSend, dataToReceive)); ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); - ASSERT_EQ(decoderObj.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } TEST_P(MqttJsonFbStringDataPTest, DataTransferOneSignalStringWithoutDomain) @@ -650,10 +759,7 @@ TEST_P(MqttJsonFbStringDataPTest, DataTransferOneSignalStringWithoutDomain) ASSERT_TRUE(compareData(dataToSend, dataToReceive)); ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); - ASSERT_EQ(decoderObj.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } INSTANTIATE_TEST_SUITE_P(DataTransferOneSignalString, @@ -668,10 +774,7 @@ TEST_P(MqttJsonFbStringTsPTest, DataTransferOneSignalIntDomainString) ASSERT_TRUE(compareData(dataToSend, dataToReceive)); ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", decoderObj.getContext().getTypeManager())); - ASSERT_EQ(decoderObj.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } INSTANTIATE_TEST_SUITE_P(DataTransferOneSignalInt, @@ -688,8 +791,8 @@ TEST_F(MqttJsonDecoderFbTest, DataTransferSeveralSignals) const auto topic = buildTopicName(); DaqInstanceInit(); - auto rootFb0 = DaqAddRootMqttFb("127.0.0.1", DEFAULT_PORT); - auto jsonFb0 = AddJsonFb(topic); + auto clientFb0 = DaqAddClientMqttFb("127.0.0.1", DEFAULT_PORT); + auto jsonFb0 = AddSubFb(topic); auto decoderFb0 = AddDecoderFb(valueF0, tsF); auto decoderFb1 = AddDecoderFb(valueF1, tsF); auto decoderFb2 = AddDecoderFb(valueF2, ""); @@ -726,28 +829,19 @@ TEST_F(MqttJsonDecoderFbTest, DataTransferSeveralSignals) EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive[0])); ASSERT_EQ(decoderFb0.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(decoderFb0.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(decoderFb0.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); EXPECT_EQ(DATA_DOUBLE_INT_1.size(), dataToReceive[1].size()); EXPECT_TRUE(compareData(DATA_DOUBLE_INT_1, dataToReceive[1])); ASSERT_EQ(decoderFb1.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(decoderFb1.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(decoderFb1.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); EXPECT_EQ(DATA_DOUBLE_INT_2.size(), dataToReceive[2].size()); EXPECT_TRUE(compareData(DATA_DOUBLE_INT_2, dataToReceive[2], false)); ASSERT_EQ(decoderFb2.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(decoderFb2.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(decoderFb2.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } @@ -770,11 +864,8 @@ TEST_F(MqttJsonDecoderFbTest, DataTransferMissingFieldOneSignal) std::vector> dataToReceive = read>(reader, signal, 0); ASSERT_EQ(dataToReceive.size(), 0); ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Warning", decoderObj.getContext().getTypeManager())); - ASSERT_EQ(decoderObj.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingFailed), - decoderObj.getContext().getTypeManager())); + Enumeration("ComponentStatusType", "Error", decoderObj.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing failed"), std::string::npos); } TEST_F(MqttJsonDecoderFbTest, DataTransferMissingFieldSeveralSignals) @@ -787,8 +878,8 @@ TEST_F(MqttJsonDecoderFbTest, DataTransferMissingFieldSeveralSignals) const auto topic = buildTopicName(); DaqInstanceInit(); - auto rootFb0 = DaqAddRootMqttFb("127.0.0.1", DEFAULT_PORT); - auto jsonFb0 = AddJsonFb(topic); + auto clientFb0 = DaqAddClientMqttFb("127.0.0.1", DEFAULT_PORT); + auto jsonFb0 = AddSubFb(topic); auto decoderFb0 = AddDecoderFb(valueF0, tsF); auto decoderFb1 = AddDecoderFb(valueF1, tsF); auto decoderFb2 = AddDecoderFb(valueF2, ""); @@ -824,27 +915,18 @@ TEST_F(MqttJsonDecoderFbTest, DataTransferMissingFieldSeveralSignals) EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, dataToReceive[0])); ASSERT_EQ(decoderFb0.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(decoderFb0.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(decoderFb0.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); EXPECT_EQ(0u, dataToReceive[1].size()); ASSERT_EQ(decoderFb1.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Warning", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(decoderFb1.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingFailed), - daqInstance.getContext().getTypeManager())); + Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); + EXPECT_NE(decoderFb1.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing failed"), std::string::npos); EXPECT_EQ(DATA_DOUBLE_INT_2.size(), dataToReceive[2].size()); EXPECT_TRUE(compareData(DATA_DOUBLE_INT_2, dataToReceive[2], false)); ASSERT_EQ(decoderFb2.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(decoderFb2.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(decoderFb2.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } TEST_F(MqttJsonFbCommunicationTest, FullDataTransfer) @@ -854,7 +936,7 @@ TEST_F(MqttJsonFbCommunicationTest, FullDataTransfer) const auto msgTemplate = VALID_JSON_DATA_0; const std::string valueF = extractFieldName(msgTemplate, ""); const std::string tsF = extractFieldName(msgTemplate, ""); - AddJsonFb(topic); + AddSubFb(topic); AddDecoderFb(valueF, tsF); const auto result = processTransfer("127.0.0.1", DEFAULT_PORT, topic, DATA_DOUBLE_INT_0, decoderObj.getSignals()[0]); @@ -865,13 +947,10 @@ TEST_F(MqttJsonFbCommunicationTest, FullDataTransfer) ASSERT_TRUE(compareData(DATA_DOUBLE_INT_0, result.dataReceived)); ASSERT_EQ(decoderObj.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(decoderObj.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(decoderObj.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } -TEST_F(MqttJsonFbCommunicationTest, FullDataTransferFor2MqttFbs) +TEST_F(MqttJsonFbCommunicationTest, DISABLED_FullDataTransferFor2MqttFbs) { const auto msgTemplate = VALID_JSON_DATA_0; const std::string valueF = extractFieldName(msgTemplate, ""); @@ -880,12 +959,12 @@ TEST_F(MqttJsonFbCommunicationTest, FullDataTransferFor2MqttFbs) const std::string topic1 = buildTopicName("1"); DaqInstanceInit(); - auto rootFb0 = DaqAddRootMqttFb("127.0.0.1", 1883); - auto jsonFb0 = AddJsonFb(topic0); + auto clientFb0 = DaqAddClientMqttFb("127.0.0.1", 1883); + auto jsonFb0 = AddSubFb(topic0); auto decoderFb0 = AddDecoderFb(valueF, tsF); - auto rootFb1 = DaqAddRootMqttFb("127.0.0.1", 1884); - auto jsonFb1 = AddJsonFb(topic1); + auto clientFb1 = DaqAddClientMqttFb("127.0.0.1", 1884); + auto jsonFb1 = AddSubFb(topic1); auto decoderFb1 = AddDecoderFb(valueF, tsF); const auto result0 = processTransfer("127.0.0.1", 1883, topic0, DATA_DOUBLE_INT_0, decoderFb0.getSignals()[0]); @@ -897,10 +976,7 @@ TEST_F(MqttJsonFbCommunicationTest, FullDataTransferFor2MqttFbs) EXPECT_TRUE(compareData(DATA_DOUBLE_INT_0, result0.dataReceived)); ASSERT_EQ(decoderFb0.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(decoderFb0.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(decoderFb0.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); ASSERT_FALSE(result1.mqttFbProblem); ASSERT_FALSE(result1.publishingProblem); @@ -908,26 +984,23 @@ TEST_F(MqttJsonFbCommunicationTest, FullDataTransferFor2MqttFbs) EXPECT_TRUE(compareData(DATA_DOUBLE_INT_1, result1.dataReceived)); ASSERT_EQ(decoderFb1.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(decoderFb1.getStatusContainer().getStatus(MQTT_FB_PARSING_STATUS_NAME), - EnumerationWithIntValue(MQTT_FB_PARSING_STATUS_TYPE, - static_cast(MqttJsonDecoderFbImpl::ParsingStatus::ParsingSuccedeed), - daqInstance.getContext().getTypeManager())); + EXPECT_NE(decoderFb1.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Parsing succeeded"), std::string::npos); } TEST_F(MqttJsonDecoderFbTest, RemovingNestedFunctionBlock) { StartUp(); - AddJsonFb(buildTopicName()); + AddSubFb(buildTopicName()); daq::FunctionBlockPtr jsonDecoderFb; { auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_VALUE_NAME, String("temp"))); - ASSERT_NO_THROW(jsonDecoderFb = jsonMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); + config.addProperty(StringProperty(PROPERTY_NAME_DEC_VALUE_NAME, String("temp"))); + ASSERT_NO_THROW(jsonDecoderFb = subMqttFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); } - ASSERT_EQ(jsonMqttFb.getFunctionBlocks().getCount(), 1u); + ASSERT_EQ(subMqttFb.getFunctionBlocks().getCount(), 1u); - ASSERT_NO_THROW(jsonMqttFb.removeFunctionBlock(jsonDecoderFb)); - ASSERT_EQ(jsonMqttFb.getFunctionBlocks().getCount(), 0u); - ASSERT_EQ(jsonMqttFb.getStatusContainer().getStatus("ComponentStatus"), + ASSERT_NO_THROW(subMqttFb.removeFunctionBlock(jsonDecoderFb)); + ASSERT_EQ(subMqttFb.getFunctionBlocks().getCount(), 0u); + ASSERT_EQ(subMqttFb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); } diff --git a/mqtt_streaming_module/tests/test_mqtt_publisher_fb.cpp b/modules/mqtt_streaming_module/tests/test_mqtt_publisher_fb.cpp similarity index 65% rename from mqtt_streaming_module/tests/test_mqtt_publisher_fb.cpp rename to modules/mqtt_streaming_module/tests/test_mqtt_publisher_fb.cpp index c407bbd..5cf30a3 100644 --- a/mqtt_streaming_module/tests/test_mqtt_publisher_fb.cpp +++ b/modules/mqtt_streaming_module/tests/test_mqtt_publisher_fb.cpp @@ -73,22 +73,32 @@ class SignalHelper return data; } - void send(const std::vector>& data) const + void send(const std::vector>& data, size_t packSize = 3) const { - for (size_t i = 0; i < data.size(); i++) + if (packSize == 0) + packSize = 1; + auto sendPacket = [this](SignalConfigPtr signal, const std::vector& data, DataPacketPtr domainPacket) { - auto sendPacket = [this](SignalConfigPtr signal, T data, DataPacketPtr domainPacket) + auto dataPacket = DataPacketWithDomain(domainPacket, signal0.getDescriptor(), data.size()); + copyData(dataPacket, data); + signal.sendPacket(dataPacket); + }; + for (size_t i = 0; i < data.size(); i += packSize) + { + std::vector dataPack; + std::vector tsPack; + for (size_t j = 0; j < packSize && (j + i) < data.size(); j++) { - auto dataPacket = DataPacketWithDomain(domainPacket, signal0.getDescriptor(), 1); - copyData(dataPacket, data); - signal.sendPacket(dataPacket); - }; - auto domainPacket = DataPacket(signal0.getDomainSignal().getDescriptor(), 1, i); - memcpy(domainPacket.getData(), &(data[i].second), sizeof(uint64_t)); + dataPack.push_back(data[j + i].first); + tsPack.push_back(data[j + i].second); + } + + auto domainPacket = DataPacket(signal0.getDomainSignal().getDescriptor(), tsPack.size(), i); + memcpy(domainPacket.getData(), tsPack.data(), sizeof(uint64_t) * tsPack.size()); SignalConfigPtr dSignal = signal0.getDomainSignal(); dSignal.sendPacket(domainPacket); - sendPacket(signal0, data[i].first, domainPacket); - sendPacket(signal1, data[i].first, domainPacket); + sendPacket(signal0, dataPack, domainPacket); + sendPacket(signal1, dataPack, domainPacket); } } @@ -140,7 +150,7 @@ class SignalHelper bool copyData(DataPacketPtr destination, const T& source) const { const auto dataType = destination.getDataDescriptor().getSampleType(); - if (checkType(dataType) && getSampleSize(dataType) != sizeof(source)) + if (checkType(dataType) && getSampleSize(dataType) != sizeof(mqtt::sample_type_t)) return false; if constexpr (std::is_same_v) { @@ -153,6 +163,22 @@ class SignalHelper return true; } + bool copyData(DataPacketPtr destination, const std::vector& source) const + { + const auto dataType = destination.getDataDescriptor().getSampleType(); + if (checkType(dataType) && getSampleSize(dataType) != sizeof(mqtt::sample_type_t)) + return false; + if constexpr (std::is_same_v, std::string>) + { + return false; + } + else + { + memcpy(destination.getRawData(), source.data(), source.size() * sizeof(T)); + } + return true; + } + T generateData(size_t i) const { T sampleData; @@ -204,13 +230,13 @@ class MqttPublisherFbHelper : public DaqTestHelper void CreatePublisherFB() { - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); config.setPropertyValue(PROPERTY_NAME_PUB_QOS, 2); - fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME, config); + config.setPropertyValue(PROPERTY_NAME_PUB_PREVIEW_SIGNAL, True); + fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config); } void CreatePublisherFB(bool multiTopic, - bool sharedTs, bool groupV, bool useSignalNames, const std::string& topicName, @@ -218,21 +244,21 @@ class MqttPublisherFbHelper : public DaqTestHelper int qos = 2, uint32_t readPeriod = 20) { - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, multiTopic ? 1 : 0); - config.setPropertyValue(PROPERTY_NAME_PUB_SHARED_TS, sharedTs ? True : False); config.setPropertyValue(PROPERTY_NAME_PUB_GROUP_VALUES, groupV ? True : False); - config.setPropertyValue(PROPERTY_NAME_PUB_USE_SIGNAL_NAMES, useSignalNames ? True : False); + config.setPropertyValue(PROPERTY_NAME_PUB_VALUE_FIELD_NAME, useSignalNames ? 2 : 0); config.setPropertyValue(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE, valuePackSize); config.setPropertyValue(PROPERTY_NAME_PUB_QOS, qos); config.setPropertyValue(PROPERTY_NAME_PUB_READ_PERIOD, readPeriod); config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_NAME, topicName); - fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME, config); + config.setPropertyValue(PROPERTY_NAME_PUB_PREVIEW_SIGNAL, True); + fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config); } - bool CreateSubscriber() + bool CreateSubscriber(std::string postfix = "_subscriberId") { - subscriber = std::make_unique(buildClientId("_subscriberId")); + subscriber = std::make_unique(buildClientId(postfix)); return subscriber->connect("127.0.0.1"); } @@ -319,6 +345,58 @@ class MqttPublisherFbHelper : public DaqTestHelper return messages; } + template + std::vector + expectedMsgsForSharedTsArr(const std::string& signalName0, const std::string& signalName1, std::vector> data, size_t packSize) + { + std::vector msgs; + const std::string PUBLISHER_SINGLE_MSG = "{ : , : " + ", \"timestamp\" : }"; + + std::vector values; + std::vector tss; + + + for (size_t j = 0; j < data.size(); j += packSize) + { + std::ostringstream ossData; + std::ostringstream ossTs; + ossData << "["; + ossTs << "["; + for (size_t i = 0; i < packSize; ++i) + { + if (i > 0) + { + ossData << ", "; + ossTs << ", "; + } + const auto& [value, ts] = data[i + j]; + + ossData << valueToString(value, false); + ossTs << valueToString(ts * 1000, false); + } + ossData << "]"; + ossTs << "]"; + values.push_back(std::move(ossData).str()); + tss.push_back(std::move(ossTs).str()); + } + + std::vector messages; + { + for (size_t i = 0; i < values.size(); ++i) + { + auto msg = PUBLISHER_SINGLE_MSG; + msg = replacePlaceholder(msg, "", signalName0); + msg = replacePlaceholder(msg, "", signalName1); + msg = replacePlaceholder(msg, "", values[i], false); + msg = replacePlaceholder(msg, "", values[i], false); + msg = replacePlaceholder(msg, "", tss[i], false); + messages.push_back(std::move(msg)); + } + } + return messages; + } + template std::vector expectedMsgsForMultimsg(const std::string& signalName0, const std::string& signalName1, std::vector> data) @@ -354,32 +432,42 @@ class MqttPublisherFbHelper : public DaqTestHelper } template - static std::string replacePlaceholder(const std::string& jsonTemplate, const std::string& ph, const vT& value) + static std::string valueToString(const vT& value, bool quoteString = true) + { + std::string result; + if constexpr (std::is_same_v) + { + if (quoteString) + result = '"' + value + '"'; + else + result = value; + } + else if constexpr (std::is_same_v || std::is_same_v) + { + result = doubleToString(value, 6); + } + else + { + result = std::to_string(value); + } + return result; + } + + template + static std::string replacePlaceholder(const std::string& jsonTemplate, const std::string& ph, const vT& value, bool quoteString = true) { std::string result = jsonTemplate; size_t pos = result.find(ph); if (pos != std::string::npos) { - std::string replacement; - if constexpr (std::is_same_v) - { - replacement = '"' + value + '"'; - } - else if constexpr (std::is_same_v || std::is_same_v) - { - replacement = doubleToString(value, 6); - } - else - { - replacement = std::to_string(value); - } + std::string replacement = valueToString(value, quoteString); result.replace(pos, ph.length(), replacement); } return result; } template - static std::string replacePlaceholder(const std::string& jsonTemplate, const std::string& ph, const std::vector& values) + static std::string replacePlaceholder(const std::string& jsonTemplate, const std::string& ph, const std::vector& values, bool quoteString = true) { std::string result = jsonTemplate; size_t pos = result.find(ph); @@ -391,18 +479,7 @@ class MqttPublisherFbHelper : public DaqTestHelper { if (i > 0) replacement += ", "; - if constexpr (std::is_same_v) - { - replacement += '"' + values[i] + '"'; - } - else if constexpr (std::is_same_v || std::is_same_v) - { - replacement += doubleToString(values[i], 6); - } - else - { - replacement += std::to_string(values[i]); - } + replacement += valueToString(values[i], quoteString); } replacement += "]"; @@ -513,7 +590,7 @@ TEST_F(MqttPublisherFbTest, DefaultConfig) daq::DictPtr fbTypes; daq::FunctionBlockTypePtr fbt; daq::PropertyObjectPtr defaultConfig; - ASSERT_NO_THROW(fbTypes = rootMqttFb.getAvailableFunctionBlockTypes()); + ASSERT_NO_THROW(fbTypes = clientMqttFb.getAvailableFunctionBlockTypes()); ASSERT_NO_THROW(fbt = fbTypes.get(PUB_FB_NAME)); ASSERT_NO_THROW(defaultConfig = fbt.createDefaultConfig()); @@ -525,18 +602,13 @@ TEST_F(MqttPublisherFbTest, DefaultConfig) ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_PUB_TOPIC_MODE).getValueType(), CoreType::ctInt); ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE).asPtr(), 0u); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_PUB_SHARED_TS)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_PUB_SHARED_TS).getValueType(), CoreType::ctBool); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_PUB_SHARED_TS).asPtr(), False); - ASSERT_FALSE(defaultConfig.getProperty(PROPERTY_NAME_PUB_SHARED_TS).getVisible()); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_PUB_GROUP_VALUES)); ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_PUB_GROUP_VALUES).getValueType(), CoreType::ctBool); ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_PUB_GROUP_VALUES).asPtr(), False); - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_PUB_USE_SIGNAL_NAMES)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_PUB_USE_SIGNAL_NAMES).getValueType(), CoreType::ctBool); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_PUB_USE_SIGNAL_NAMES).asPtr(), False); + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_PUB_VALUE_FIELD_NAME)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_PUB_VALUE_FIELD_NAME).getValueType(), CoreType::ctInt); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_PUB_VALUE_FIELD_NAME).asPtr(), 0u); ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE)); ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE).getValueType(), CoreType::ctInt); @@ -555,6 +627,10 @@ TEST_F(MqttPublisherFbTest, DefaultConfig) ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_PUB_TOPIC_NAME).getValueType(), CoreType::ctString); ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_PUB_TOPIC_NAME).asPtr().toStdString(), ""); ASSERT_FALSE(defaultConfig.getProperty(PROPERTY_NAME_PUB_TOPIC_NAME).getVisible()); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_PUB_PREVIEW_SIGNAL)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_PUB_PREVIEW_SIGNAL).getValueType(), CoreType::ctBool); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_PUB_PREVIEW_SIGNAL).asPtr().getValue(False), False); } TEST_F(MqttPublisherFbTest, PropertyVisibility) @@ -563,24 +639,6 @@ TEST_F(MqttPublisherFbTest, PropertyVisibility) daq::FunctionBlockTypePtr fbt = MqttPublisherFbImpl::CreateType(); daq::PropertyObjectPtr defaultConfig = fbt.createDefaultConfig(); - ASSERT_FALSE(defaultConfig.getProperty(PROPERTY_NAME_PUB_SHARED_TS).getVisible()); - defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); // Set to Multi topic - ASSERT_TRUE(defaultConfig.getProperty(PROPERTY_NAME_PUB_SHARED_TS).getVisible()); - - defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 0); // Set to Single topic - defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_GROUP_VALUES, True); - ASSERT_TRUE(defaultConfig.getProperty(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE).getVisible()); - defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); // Set to Multi topic - ASSERT_FALSE(defaultConfig.getProperty(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE).getVisible()); - defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); // Set to Single topic - defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_GROUP_VALUES, True); - ASSERT_FALSE(defaultConfig.getProperty(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE).getVisible()); - - defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 0); // Set to Single topic - ASSERT_TRUE(defaultConfig.getProperty(PROPERTY_NAME_PUB_GROUP_VALUES).getVisible()); - defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); // Set to Multi topic - ASSERT_FALSE(defaultConfig.getProperty(PROPERTY_NAME_PUB_GROUP_VALUES).getVisible()); - defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 0); // Set to Single topic ASSERT_FALSE(defaultConfig.getProperty(PROPERTY_NAME_PUB_TOPIC_NAME).getVisible()); defaultConfig.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); // Set to Multi topic @@ -590,18 +648,22 @@ TEST_F(MqttPublisherFbTest, PropertyVisibility) TEST_F(MqttPublisherFbTest, Config) { StartUp(); - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); - config.setPropertyValue(PROPERTY_NAME_PUB_SHARED_TS, True); config.setPropertyValue(PROPERTY_NAME_PUB_GROUP_VALUES, True); - config.setPropertyValue(PROPERTY_NAME_PUB_USE_SIGNAL_NAMES, True); + config.setPropertyValue(PROPERTY_NAME_PUB_VALUE_FIELD_NAME, 1); config.setPropertyValue(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE, 3); config.setPropertyValue(PROPERTY_NAME_PUB_QOS, 2); config.setPropertyValue(PROPERTY_NAME_PUB_READ_PERIOD, 100); config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_NAME, buildTopicName()); + config.setPropertyValue(PROPERTY_NAME_PUB_PREVIEW_SIGNAL, True); daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Warning", daqInstance.getContext().getTypeManager())); + SignalHelper helper; + fb.getInputPorts()[0].connect(helper.signal0); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), @@ -609,7 +671,7 @@ TEST_F(MqttPublisherFbTest, Config) static_cast(MqttPublisherFbImpl::SettingStatus::Valid), daqInstance.getContext().getTypeManager())); const auto allProperties = fb.getAllProperties(); - ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount()); + ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount() + 2); // +2 for Topics property, Schema property for (const auto& pror : config.getAllProperties()) { @@ -619,10 +681,10 @@ TEST_F(MqttPublisherFbTest, Config) } MqttPublisherFbImpl* ptr = reinterpret_cast(fb.getObject()); ASSERT_TRUE(ptr != nullptr); - EXPECT_EQ(ptr->getFbConfig().topicMode, TopicMode::Multi); - EXPECT_TRUE(ptr->getFbConfig().sharedTs); + EXPECT_EQ(ptr->getFbConfig().topicMode, TopicMode::Single); EXPECT_TRUE(ptr->getFbConfig().groupValues); - EXPECT_TRUE(ptr->getFbConfig().useSignalNames); + EXPECT_TRUE(ptr->getFbConfig().enablePreview); + EXPECT_EQ(ptr->getFbConfig().valueFieldName, SignalValueJSONKey::LocalID); EXPECT_EQ(ptr->getFbConfig().groupValuesPackSize, 3); EXPECT_EQ(ptr->getFbConfig().qos, 2); EXPECT_EQ(ptr->getFbConfig().periodMs, 100); @@ -632,7 +694,9 @@ TEST_F(MqttPublisherFbTest, Creation) { StartUp(); daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME)); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME)); + SignalHelper helper; + fb.getInputPorts()[0].connect(helper.signal0); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), @@ -644,9 +708,11 @@ TEST_F(MqttPublisherFbTest, Creation) TEST_F(MqttPublisherFbTest, TwoFbCreation) { StartUp(); + SignalHelper helper; { daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME)); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME)); + fb.getInputPorts()[0].connect(helper.signal0); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), @@ -656,7 +722,8 @@ TEST_F(MqttPublisherFbTest, TwoFbCreation) } { daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME)); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME)); + fb.getInputPorts()[0].connect(helper.signal0); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), @@ -664,7 +731,7 @@ TEST_F(MqttPublisherFbTest, TwoFbCreation) static_cast(MqttPublisherFbImpl::SettingStatus::Valid), daqInstance.getContext().getTypeManager())); } - auto fbs = rootMqttFb.getFunctionBlocks(); + auto fbs = clientMqttFb.getFunctionBlocks(); ASSERT_EQ(fbs.getCount(), 2u); } @@ -672,9 +739,13 @@ TEST_F(MqttPublisherFbTest, CreationWithDefaultConfig) { StartUp(); daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME)); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME)); auto signals = fb.getSignals(); ASSERT_EQ(signals.getCount(), 0u); + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Warning", daqInstance.getContext().getTypeManager())); + SignalHelper helper; + fb.getInputPorts()[0].connect(helper.signal0); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), @@ -688,8 +759,10 @@ TEST_F(MqttPublisherFbTest, CreationWithPartialConfig) StartUp(); daq::FunctionBlockPtr fb; auto config = PropertyObject(); - config.addProperty(BoolProperty(PROPERTY_NAME_PUB_USE_SIGNAL_NAMES, True)); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + config.addProperty(IntProperty(PROPERTY_NAME_PUB_READ_PERIOD, 20)); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + SignalHelper helper; + fb.getInputPorts()[0].connect(helper.signal0); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), @@ -704,11 +777,11 @@ TEST_F(MqttPublisherFbTest, ConnectToPort) StatusHelper::addTypesToTypeManager(MQTT_PUB_FB_SIG_STATUS_TYPE, MQTT_PUB_FB_SIG_STATUS_NAME, MqttPublisherFbImpl::signalStatusMap, - rootMqttFb.getContext().getTypeManager()); + clientMqttFb.getContext().getTypeManager()); StatusHelper::addTypesToTypeManager(MQTT_PUB_FB_PUB_STATUS_TYPE, MQTT_PUB_FB_PUB_STATUS_NAME, MqttPublisherFbImpl::publishingStatusMap, - rootMqttFb.getContext().getTypeManager()); + clientMqttFb.getContext().getTypeManager()); const auto sigStValid = EnumerationWithIntValue(MQTT_PUB_FB_SIG_STATUS_TYPE, static_cast(MqttPublisherFbImpl::SignalStatus::Valid), daqInstance.getContext().getTypeManager()); @@ -720,10 +793,11 @@ TEST_F(MqttPublisherFbTest, ConnectToPort) daqInstance.getContext().getTypeManager()); const auto comStOk = Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager()); const auto comStError = Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager()); + const auto comStWarn = Enumeration("ComponentStatusType", "Warning", daqInstance.getContext().getTypeManager()); { daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME)); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME)); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStNotConnected); auto help = SignalHelper(); @@ -733,15 +807,26 @@ TEST_F(MqttPublisherFbTest, ConnectToPort) ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStOk); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStValid); - fb.getInputPorts()[1].connect(help.signal0); + fb.getInputPorts()[1].connect(help.signal1); ASSERT_EQ(fb.getInputPorts().getCount(), 3u); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStOk); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStValid); + + fb.getInputPorts()[2].connect(help.signal0); + ASSERT_EQ(fb.getInputPorts().getCount(), 4u); + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStError); + ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStInvalid); // disconnection + fb.getInputPorts()[1].disconnect(); + ASSERT_EQ(fb.getInputPorts().getCount(), 3u); + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStError); + ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStInvalid); + fb.getInputPorts()[1].disconnect(); ASSERT_EQ(fb.getInputPorts().getCount(), 2u); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStOk); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStValid); + // connection without a domain signal fb.getInputPorts()[1].connect(help.signalWithoutDomain); ASSERT_EQ(fb.getInputPorts().getCount(), 3u); @@ -753,15 +838,16 @@ TEST_F(MqttPublisherFbTest, ConnectToPort) ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStOk); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStValid); fb.getInputPorts()[0].disconnect(); - ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStOk); + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStWarn); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStNotConnected); } { daq::FunctionBlockPtr fb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_PUB_SHARED_TS, True); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_NAME, String(buildTopicName())); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); auto help = SignalHelper(); auto signal0 = help.createSignal(DataDescriptorBuilder().setRule(LinearDataRule(2, 3)).setTickResolution(Ratio(1, 1000))); @@ -775,9 +861,10 @@ TEST_F(MqttPublisherFbTest, ConnectToPort) { daq::FunctionBlockPtr fb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_PUB_SHARED_TS, True); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_NAME, String(buildTopicName())); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); auto help = SignalHelper(); auto signal0 = help.createSignal(DataDescriptorBuilder().setRule(LinearDataRule(1, 3)).setTickResolution(Ratio(1, 1000))); @@ -794,9 +881,10 @@ TEST_F(MqttPublisherFbTest, ConnectToPort) { daq::FunctionBlockPtr fb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_PUB_SHARED_TS, True); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_NAME, String(buildTopicName())); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); auto help = SignalHelper(); auto signal0 = help.createSignal(DataDescriptorBuilder().setRule(LinearDataRule(2, 3)).setTickResolution(Ratio(1, 1000))); @@ -810,17 +898,135 @@ TEST_F(MqttPublisherFbTest, ConnectToPort) ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStOk); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStValid); } + + { + daq::FunctionBlockPtr fb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_NAME, String(buildTopicName())); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + auto help = SignalHelper(); + + auto signal0 = help.createSignal(DataDescriptorBuilder().setRule(LinearDataRule(1, 3)).setTickResolution(Ratio(1, 500))); + auto signal1 = help.createSignal(DataDescriptorBuilder().setRule(LinearDataRule(1, 3)).setTickResolution(Ratio(1, 500))); + signal0.setName("signal"); + signal1.setName("signal"); + fb.getInputPorts()[0].connect(signal0); + fb.getInputPorts()[1].connect(signal1); + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStOk); + ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStValid); + fb.setPropertyValue(PROPERTY_NAME_PUB_VALUE_FIELD_NAME, 2); // Set to SignalName + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStError); + ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStInvalid); + fb.getInputPorts()[0].disconnect(); + ASSERT_EQ(fb.getInputPorts().getCount(), 2u); + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), comStOk); + ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SIG_STATUS_NAME), sigStValid); + } +} + +TEST_F(MqttPublisherFbTest, PreviewSignals) +{ + StartUp(); + + daq::FunctionBlockPtr fb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 0); + config.setPropertyValue(PROPERTY_NAME_PUB_PREVIEW_SIGNAL, False); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + auto help = SignalHelper(); + + ASSERT_EQ(fb.getSignals().getCount(), 0u); + ASSERT_EQ(fb.getInputPorts().getCount(), 1u); + fb.getInputPorts()[0].connect(help.signal0); + ASSERT_EQ(fb.getSignals().getCount(), 0u); + ASSERT_EQ(fb.getInputPorts().getCount(), 2u); + fb.getInputPorts()[0].disconnect(); + ASSERT_EQ(fb.getSignals().getCount(), 0u); + ASSERT_EQ(fb.getInputPorts().getCount(), 1u); + fb.getInputPorts()[0].connect(help.signal0); + ASSERT_EQ(fb.getSignals().getCount(), 0u); + ASSERT_EQ(fb.getInputPorts().getCount(), 2u); + + fb.getInputPorts()[1].connect(help.signal0); + ASSERT_EQ(fb.getSignals().getCount(), 0u); + ASSERT_EQ(fb.getInputPorts().getCount(), 3u); + ASSERT_NO_THROW(fb.setPropertyValue(PROPERTY_NAME_PUB_PREVIEW_SIGNAL, True)); + ASSERT_EQ(fb.getSignals().getCount(), 2u); + ASSERT_EQ(fb.getInputPorts().getCount(), 3u); + ASSERT_NO_THROW(fb.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1)); + ASSERT_EQ(fb.getSignals().getCount(), 1u); + ASSERT_EQ(fb.getInputPorts().getCount(), 3u); + ASSERT_NO_THROW(fb.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 0)); + ASSERT_EQ(fb.getSignals().getCount(), 2u); + ASSERT_EQ(fb.getInputPorts().getCount(), 3u); + ASSERT_NO_THROW(fb.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1)); + + // disconnection + fb.getInputPorts()[1].disconnect(); + ASSERT_EQ(fb.getSignals().getCount(), 1u); + ASSERT_EQ(fb.getInputPorts().getCount(), 2u); + // disconnection + fb.getInputPorts()[0].disconnect(); + ASSERT_EQ(fb.getSignals().getCount(), 1u); + ASSERT_EQ(fb.getInputPorts().getCount(), 1u); + ASSERT_NO_THROW(fb.setPropertyValue(PROPERTY_NAME_PUB_PREVIEW_SIGNAL, False)); + ASSERT_EQ(fb.getSignals().getCount(), 0u); + ASSERT_EQ(fb.getInputPorts().getCount(), 1u); + ASSERT_NO_THROW(fb.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 0)); + fb.getInputPorts()[0].connect(help.signal0); + ASSERT_EQ(fb.getInputPorts().getCount(), 2u); + fb.getInputPorts()[1].connect(help.signal1); + ASSERT_EQ(fb.getInputPorts().getCount(), 3u); + fb.getInputPorts()[2].connect(help.signal0); + ASSERT_EQ(fb.getInputPorts().getCount(), 4u); + ASSERT_EQ(fb.getSignals().getCount(), 0u); + ASSERT_NO_THROW(fb.setPropertyValue(PROPERTY_NAME_PUB_PREVIEW_SIGNAL, True)); + ASSERT_EQ(fb.getSignals().getCount(), 3u); + ASSERT_EQ(fb.getInputPorts().getCount(), 4u); } +TEST_F(MqttPublisherFbTest, TopicsList) +{ + StartUp(); + + { + daq::FunctionBlockPtr fb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 0); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + auto help = SignalHelper(); + + ASSERT_NO_THROW(fb.getInputPorts()[0].connect(help.signal0)); + ASSERT_EQ(fb.getPropertyValue(PROPERTY_NAME_PUB_TOPICS).asPtr().getCount(), 1u); + ASSERT_NO_THROW(fb.getInputPorts()[1].connect(help.signal1)); + ASSERT_EQ(fb.getPropertyValue(PROPERTY_NAME_PUB_TOPICS).asPtr().getCount(), 2u); + ASSERT_TRUE(fb.getProperty(PROPERTY_NAME_PUB_TOPICS).getVisible()); + } + + { + daq::FunctionBlockPtr fb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + auto help = SignalHelper(); + + ASSERT_NO_THROW(fb.getInputPorts()[0].connect(help.signal0)); + ASSERT_EQ(fb.getPropertyValue(PROPERTY_NAME_PUB_TOPICS).asPtr().getCount(), 1u); + ASSERT_NO_THROW(fb.getInputPorts()[1].connect(help.signal1)); + ASSERT_EQ(fb.getPropertyValue(PROPERTY_NAME_PUB_TOPICS).asPtr().getCount(), 1u); + ASSERT_FALSE(fb.getProperty(PROPERTY_NAME_PUB_TOPICS).getVisible()); + } +} TEST_F(MqttPublisherFbTest, WrongConfig) { StartUp(); daq::FunctionBlockPtr fb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_PUB_SHARED_TS, True); + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(PUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); config.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_NAME, String("/test/#")); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(PUB_FB_NAME, config)); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(PUB_FB_NAME, config)); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), @@ -828,7 +1034,9 @@ TEST_F(MqttPublisherFbTest, WrongConfig) static_cast(MqttPublisherFbImpl::SettingStatus::Invalid), daqInstance.getContext().getTypeManager())); - fb.setPropertyValue(PROPERTY_NAME_PUB_SHARED_TS, False); + fb.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 0); + SignalHelper helper; + fb.getInputPorts()[0].connect(helper.signal0); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), @@ -836,7 +1044,7 @@ TEST_F(MqttPublisherFbTest, WrongConfig) static_cast(MqttPublisherFbImpl::SettingStatus::Valid), daqInstance.getContext().getTypeManager())); - fb.setPropertyValue(PROPERTY_NAME_PUB_SHARED_TS, True); + fb.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_MODE, 1); fb.setPropertyValue(PROPERTY_NAME_PUB_TOPIC_NAME, String("/test/+/test")); ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); @@ -854,7 +1062,74 @@ TEST_F(MqttPublisherFbTest, WrongConfig) daqInstance.getContext().getTypeManager())); } -TEST_P(MqttPublisherFbPTest, TransferSingle) +TEST_F(MqttPublisherFbTest, TransferSingle0) +{ + const size_t sampleCnt0 = 14; + const size_t sampleCnt1 = sampleCnt0 * 2; + SignalHelper helpDouble{}; + SignalHelper helpUint{}; + StartUp(); + + ASSERT_NO_THROW(CreatePublisherFB()); + fb.getInputPorts()[0].connect(helpDouble.signal0); + fb.getInputPorts()[1].connect(helpUint.signal1); + + auto signalList = List(); + fb->getSignals(&signalList); + auto reader0 = daq::PacketReader(signalList[0]); + auto reader1 = daq::PacketReader(signalList[1]); + std::vector dataToReceive0; + std::vector dataToReceive1; + ASSERT_TRUE(CreateSubscriber("_0")); + + const auto data0 = helpDouble.generateTestData(sampleCnt0); + const std::vector messages0 = expectedMsgsForSingle(helpDouble.signal0.getGlobalId().toStdString(), data0); + const std::string topic0 = helpDouble.signal0.getGlobalId().toStdString(); + auto ok = transfer(topic0, messages0, helpDouble, data0); + ASSERT_TRUE(ok); + + ASSERT_TRUE(CreateSubscriber("_1")); + const auto data1 = helpUint.generateTestData(sampleCnt1); + const std::vector messages1 = expectedMsgsForSingle(helpUint.signal1.getGlobalId().toStdString(), data1); + const std::string topic1 = helpUint.signal1.getGlobalId().toStdString(); + ok = transfer(topic1, messages1, helpUint, data1); + ASSERT_TRUE(ok); + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), + EnumerationWithIntValue(MQTT_PUB_FB_SET_STATUS_TYPE, + static_cast(MqttPublisherFbImpl::SettingStatus::Valid), + daqInstance.getContext().getTypeManager())); + ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_PUB_STATUS_NAME), + EnumerationWithIntValue(MQTT_PUB_FB_PUB_STATUS_TYPE, + static_cast(MqttPublisherFbImpl::PublishingStatus::Ok), + daqInstance.getContext().getTypeManager())); + + auto lambdaReader = [](PacketReaderPtr reader, const std::vector& messages) + { + std::vector dataToReceive; + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.emplace_back(readData.cbegin(), readData.cend()); + } + } + ASSERT_EQ(messages.size(), dataToReceive.size()); + ASSERT_EQ(messages, dataToReceive); + }; + lambdaReader(reader0, messages0); + lambdaReader(reader1, messages1); +} + +TEST_P(MqttPublisherFbPTest, TransferSingle1) { const size_t sampleCnt = 15; H param = GetParam(); @@ -864,8 +1139,12 @@ TEST_P(MqttPublisherFbPTest, TransferSingle) StartUp(); ASSERT_NO_THROW(CreatePublisherFB()); - fb.getInputPorts()[0].connect(help.signal0); + + auto signalList = List(); + fb->getSignals(&signalList); + auto reader = daq::PacketReader(signalList[0]); + std::vector dataToReceive; ASSERT_TRUE(CreateSubscriber()); const auto data = help.generateTestData(sampleCnt); @@ -883,6 +1162,23 @@ TEST_P(MqttPublisherFbPTest, TransferSingle) EnumerationWithIntValue(MQTT_PUB_FB_PUB_STATUS_TYPE, static_cast(MqttPublisherFbImpl::PublishingStatus::Ok), daqInstance.getContext().getTypeManager())); + + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.emplace_back(readData.cbegin(), readData.cend()); + } + } + ASSERT_EQ(messages.size(), dataToReceive.size()); + ASSERT_EQ(messages, dataToReceive); }, param); } @@ -897,9 +1193,14 @@ TEST_P(MqttPublisherFbPTest, TransferSingleGroupValues) { StartUp(); - ASSERT_NO_THROW(CreatePublisherFB(false, false, true, false, buildTopicName(), packSize)); + ASSERT_NO_THROW(CreatePublisherFB(false, true, false, buildTopicName(), packSize)); fb.getInputPorts()[0].connect(help.signal0); + + auto signalList = List(); + fb->getSignals(&signalList); + auto reader = daq::PacketReader(signalList[0]); + std::vector dataToReceive; ASSERT_TRUE(CreateSubscriber()); const auto data = help.generateTestData(sampleCnt); @@ -918,6 +1219,23 @@ TEST_P(MqttPublisherFbPTest, TransferSingleGroupValues) EnumerationWithIntValue(MQTT_PUB_FB_PUB_STATUS_TYPE, static_cast(MqttPublisherFbImpl::PublishingStatus::Ok), daqInstance.getContext().getTypeManager())); + + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.emplace_back(readData.cbegin(), readData.cend()); + } + } + ASSERT_EQ(messages.size(), dataToReceive.size()); + ASSERT_EQ(messages, dataToReceive); }, param); } @@ -931,10 +1249,15 @@ TEST_P(MqttPublisherFbPTest, TransferSharedTs) { StartUp(); const std::string topic = buildTopicName(); - ASSERT_NO_THROW(CreatePublisherFB(true, true, false, false, topic)); + ASSERT_NO_THROW(CreatePublisherFB(true, false, false, topic)); fb.getInputPorts()[0].connect(help.signal0); fb.getInputPorts()[1].connect(help.signal1); + + auto signalList = List(); + fb->getSignals(&signalList); + auto reader = daq::PacketReader(signalList[0]); + std::vector dataToReceive; ASSERT_TRUE(CreateSubscriber()); const auto data = help.generateTestData(sampleCnt); @@ -952,11 +1275,85 @@ TEST_P(MqttPublisherFbPTest, TransferSharedTs) EnumerationWithIntValue(MQTT_PUB_FB_PUB_STATUS_TYPE, static_cast(MqttPublisherFbImpl::PublishingStatus::Ok), daqInstance.getContext().getTypeManager())); + + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.emplace_back(readData.cbegin(), readData.cend()); + } + } + ASSERT_EQ(messages.size(), dataToReceive.size()); + ASSERT_EQ(messages, dataToReceive); + }, + param); +} + +TEST_P(MqttPublisherFbPTest, TransferSharedTsArr) +{ + constexpr size_t sampleCnt = 15; + constexpr size_t packSize = 3; + H param = GetParam(); + std::visit( + [&](auto& help) + { + StartUp(); + const std::string topic = buildTopicName(); + ASSERT_NO_THROW(CreatePublisherFB(true, true, false, topic, packSize)); + + fb.getInputPorts()[0].connect(help.signal0); + fb.getInputPorts()[1].connect(help.signal1); + + auto signalList = List(); + fb->getSignals(&signalList); + auto reader = daq::PacketReader(signalList[0]); + std::vector dataToReceive; + ASSERT_TRUE(CreateSubscriber()); + + const auto data = help.generateTestData(sampleCnt); + const std::vector messages = + expectedMsgsForSharedTsArr(help.signal0.getGlobalId().toStdString(), help.signal1.getGlobalId().toStdString(), data, packSize); + auto ok = transfer(topic, messages, help, data); + ASSERT_TRUE(ok); + ASSERT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_SET_STATUS_NAME), + EnumerationWithIntValue(MQTT_PUB_FB_SET_STATUS_TYPE, + static_cast(MqttPublisherFbImpl::SettingStatus::Valid), + daqInstance.getContext().getTypeManager())); + ASSERT_EQ(fb.getStatusContainer().getStatus(MQTT_PUB_FB_PUB_STATUS_NAME), + EnumerationWithIntValue(MQTT_PUB_FB_PUB_STATUS_TYPE, + static_cast(MqttPublisherFbImpl::PublishingStatus::Ok), + daqInstance.getContext().getTypeManager())); + + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.emplace_back(readData.cbegin(), readData.cend()); + } + } + ASSERT_EQ(messages.size(), dataToReceive.size()); + ASSERT_EQ(messages, dataToReceive); }, param); } -TEST_P(MqttPublisherFbPTest, TransferMultimessage) +TEST_P(MqttPublisherFbPTest, DISABLED_TransferMultimessage) { constexpr size_t sampleCnt = 15; H param = GetParam(); @@ -965,7 +1362,7 @@ TEST_P(MqttPublisherFbPTest, TransferMultimessage) { StartUp(); const std::string topic = buildTopicName(); - ASSERT_NO_THROW(CreatePublisherFB(true, false, false, false, topic)); + ASSERT_NO_THROW(CreatePublisherFB(true, false, false, topic)); fb.getInputPorts()[0].connect(help.signal0); fb.getInputPorts()[1].connect(help.signal1); diff --git a/mqtt_streaming_module/tests/test_mqtt_streaming_module.cpp b/modules/mqtt_streaming_module/tests/test_mqtt_streaming_module.cpp similarity index 96% rename from mqtt_streaming_module/tests/test_mqtt_streaming_module.cpp rename to modules/mqtt_streaming_module/tests/test_mqtt_streaming_module.cpp index d82a71a..bfc7cc2 100644 --- a/mqtt_streaming_module/tests/test_mqtt_streaming_module.cpp +++ b/modules/mqtt_streaming_module/tests/test_mqtt_streaming_module.cpp @@ -65,8 +65,8 @@ TEST_F(MqttStreamingClientModuleTest, GetAvailableComponentTypes) DictPtr functionBlockTypes; ASSERT_NO_THROW(functionBlockTypes = module.getAvailableFunctionBlockTypes()); ASSERT_EQ(functionBlockTypes.getCount(), 1u); - ASSERT_TRUE(functionBlockTypes.hasKey(ROOT_FB_NAME)); - ASSERT_EQ(functionBlockTypes.get(ROOT_FB_NAME).getId(), ROOT_FB_NAME); + ASSERT_TRUE(functionBlockTypes.hasKey(CLIENT_FB_NAME)); + ASSERT_EQ(functionBlockTypes.get(CLIENT_FB_NAME).getId(), CLIENT_FB_NAME); DictPtr deviceTypes; ASSERT_NO_THROW(deviceTypes = module.getAvailableDeviceTypes()); diff --git a/modules/mqtt_streaming_module/tests/test_mqtt_subscriber_fb.cpp b/modules/mqtt_streaming_module/tests/test_mqtt_subscriber_fb.cpp new file mode 100644 index 0000000..c0b1028 --- /dev/null +++ b/modules/mqtt_streaming_module/tests/test_mqtt_subscriber_fb.cpp @@ -0,0 +1,606 @@ +#include "mqtt_streaming_module/mqtt_subscriber_fb_impl.h" +#include "test_daq_test_helper.h" +#include "test_data.h" +#include +#include +#include +#include +#include +#include +#include "MqttAsyncClientWrapper.h" +#include + +using namespace daq; +using namespace daq::modules::mqtt_streaming_module; + +namespace daq::modules::mqtt_streaming_module +{ +class MqttSubscriberFbHelper +{ +public: + std::unique_ptr obj; + + void CreateSubFB(std::string topic) + { + auto config = PropertyObject(); + config.addProperty(StringProperty(PROPERTY_NAME_SUB_TOPIC, "")); + config.addProperty(BoolProperty(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, False)); + const auto fbType = FunctionBlockType(SUB_FB_NAME, SUB_FB_NAME, "", config); + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, topic); + config.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, True); + obj = std::make_unique(NullContext(), nullptr, fbType, nullptr, config); + } + + auto getSignals() + { + auto signalList = List(); + obj->getSignals(&signalList); + return signalList; + } + + std::string buildTopicName(const std::string& postfix = "") + { + return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + postfix; + } + + std::string buildClientId() + { + return std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + "_ClientId"; + } + + void onSignalsMessage(mqtt::MqttMessage& msg) + { + mqtt::MqttAsyncClient unused; + obj->onSignalsMessage(unused, msg); + } +}; + +class MqttSubscriberFbTest : public testing::Test, public DaqTestHelper, public MqttSubscriberFbHelper +{ +}; + +class MqttSubscriberFbTopicPTest : public ::testing::TestWithParam>, + public DaqTestHelper, + public MqttSubscriberFbHelper +{ +}; + +class MqttSubscriberFbConfigPTest : public ::testing::TestWithParam, + public DaqTestHelper, + public MqttSubscriberFbHelper +{ +}; + +class MqttSubscriberFbConfigFilePTest : public ::testing::TestWithParam, + public DaqTestHelper, + public MqttSubscriberFbHelper +{ +}; +} // namespace daq::modules::mqtt_streaming_module + +TEST_F(MqttSubscriberFbTest, DefaultConfig) +{ + StartUp(); + daq::DictPtr fbTypes; + daq::FunctionBlockTypePtr fbt; + daq::PropertyObjectPtr defaultConfig; + ASSERT_NO_THROW(fbTypes = clientMqttFb.getAvailableFunctionBlockTypes()); + ASSERT_NO_THROW(fbt = fbTypes.get(SUB_FB_NAME)); + ASSERT_NO_THROW(defaultConfig = fbt.createDefaultConfig()); + + ASSERT_TRUE(defaultConfig.assigned()); + + EXPECT_EQ(defaultConfig.getAllProperties().getCount(), 6u); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SUB_JSON_CONFIG)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SUB_JSON_CONFIG).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG).asPtr().getLength(), 0u); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SUB_JSON_CONFIG_FILE)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SUB_JSON_CONFIG_FILE).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG_FILE).asPtr().getLength(), 0u); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SUB_TOPIC)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SUB_TOPIC).getValueType(), CoreType::ctString); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SUB_TOPIC).asPtr().getLength(), 0u); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SUB_QOS)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SUB_QOS).getValueType(), CoreType::ctInt); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SUB_QOS).asPtr().getValue(DEFAULT_SUB_QOS), DEFAULT_SUB_QOS); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SUB_PREVIEW_SIGNAL)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SUB_PREVIEW_SIGNAL).getValueType(), CoreType::ctBool); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL).asPtr().getValue(False), False); + + ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING)); + ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING).getValueType(), CoreType::ctBool); + ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING).asPtr().getValue(False), False); +} + +TEST_F(MqttSubscriberFbTest, PropertyVisibility) +{ + daq::DictPtr fbTypes; + daq::FunctionBlockTypePtr fbt = MqttSubscriberFbImpl::CreateType(); + daq::PropertyObjectPtr defaultConfig = fbt.createDefaultConfig(); + + defaultConfig.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, True); + ASSERT_TRUE(defaultConfig.getProperty(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING).getVisible()); + defaultConfig.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, False); + ASSERT_FALSE(defaultConfig.getProperty(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING).getVisible()); +} + +TEST_F(MqttSubscriberFbTest, Config) +{ + StartUp(); + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, buildTopicName()); + daq::FunctionBlockPtr subFb; + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + + const auto allProperties = subFb.getAllProperties(); + ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount()); + + for (const auto& pror : config.getAllProperties()) + { + const auto propName = pror.getName(); + ASSERT_TRUE(subFb.hasProperty(propName)); + ASSERT_EQ(subFb.getPropertyValue(propName), config.getPropertyValue(propName)); + } +} + +TEST_F(MqttSubscriberFbTest, CreationWithDefaultConfig) +{ + StartUp(); + daq::FunctionBlockPtr subFb; + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME)); + EXPECT_EQ(subFb.getSignals().getCount(), 0u); + ASSERT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); +} + +TEST_F(MqttSubscriberFbTest, CreationWithPartialConfig) +{ + // If FB has only one property, partial config is equivalent to custom config + StartUp(); + daq::FunctionBlockPtr subFb; + auto config = PropertyObject(); + config.addProperty(StringProperty(PROPERTY_NAME_SUB_TOPIC, String(buildTopicName()))); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + EXPECT_EQ(subFb.getSignals().getCount(), 0u); + ASSERT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); +} + +TEST_F(MqttSubscriberFbTest, CreationWithCustomConfig) +{ + // If FB has only one property, partial config is equivalent to custom config + StartUp(); + daq::FunctionBlockPtr subFb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, True); + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, buildTopicName()); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + EXPECT_EQ(subFb.getSignals().getCount(), 1u); + ASSERT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); +} + +TEST_F(MqttSubscriberFbTest, PreviewSignal) +{ + StartUp(); + daq::FunctionBlockPtr subFb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, True); + config.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING, False); + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, buildTopicName()); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + ASSERT_EQ(subFb.getSignals().getCount(), 1u); + EXPECT_EQ(subFb.getSignals()[0].getDescriptor().getSampleType(), daq::SampleType::Binary); + subFb.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL_IS_STRING, True); + EXPECT_EQ(subFb.getSignals()[0].getDescriptor().getSampleType(), daq::SampleType::String); +} + +TEST_F(MqttSubscriberFbTest, SubscriptionStatusWaitingForData) +{ + StartUp(); + + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, buildTopicName()); + daq::FunctionBlockPtr subFb; + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + ASSERT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); +} + +TEST_P(MqttSubscriberFbTopicPTest, CheckSubscriberFbTopic) +{ + auto [topic, result] = GetParam(); + StartUp(); + + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, topic); + config.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, True); + daq::FunctionBlockPtr fb; + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + auto signals = fb.getSignals(); + ASSERT_EQ(signals.getCount(), 1); + const auto expectedComponentStatus = result ? "Ok" : "Error"; + EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", expectedComponentStatus, daqInstance.getContext().getTypeManager())); +} + +INSTANTIATE_TEST_SUITE_P(TopicTest, + MqttSubscriberFbTopicPTest, + ::testing::Values(std::make_pair("", false), + std::make_pair("goodTopic/test", true), + std::make_pair("/goodTopic/test0", true), + std::make_pair("badTopic/+/test/topic", false), + std::make_pair("badTopic/+/+/topic", false), + std::make_pair("badTopic/#", false))); + +TEST_F(MqttSubscriberFbTest, RemovingNestedFunctionBlock) +{ + StartUp(); + daq::FunctionBlockPtr subFb; + { + auto config = PropertyObject(); + config.addProperty(StringProperty(PROPERTY_NAME_SUB_TOPIC, String(buildTopicName()))); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + ASSERT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + } + daq::FunctionBlockPtr jsonDecoderFb; + { + auto config = PropertyObject(); + config.addProperty(StringProperty(PROPERTY_NAME_DEC_VALUE_NAME, String("temp"))); + ASSERT_NO_THROW(jsonDecoderFb = subFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); + } + ASSERT_EQ(subFb.getFunctionBlocks().getCount(), 1u); + + ASSERT_NO_THROW(subFb.removeFunctionBlock(jsonDecoderFb)); + ASSERT_EQ(subFb.getFunctionBlocks().getCount(), 0u); +} + +TEST_F(MqttSubscriberFbTest, TwoFbCreation) +{ + StartUp(); + { + daq::FunctionBlockPtr fb; + auto config = PropertyObject(); + config.addProperty(StringProperty(PROPERTY_NAME_SUB_TOPIC, buildTopicName("0"))); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + } + { + daq::FunctionBlockPtr fb; + auto config = PropertyObject(); + config.addProperty(StringProperty(PROPERTY_NAME_SUB_TOPIC, buildTopicName("1"))); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + } + auto fbs = clientMqttFb.getFunctionBlocks(); + ASSERT_EQ(fbs.getCount(), 2u); +} + +TEST_F(MqttSubscriberFbTest, PropertyChanged) +{ + StartUp(); + + daq::FunctionBlockPtr fb; + auto config = PropertyObject(); + auto topic = buildTopicName("0"); + config.addProperty(StringProperty(PROPERTY_NAME_SUB_TOPIC, topic)); + ASSERT_NO_THROW(fb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + auto subFb = reinterpret_cast(*fb); + + ASSERT_EQ(topic, subFb->getSubscribedTopic()); + topic = buildTopicName("1"); + fb.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, topic); + ASSERT_EQ(topic, subFb->getSubscribedTopic()); +} + +TEST_F(MqttSubscriberFbTest, JsonInit0) +{ + StartUp(); + daq::FunctionBlockPtr subFb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG, String(VALID_JSON_1_TOPIC_0)); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + ASSERT_EQ(subFb.getFunctionBlocks().getCount(), 3u); + ASSERT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + auto lambda = [&](FunctionBlockPtr nestedFb, std::string value, std::string ts, std::string symbol) + { + EXPECT_EQ(nestedFb.getSignals()[0].getName().toStdString(), value); + if (!symbol.empty()) + EXPECT_EQ(nestedFb.getSignals()[0].getDescriptor().getUnit().getSymbol().toStdString(), symbol); + EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_DEC_VALUE_NAME).asPtr().toStdString(), value); + EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_DEC_TS_NAME).asPtr().toStdString(), ts); + }; + EXPECT_EQ(subFb.getPropertyValue(PROPERTY_NAME_SUB_TOPIC).asPtr().toStdString(), "openDAQ/RefDev0/IO/AI/RefCh0/Sig/AI0"); + + lambda(subFb.getFunctionBlocks()[0], "value", "timestamp", "V"); + lambda(subFb.getFunctionBlocks()[1], "value1", "", ""); + lambda(subFb.getFunctionBlocks()[2], "value2", "", "W"); + +} + +TEST_F(MqttSubscriberFbTest, JsonInit1) +{ + StartUp(); + daq::FunctionBlockPtr subFb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG, String(VALID_JSON_1_TOPIC_1)); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + ASSERT_EQ(subFb.getFunctionBlocks().getCount(), 3u); + ASSERT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + auto lambda = [&](FunctionBlockPtr nestedFb, std::string value, std::string ts, std::string symbol) + { + EXPECT_EQ(nestedFb.getSignals()[0].getName().toStdString(), value); + if (!symbol.empty()) + EXPECT_EQ(nestedFb.getSignals()[0].getDescriptor().getUnit().getSymbol().toStdString(), symbol); + EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_DEC_VALUE_NAME).asPtr().toStdString(), value); + EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_DEC_TS_NAME).asPtr().toStdString(), ts); + }; + EXPECT_EQ(subFb.getPropertyValue(PROPERTY_NAME_SUB_TOPIC).asPtr().toStdString(), "/mirip/UNet3AC2/sensor/data"); + + lambda(subFb.getFunctionBlocks()[0], "temp", "ts", "°C"); + lambda(subFb.getFunctionBlocks()[1], "humi", "ts", "%"); + lambda(subFb.getFunctionBlocks()[2], "tds_value", "ts", "ppm"); + +} + +TEST_P(MqttSubscriberFbConfigPTest, JsonWrongInit) +{ + const auto configJson = GetParam(); + StartUp(); + daq::FunctionBlockPtr subFb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG, String(configJson)); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + EXPECT_EQ(subFb.getFunctionBlocks().getCount(), 0u); + EXPECT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); + EXPECT_EQ(subFb.getPropertyValue(PROPERTY_NAME_SUB_TOPIC).asPtr().toStdString(), ""); +} + +INSTANTIATE_TEST_SUITE_P( + JsonConfigTest, + MqttSubscriberFbConfigPTest, + ::testing::Values( + VALID_JSON_3_TOPIC_2, + WILDCARD_JSON_0, + WILDCARD_JSON_1, + INVALID_JSON_1, + INVALID_JSON_3)); + +TEST_P(MqttSubscriberFbConfigFilePTest, JsonInitFromFile) +{ + const auto configJson = GetParam(); + StartUp(); + daq::FunctionBlockPtr subFb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG_FILE, String(configJson)); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + ASSERT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); +} + +INSTANTIATE_TEST_SUITE_P(JsonConfigTest, + MqttSubscriberFbConfigFilePTest, + ::testing::Values("data/public-example0.json", + "data/public-example1.json", + "data/public-example2.json", + "data/public-example3.json")); + +TEST_F(MqttSubscriberFbTest, JsonInitFromFileWithChecking) +{ + StartUp(); + daq::FunctionBlockPtr subFb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG_FILE, String("data/public-example0.json")); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + ASSERT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + ASSERT_EQ(subFb.getFunctionBlocks().getCount(), 3u); + auto lambda = [&](FunctionBlockPtr nestedFb, std::string value, std::string ts, std::string symbol) + { + EXPECT_EQ(nestedFb.getSignals()[0].getName().toStdString(), value); + if (!symbol.empty()) + EXPECT_EQ(nestedFb.getSignals()[0].getDescriptor().getUnit().getSymbol().toStdString(), symbol); + EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_DEC_VALUE_NAME).asPtr().toStdString(), value); + EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_DEC_TS_NAME).asPtr().toStdString(), ts); + }; + EXPECT_EQ(subFb.getPropertyValue(PROPERTY_NAME_SUB_TOPIC).asPtr().toStdString(), "/mirip/UNet3AC2/sensor/data"); + + lambda(subFb.getFunctionBlocks()[0], "temp", "ts", "°C"); + lambda(subFb.getFunctionBlocks()[1], "humi", "ts", "%"); + lambda(subFb.getFunctionBlocks()[2], "tds_value", "ts", "ppm"); + +} + +TEST_F(MqttSubscriberFbTest, JsonInitFromFileWrongPath) +{ + StartUp(); + daq::FunctionBlockPtr subFb; + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_JSON_CONFIG_FILE, String("/justWrongPath/wrongFile.txt")); + ASSERT_NO_THROW(subFb = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config)); + EXPECT_EQ(subFb.getFunctionBlocks().getCount(), 0u); + EXPECT_EQ(subFb.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); + EXPECT_EQ(subFb.getPropertyValue(PROPERTY_NAME_SUB_TOPIC).asPtr().toStdString(), ""); +} + +TEST_F(MqttSubscriberFbTest, DataTransfer) +{ + const auto topic = buildTopicName(); + const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, + std::vector{0x11, 0x12, 0x13, 0x14}, + std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, + std::vector{0x31}, + std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; + std::vector> dataToReceive; + + CreateSubFB({topic}); + + auto signalList = List(); + obj->getSignals(&signalList); + auto reader = daq::PacketReader(signalList[0]); + + for (const auto& data : dataToSend) + { + mqtt::MqttMessage msg = {topic, data, 1, 0}; + onSignalsMessage(msg); + } + + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.push_back(std::move(readData)); + } + } + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_EQ(dataToSend, dataToReceive); +} + +TEST_F(MqttSubscriberFbTest, CheckRawFbFullDataTransfer) +{ + const std::string topic = buildTopicName(); + + StartUp(); + + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, topic); + config.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, True); + auto singal = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config).getSignals()[0]; + auto reader = daq::PacketReader(singal); + + MqttAsyncClientWrapper publisher("testPublisherId"); + ASSERT_TRUE(publisher.connect("127.0.0.1")); + + const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, + std::vector{0x11, 0x12, 0x13, 0x14}, + std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, + std::vector{0x31}, + std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; + std::vector> dataToReceive; + + for (const auto& data : dataToSend) + { + mqtt::MqttMessage msg = {topic, data, 1, 0}; + ASSERT_TRUE(publisher.publishMsg(msg)); + } + helper::utils::Timer tmr(3000, true); + while ((!reader.getEmpty() || !tmr.expired()) && dataToReceive.size() != dataToSend.size()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.push_back(std::move(readData)); + } + } + + ASSERT_EQ(dataToSend.size(), dataToReceive.size()); + ASSERT_EQ(dataToSend, dataToReceive); +} + +TEST_F(MqttSubscriberFbTest, CheckRawFbFullDataTransferWithReconfiguring) +{ + const std::string topic0 = buildTopicName("0"); + const std::string topic1 = buildTopicName("1"); + const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, + std::vector{0x11, 0x12, 0x13, 0x14}}; + std::vector> dataToReceive; + + StartUp(); + + auto config = clientMqttFb.getAvailableFunctionBlockTypes().get(SUB_FB_NAME).createDefaultConfig(); + config.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, topic0); + config.setPropertyValue(PROPERTY_NAME_SUB_PREVIEW_SIGNAL, True); + auto rawFB = clientMqttFb.addFunctionBlock(SUB_FB_NAME, config); + auto singal = rawFB.getSignals()[0]; + auto reader = daq::PacketReader(singal); + + MqttAsyncClientWrapper publisher("testPublisherId"); + ASSERT_TRUE(publisher.connect("127.0.0.1")); + EXPECT_NE(rawFB.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Waiting for data"), std::string::npos); + + mqtt::MqttMessage msg = {topic0, dataToSend[0], 2, 0}; + ASSERT_TRUE(publisher.publishMsg(msg)); + + auto readerLambda = [&reader, &dataToReceive]() + { + while (!reader.getEmpty()) + { + auto packet = reader.read(); + if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) + { + continue; + } + if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) + { + std::vector readData(dataPacket.getDataSize()); + memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); + dataToReceive.push_back(std::move(readData)); + } + } + }; + helper::utils::Timer tmr(1000, true); + + bool hasData = false; + while (tmr.expired() == false && hasData == false) + hasData = (rawFB.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Data has been received") != std::string::npos); + + + EXPECT_TRUE(hasData); + + readerLambda(); + ASSERT_EQ(dataToReceive.size(), 1u); + ASSERT_EQ(dataToSend[0], dataToReceive[0]); + + dataToReceive.clear(); + + ASSERT_NO_THROW(rawFB.setPropertyValue(PROPERTY_NAME_SUB_TOPIC, topic1)); + EXPECT_EQ(rawFB.getStatusContainer().getStatus("ComponentStatus"), + Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); + EXPECT_NE(rawFB.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Waiting for data"), std::string::npos); + + msg = {topic1, dataToSend[1], 2, 0}; + ASSERT_TRUE(publisher.publishMsg(msg)); + tmr.restart(); + + hasData = false; + while (tmr.expired() == false && hasData == false) + hasData = (rawFB.getStatusContainer().getStatusMessage("ComponentStatus").toStdString().find("Data has been received") != std::string::npos); + + EXPECT_TRUE(hasData); + + readerLambda(); + ASSERT_EQ(dataToReceive.size(), 1u); + ASSERT_EQ(dataToSend[1], dataToReceive[0]); +} + diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/constants.h b/mqtt_streaming_module/include/mqtt_streaming_module/constants.h deleted file mode 100644 index 779f290..0000000 --- a/mqtt_streaming_module/include/mqtt_streaming_module/constants.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include "common.h" - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE - -static const char* MODULE_NAME = "OpenDaqMqttModule"; -static const char* MODULE_ID = "OpenDaqMqttModule"; -static const char* SHORT_MODULE_NAME = "MqttModule"; - -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_TIMEOUT = 3000; // ms - -static constexpr uint32_t DEFAULT_PUB_READ_PERIOD = 20; // ms -static constexpr uint32_t DEFAULT_PUB_QOS = 1; -static constexpr uint32_t DEFAULT_SUB_QOS = 1; -static constexpr uint32_t DEFAULT_PUB_PACK_SIZE = 1; - -static constexpr const char* DEFAULT_SIGNAL_NAME = "mqttValueSignal"; - -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_CONNECT_TIMEOUT = "ConnectTimeout"; -static constexpr const char* PROPERTY_NAME_SIGNAL_LIST = "SignalList"; -static constexpr const char* PROPERTY_NAME_JSON_CONFIG = "JsonConfig"; -static constexpr const char* PROPERTY_NAME_JSON_CONFIG_FILE = "JsonConfigFile"; -static constexpr const char* PROPERTY_NAME_TOPIC = "Topic"; -static constexpr const char* PROPERTY_NAME_VALUE_NAME = "ValueName"; -static constexpr const char* PROPERTY_NAME_TS_NAME = "TimestampName"; -static constexpr const char* PROPERTY_NAME_UNIT = "Unit"; -static constexpr const char* PROPERTY_NAME_SIGNAL_NAME = "SignalName"; - - -static constexpr const char* PROPERTY_NAME_PUB_TOPIC_MODE = "TopicMode"; -static constexpr const char* PROPERTY_NAME_PUB_TOPIC_NAME = "Topic"; -static constexpr const char* PROPERTY_NAME_PUB_SHARED_TS = "SharedTimestamp"; -static constexpr const char* PROPERTY_NAME_PUB_GROUP_VALUES = "GroupValues"; -static constexpr const char* PROPERTY_NAME_PUB_USE_SIGNAL_NAMES = "UseSignalNames"; -static constexpr const char* PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE = "GroupValuesPackSize"; -static constexpr const char* PROPERTY_NAME_PUB_QOS = "MqttQoS"; -static constexpr const char* PROPERTY_NAME_SUB_QOS = "MqttQoS"; -static constexpr const char* PROPERTY_NAME_PUB_READ_PERIOD = "ReaderPeriod"; - -static constexpr const char* RAW_FB_NAME = "RawSubscriberMqttFb"; -static constexpr const char* JSON_FB_NAME = "JsonSubscriberMqttFb"; -static constexpr const char* PUB_FB_NAME = "PublisherMqttFb"; -static constexpr const char* ROOT_FB_NAME = "RootMqttFb"; -static constexpr const char* JSON_DECODER_FB_NAME = "JsonDecoderMqttFb"; - -static const char* MQTT_LOCAL_ROOT_FB_ID_PREFIX = "RootMqttFb"; -static const char* MQTT_LOCAL_PUB_FB_ID_PREFIX = "PublisherMqttFb"; -static const char* MQTT_LOCAL_RAW_FB_ID_PREFIX = "RawSubscriberMqttFb"; -static const char* MQTT_LOCAL_JSON_FB_ID_PREFIX = "JsonSubscriberMqttFb"; -static const char* MQTT_LOCAL_JSON_DECODER_FB_ID_PREFIX = "JsonDecoderMqttFb"; - - -static const char* MQTT_ROOT_FB_CON_STATUS_TYPE = "BrokerConnectionStatusType"; -static const char* MQTT_FB_SUB_STATUS_TYPE = "MqttSubscriptionStatusType"; -static const char* MQTT_PUB_FB_SIG_STATUS_TYPE = "MqttSignalStatusType"; -static const char* MQTT_PUB_FB_PUB_STATUS_TYPE = "MqttPublishingStatusType"; -static const char* MQTT_FB_PARSING_STATUS_TYPE = "MqttParsingStatusType"; -static const char* MQTT_PUB_FB_SET_STATUS_TYPE = "MqttSettingStatusType"; - - -static const char* MQTT_ROOT_FB_CON_STATUS_NAME = "ConnectionStatus"; -static const char* MQTT_PUB_FB_SIG_STATUS_NAME = "SignalStatus"; -static const char* MQTT_PUB_FB_PUB_STATUS_NAME = "PublishingStatus"; -static const char* MQTT_FB_SUB_STATUS_NAME = "SubscriptionStatus"; -static const char* MQTT_FB_PARSING_STATUS_NAME = "ParsingStatus"; -static const char* MQTT_PUB_FB_SET_STATUS_NAME = "SettingStatus"; - -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_base_fb.h b/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_base_fb.h deleted file mode 100644 index 25cf036..0000000 --- a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_base_fb.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2022-2025 openDAQ d.o.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include - -#include "MqttAsyncClient.h" -#include "mqtt_streaming_module/constants.h" -#include "mqtt_streaming_module/status_helper.h" - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE - -class MqttBaseFb : public FunctionBlock -{ -public: - enum class SubscriptionStatus : EnumType - { - InvalidTopicName = 0, - SubscribingError, - WaitingForData, - HasData - }; - - struct CmdResult - { - bool success = false; - std::string msg; - int token = 0; - - CmdResult(bool success = false, const std::string& msg = "", int token = 0) - : success(success), - msg(msg), - token(token) - { - } - }; - - - explicit DAQ_MQTT_STREAM_MODULE_API MqttBaseFb(const ContextPtr& ctx, - const ComponentPtr& parent, - const FunctionBlockTypePtr& type, - const StringPtr& localId, - std::shared_ptr subscriber, - const PropertyObjectPtr& config = nullptr); - virtual ~MqttBaseFb() = default; - - virtual std::string getSubscribedTopic() const = 0; - -protected: - static std::vector> subscriptionStatusMap; - - std::shared_ptr subscriber; - StatusHelper subscriptionStatus; - int qos = DEFAULT_SUB_QOS; - - virtual void processMessage(const mqtt::MqttMessage& msg) = 0; - - void initProperties(const PropertyObjectPtr& config); - virtual void readProperties() = 0; - - DAQ_MQTT_STREAM_MODULE_API void onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg); - - virtual void clearSubscribedTopic() = 0; - CmdResult subscribeToTopic(); - CmdResult unsubscribeFromTopic(); - - virtual void propertyChanged() = 0; - - void removed() override; -}; - -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_raw_receiver_fb_impl.h b/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_raw_receiver_fb_impl.h deleted file mode 100644 index 4ed7b91..0000000 --- a/mqtt_streaming_module/include/mqtt_streaming_module/mqtt_raw_receiver_fb_impl.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE - -class MqttRawReceiverFbImpl final : public MqttBaseFb -{ - friend class MqttRawFbTest; - -public: - explicit DAQ_MQTT_STREAM_MODULE_API MqttRawReceiverFbImpl(const ContextPtr& ctx, - const ComponentPtr& parent, - const FunctionBlockTypePtr& type, - std::shared_ptr subscriber, - const PropertyObjectPtr& config = nullptr); - DAQ_MQTT_STREAM_MODULE_API ~MqttRawReceiverFbImpl() override; - - static FunctionBlockTypePtr CreateType(); - - std::string getSubscribedTopic() const override; -private: - std::mutex sync; - SignalConfigPtr outputSignal; - std::string topicForSubscribing; - static std::atomic localIndex; - - static std::string generateLocalId(); - - void createSignals(); - void clearSubscribedTopic() override; - - void processMessage(const mqtt::MqttMessage& msg) override; - void readProperties() override; - void propertyChanged() override; -}; - -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/include/mqtt_streaming_module/types.h b/mqtt_streaming_module/include/mqtt_streaming_module/types.h deleted file mode 100644 index 0f758ff..0000000 --- a/mqtt_streaming_module/include/mqtt_streaming_module/types.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE - -// std::vector> -using MqttDataSample = std::pair; -using MqttData = std::vector; - -enum class TopicMode { - Single = 0, - Multi, - _count -}; - -struct PublisherFbConfig { - TopicMode topicMode; - std::string topicName; - bool sharedTs; - bool groupValues; - bool useSignalNames; - size_t groupValuesPackSize; - int qos; - int periodMs; -}; - -struct SignalContext -{ - size_t index; - InputPortConfigPtr inputPort; - std::vector data; -}; - -struct ProcedureStatus -{ - bool success; - std::vector messages; - - void addError(const std::string& msg) - { - success = false; - messages.push_back(msg); - } -}; - -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/src/atomic_signal_sample_arr_handler.cpp b/mqtt_streaming_module/src/atomic_signal_sample_arr_handler.cpp deleted file mode 100644 index 6956312..0000000 --- a/mqtt_streaming_module/src/atomic_signal_sample_arr_handler.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include -#include -#include -#include -#include -#include - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE - -AtomicSignalSampleArrayHandler::AtomicSignalSampleArrayHandler(bool useSignalNames, size_t packSize) - : AtomicSignalAtomicSampleHandler(useSignalNames), - packSize(packSize > 0 ? packSize : 1) -{ -} - -MqttData AtomicSignalSampleArrayHandler::processSignalContext(SignalContext& signalContext) -{ - MqttData messages; - const auto conn = signalContext.inputPort.getConnection(); - if (!conn.assigned()) - return messages; - - PacketPtr packet = conn.dequeue(); - while (packet.assigned()) - { - if (packet.getType() == PacketType::Event) - { - auto eventPacket = packet.asPtr(true); - LOG_T("Processing {} event", eventPacket.getEventId()) - if (eventPacket.getEventId() == event_packet_id::DATA_DESCRIPTOR_CHANGED) - { - DataDescriptorPtr valueSignalDescriptor = eventPacket.getParameters().get(event_packet_param::DATA_DESCRIPTOR); - DataDescriptorPtr domainSignalDescriptor = eventPacket.getParameters().get(event_packet_param::DOMAIN_DATA_DESCRIPTOR); - processSignalDescriptorChanged(signalContext, valueSignalDescriptor, domainSignalDescriptor); - } - } - else if (packet.getType() == PacketType::Data) - { - signalContext.data.push_back(packet.asPtr()); - if (signalContext.data.size() >= packSize) - { - messages.emplace_back(processDataPackets(signalContext, signalContext.data)); - signalContext.data.clear(); - } - } - - packet = conn.dequeue(); - } - return messages; -} - -std::string AtomicSignalSampleArrayHandler::toString(const std::string valueFieldName, const std::vector& dataPackets) -{ - std::ostringstream dataOss; - std::ostringstream tsOss; - bool hasDomain = true; - dataOss << "["; - tsOss << "["; - for (size_t i = 0; i < dataPackets.size(); ++i) - { - if (i > 0) - { - dataOss << ", "; - tsOss << ", "; - } - - dataOss << HandlerBase::toString(dataPackets[i]); - - if (auto domainPacket = dataPackets[i].getDomainPacket(); domainPacket.assigned()) - { - uint64_t ts = convertToEpoch(domainPacket); - tsOss << std::to_string(ts); - } - else - { - hasDomain = false; - } - } - dataOss << "]"; - tsOss << "]"; - std::string result; - if (hasDomain) - result = fmt::format("{{\"{}\" : {}, \"timestamp\": {}}}", valueFieldName, dataOss.str(), tsOss.str()); - else - result = fmt::format("{{\"{}\" : {}}}", valueFieldName, dataOss.str()); - - return result; -} - -MqttDataSample AtomicSignalSampleArrayHandler::processDataPackets(SignalContext& signalContext, const std::vector& dataPacket) -{ - if (dataPacket.empty()) - return MqttDataSample{"", ""}; - const auto signal = signalContext.inputPort.getSignal(); - std::string valueFieldName = useSignalNames ? signal.getName().toStdString() : signal.getGlobalId().toStdString(); - auto msg = toString(valueFieldName, dataPacket); - std::string topic = buildTopicName(signalContext); - return MqttDataSample{topic, msg}; -} -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/src/mqtt_base_fb.cpp b/mqtt_streaming_module/src/mqtt_base_fb.cpp deleted file mode 100644 index d735c17..0000000 --- a/mqtt_streaming_module/src/mqtt_base_fb.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "mqtt_streaming_module/constants.h" -#include - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE - -constexpr int MQTT_FB_UNSUBSCRIBE_TOUT = 3000; - -std::vector> MqttBaseFb::subscriptionStatusMap = - {{SubscriptionStatus::InvalidTopicName, "InvalidTopicName"}, - {SubscriptionStatus::SubscribingError, "SubscribingError"}, - {SubscriptionStatus::WaitingForData, "WaitingForData"}, - {SubscriptionStatus::HasData, "HasData"}}; - -MqttBaseFb::MqttBaseFb(const ContextPtr& ctx, - const ComponentPtr& parent, - const FunctionBlockTypePtr& type, - const StringPtr& localId, - std::shared_ptr subscriber, - const PropertyObjectPtr& config) - : FunctionBlock(type, ctx, parent, localId), - subscriber(subscriber), - subscriptionStatus(MQTT_FB_SUB_STATUS_TYPE, - MQTT_FB_SUB_STATUS_NAME, - statusContainer, - subscriptionStatusMap, - SubscriptionStatus::InvalidTopicName, - context.getTypeManager()) -{ - initComponentStatus(); -} - -void MqttBaseFb::removed() -{ - FunctionBlock::removed(); - unsubscribeFromTopic(); -} - -void MqttBaseFb::onSignalsMessage(const mqtt::MqttAsyncClient& subscriber, const mqtt::MqttMessage& msg) -{ - processMessage(msg); -} - -void MqttBaseFb::initProperties(const PropertyObjectPtr& config) -{ - for (const auto& prop : config.getAllProperties()) - { - const auto propName = prop.getName(); - if (!objPtr.hasProperty(propName)) - { - if (const auto internalProp = prop.asPtrOrNull(true); internalProp.assigned()) - { - objPtr.addProperty(internalProp.clone()); - objPtr.setPropertyValue(propName, prop.getValue()); - objPtr.getOnPropertyValueWrite(prop.getName()) += - [this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) { propertyChanged(); }; - } - } - else - { - objPtr.setPropertyValue(propName, prop.getValue()); - } - } - readProperties(); -} - -MqttBaseFb::CmdResult MqttBaseFb::subscribeToTopic() -{ - MqttBaseFb::CmdResult result{false}; - if (subscriber) - { - auto lambda = [this](const mqtt::MqttAsyncClient &client, mqtt::MqttMessage &msg){this->onSignalsMessage(client, msg);}; - const auto topic = getSubscribedTopic(); - if (!topic.empty()) - { - LOG_I("Trying to subscribe to the topic : {}", topic); - subscriber->setMessageArrivedCb(topic, lambda); - if (auto subRes = subscriber->subscribe(topic, qos); subRes.success == false) - { - LOG_W("Failed to subscribe to the topic: \"{}\"; reason: {}", topic, subRes.msg); - setComponentStatusWithMessage(ComponentStatus::Warning, "Some topics failed to subscribe!"); - subscriptionStatus.setStatus(SubscriptionStatus::SubscribingError, "The reason: " + subRes.msg); - result = {false, "Failed to subscribe to the topic: \"" + topic + "\"; reason: " + subRes.msg}; - } - else - { - // subscriber->subscribe(...) is asynchronous. It puts command in queue and returns immediately. - LOG_D("Trying to subscribe to the topic: {}", topic); - setComponentStatus(ComponentStatus::Ok); - subscriptionStatus.setStatus(SubscriptionStatus::WaitingForData, "Topic: " + topic); - result = {true, "", result.token}; - } - } - else - { - result = {false, "Couldn't subscribe to an empty topic"}; - LOG_W("{}", result.msg); - } - } - else - { - const std::string msg = "MQTT subscriber client is not set!"; - setComponentStatusWithMessage(ComponentStatus::Error, msg); - subscriptionStatus.setStatus(SubscriptionStatus::SubscribingError, msg); - result = {false, msg}; - } - return result; -} - -MqttBaseFb::CmdResult MqttBaseFb::unsubscribeFromTopic() -{ - MqttBaseFb::CmdResult result{true}; - if (subscriber) - { - const auto topic = getSubscribedTopic(); - if (!topic.empty()) - { - subscriber->setMessageArrivedCb(topic, nullptr); - mqtt::CmdResult unsubRes = subscriber->unsubscribe(topic); - if (unsubRes.success) - unsubRes = subscriber->waitForCompletion(unsubRes.token, MQTT_FB_UNSUBSCRIBE_TOUT); - - if (unsubRes.success) - { - clearSubscribedTopic(); - LOG_I("The topic \'{}\' has been unsubscribed successfully", topic); - result = {true}; - } - else - { - const auto msg = fmt::format("Failed to unsubscribe from the topic \'{}\'; reason: {}", topic, unsubRes.msg); - LOG_W("{}", msg); - setComponentStatus(ComponentStatus::Warning); - subscriptionStatus.setStatus(SubscriptionStatus::SubscribingError, msg); - result = {false, msg}; - } - } - } - else - { - const std::string msg = "MQTT subscriber client is not set!"; - setComponentStatusWithMessage(ComponentStatus::Error, msg); - subscriptionStatus.setStatus(SubscriptionStatus::SubscribingError, msg); - result = {false, msg}; - } - return result; -} - -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/src/mqtt_json_receiver_fb_impl.cpp b/mqtt_streaming_module/src/mqtt_json_receiver_fb_impl.cpp deleted file mode 100644 index 341a2ac..0000000 --- a/mqtt_streaming_module/src/mqtt_json_receiver_fb_impl.cpp +++ /dev/null @@ -1,375 +0,0 @@ -#include "mqtt_streaming_module/constants.h" -#include -#include -#include -#include -#include -#include - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE - -std::atomic MqttJsonReceiverFbImpl::localIndex = 0; - -MqttJsonReceiverFbImpl::MqttJsonReceiverFbImpl(const ContextPtr& ctx, - const ComponentPtr& parent, - const FunctionBlockTypePtr& type, - std::shared_ptr subscriber, - const PropertyObjectPtr& config) - : MqttBaseFb(ctx, parent, type, generateLocalId(), subscriber, config), - jsonDataWorker(loggerComponent) -{ - initNestedFbTypes(); - if (config.assigned()) - initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); - else - initProperties(type.createDefaultConfig()); - if (topicForSubscribing.empty()) - { - readJsonConfig(); - } - createSignals(); - subscribeToTopic(); -} - -MqttJsonReceiverFbImpl::~MqttJsonReceiverFbImpl() -{ - unsubscribeFromTopic(); -} - -void MqttJsonReceiverFbImpl::initProperties(const PropertyObjectPtr& config) -{ - for (const auto& prop : config.getAllProperties()) - { - const auto propName = prop.getName(); - if (propName == PROPERTY_NAME_JSON_CONFIG || propName == PROPERTY_NAME_JSON_CONFIG_FILE) - { - if (!objPtr.hasProperty(propName)) - { - auto propClone = PropertyBuilder(prop.getName()) - .setValueType(prop.getValueType()) - .setDescription(prop.getDescription()) - .setDefaultValue(prop.getValue()) - .setVisible(false) - .setReadOnly(true) - .build(); - objPtr.addProperty(propClone); - } - } - else - { - if (!objPtr.hasProperty(propName)) - { - if (const auto internalProp = prop.asPtrOrNull(true); internalProp.assigned()) - { - objPtr.addProperty(internalProp.clone()); - objPtr.setPropertyValue(propName, prop.getValue()); - objPtr.getOnPropertyValueWrite(prop.getName()) += [this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) - { propertyChanged(); }; - } - } - else - { - objPtr.setPropertyValue(propName, prop.getValue()); - } - } - } - readProperties(); -} - -FunctionBlockTypePtr MqttJsonReceiverFbImpl::CreateType() -{ - auto defaultConfig = PropertyObject(); - { - auto builder = - StringPropertyBuilder(PROPERTY_NAME_TOPIC, String("")).setDescription("An MQTT topic to subscribe to for receiving JSON data."); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = - IntPropertyBuilder(PROPERTY_NAME_SUB_QOS, DEFAULT_SUB_QOS) - .setMinValue(0) - .setMaxValue(2) - .setSuggestedValues(List(0, 1, 2)) - .setDescription( - fmt::format("MQTT Quality of Service level for subscribing. It can be 0 (at most once), 1 (at least once), or 2 " - "(exactly once). By default it is set to {}.", - DEFAULT_SUB_QOS)); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = StringPropertyBuilder(PROPERTY_NAME_JSON_CONFIG, String("")) - .setDescription( - "JSON configuration string that defines an MQTT topic and corresponding signals to subscribe to."); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = StringPropertyBuilder(PROPERTY_NAME_JSON_CONFIG_FILE, String("")) - .setDescription("Path to file where the JSON configuration string is stored."); - defaultConfig.addProperty(builder.build()); - } - const auto fbType = FunctionBlockType(JSON_FB_NAME, - JSON_FB_NAME, - "The JSON MQTT function block allows subscribing to MQTT topics, extracting values and " - "timestamps from MQTT JSON messages, and converting them into openDAQ signal data samples.", - defaultConfig); - return fbType; -} - -std::string MqttJsonReceiverFbImpl::generateLocalId() -{ - return std::string(MQTT_LOCAL_JSON_FB_ID_PREFIX + std::to_string(localIndex++)); -} - -void MqttJsonReceiverFbImpl::initNestedFbTypes() -{ - nestedFbTypes = Dict(); - // Add a function block type for manual JSON configuration - { - const auto fbType = MqttJsonDecoderFbImpl::CreateType(); - nestedFbTypes.set(fbType.getId(), fbType); - } -} - -void MqttJsonReceiverFbImpl::readProperties() -{ - auto lock = this->getRecursiveConfigLock(); - topicForSubscribing.clear(); - bool isPresent = false; - if (objPtr.hasProperty(PROPERTY_NAME_TOPIC)) - { - auto topicStr = objPtr.getPropertyValue(PROPERTY_NAME_TOPIC).asPtrOrNull(); - if (topicStr.assigned()) - { - isPresent = true; - setTopic(topicStr.toStdString()); - } - } - if (objPtr.hasProperty(PROPERTY_NAME_SUB_QOS)) - { - auto qosProp = objPtr.getPropertyValue(PROPERTY_NAME_SUB_QOS).asPtrOrNull(); - if (qosProp.assigned()) - { - const auto qos = qosProp.getValue(DEFAULT_SUB_QOS); - this->qos = (qos < 0 || qos > 2) ? DEFAULT_SUB_QOS : qos; - } - } - if (!isPresent) - { - LOG_W("\'{}\' property is missing!", PROPERTY_NAME_TOPIC); - setComponentStatus(ComponentStatus::Warning); - subscriptionStatus.setStatus(SubscriptionStatus::InvalidTopicName, "The topic property is not set!"); - } -} - -void MqttJsonReceiverFbImpl::readJsonConfig() -{ - bool hasJsonConfig = false; - if (objPtr.hasProperty(PROPERTY_NAME_JSON_CONFIG)) - { - const auto signalConfig = objPtr.getPropertyValue(PROPERTY_NAME_JSON_CONFIG).asPtrOrNull(); - if (signalConfig.assigned()) - { - if (!signalConfig.toStdString().empty()) - { - hasJsonConfig = true; - setJsonConfig(signalConfig.toStdString()); - } - } - } - if (hasJsonConfig == false && objPtr.hasProperty(PROPERTY_NAME_JSON_CONFIG_FILE)) - { - const auto configPath = objPtr.getPropertyValue(PROPERTY_NAME_JSON_CONFIG_FILE).asPtrOrNull(); - if (configPath.assigned()) - { - if (!configPath.toStdString().empty()) - { - auto res = readFileToString(configPath.toStdString()); - if (res.first) - { - hasJsonConfig = true; - setJsonConfig(res.second); - } - else - { - auto msg = fmt::format("Failed to read JSON config from file: {}", configPath.toStdString()); - LOG_W("{}", msg); - setComponentStatusWithMessage(ComponentStatus::Error, msg); - } - } - } - } -} - -std::pair MqttJsonReceiverFbImpl::readFileToString(const std::string& filePath) -{ - std::ifstream file(filePath); - if (!file) - return std::pair(false, ""); - - std::ostringstream buffer; - buffer << file.rdbuf(); // Read the entire file buffer - return std::pair(true, buffer.str()); -} - -void MqttJsonReceiverFbImpl::setJsonConfig(const std::string config) -{ - jsonDataWorker.setConfig(config); - auto result = jsonDataWorker.isJsonValid(); - if (result.success) - { - auto topic = jsonDataWorker.extractTopic(); - result.success = setTopic(topic); - if (result.success) - { - { - auto event = objPtr.getOnPropertyValueWrite(PROPERTY_NAME_TOPIC); - event.mute(); - objPtr.setPropertyValue(PROPERTY_NAME_TOPIC, String(topic)); - event.unmute(); - } - if (const auto signalDscs = jsonDataWorker.extractDescription(); !signalDscs.empty()) - { - auto fbConfig = MqttJsonDecoderFbImpl::CreateType().createDefaultConfig(); - for (const auto& [signalName, descriptor] : signalDscs) - { - LOG_I("Creating a decoder FB for the signal \"{}\":", signalName); - fbConfig.setPropertyValue(PROPERTY_NAME_VALUE_NAME, descriptor.valueFieldName); - fbConfig.setPropertyValue(PROPERTY_NAME_TS_NAME, descriptor.tsFieldName); - fbConfig.setPropertyValue(PROPERTY_NAME_SIGNAL_NAME, signalName); - if (descriptor.unit.assigned()) - fbConfig.setPropertyValue(PROPERTY_NAME_UNIT, descriptor.unit.getSymbol()); - MqttJsonReceiverFbImpl::onAddFunctionBlock(JSON_DECODER_FB_NAME, fbConfig); - } - } - } - else - { - result.msg = "Failed to set topic from JSON config."; - } - } - if (!result.success) - { - result.msg = fmt::format("JSON config is wrong! {}", result.msg); - LOG_W("{}", result.msg); - setComponentStatusWithMessage(ComponentStatus::Error, result.msg); - } -} - -void MqttJsonReceiverFbImpl::propertyChanged() -{ - auto lock = this->getRecursiveConfigLock(); - auto result = unsubscribeFromTopic(); - if (result.success == false) - { - LOG_W("Failed to unsubscribe from the previous topic before subscribing to a new one; reason: {}", result.msg); - return; - } - readProperties(); - result = subscribeToTopic(); -} - -bool MqttJsonReceiverFbImpl::setTopic(std::string topic) -{ - const auto validationStatus = mqtt::MqttDataWrapper::validateTopic(topic, loggerComponent); - if (validationStatus.success) - { - LOG_I("An MQTT topic: {}", topic); - topicForSubscribing = std::move(topic); - setComponentStatus(ComponentStatus::Ok); - subscriptionStatus.setStatus(SubscriptionStatus::WaitingForData, "Subscribing to topic: " + topicForSubscribing); - } - else - { - setComponentStatus(ComponentStatus::Warning); - subscriptionStatus.setStatus(SubscriptionStatus::InvalidTopicName, validationStatus.msg); - } - return validationStatus.success; -} - -DictPtr MqttJsonReceiverFbImpl::onGetAvailableFunctionBlockTypes() -{ - return nestedFbTypes; -} - -FunctionBlockPtr MqttJsonReceiverFbImpl::onAddFunctionBlock(const StringPtr& typeId, const PropertyObjectPtr& config) -{ - - FunctionBlockPtr nestedFunctionBlock; - if (nestedFbTypes.hasKey(typeId)) - { - auto fbTypePtr = nestedFbTypes.getOrDefault(typeId); - if (fbTypePtr.getName() == JSON_DECODER_FB_NAME) - { - nestedFunctionBlock = createWithImplementation(context, functionBlocks, fbTypePtr, config); - } - } - if (nestedFunctionBlock.assigned()) - { - addNestedFunctionBlock(nestedFunctionBlock); - { - auto lock = this->getAcquisitionLock2(); - nestedFunctionBlocks.push_back(nestedFunctionBlock); - } - setComponentStatus(ComponentStatus::Ok); - } - else - { - DAQ_THROW_EXCEPTION(NotFoundException, "Function block type is not available: " + typeId.toStdString()); - } - return nestedFunctionBlock; -} - -void MqttJsonReceiverFbImpl::onRemoveFunctionBlock(const FunctionBlockPtr& functionBlock) -{ - { - auto lock = this->getAcquisitionLock2(); - auto it = std::find_if(nestedFunctionBlocks.begin(), - nestedFunctionBlocks.end(), - [&functionBlock](const FunctionBlockPtr& fb) { return fb.getObject() == functionBlock.getObject(); }); - - if (it != nestedFunctionBlocks.end()) - { - nestedFunctionBlocks.erase(it); - } - } - FunctionBlockImpl::onRemoveFunctionBlock(functionBlock); -} - -void MqttJsonReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) -{ - if (topicForSubscribing == msg.getTopic()) - { - if (subscriptionStatus.getStatus() == SubscriptionStatus::WaitingForData) - { - subscriptionStatus.setStatus(SubscriptionStatus::HasData); - } - - std::string jsonObjStr(msg.getData().begin(), msg.getData().end()); - auto acqlock = this->getAcquisitionLock2(); - for (const auto& fb : nestedFunctionBlocks) - { - if (fb.assigned()) - { - auto decoderFb = reinterpret_cast(*fb); - decoderFb->processMessage(jsonObjStr); - } - } - } -} - -void MqttJsonReceiverFbImpl::createSignals() -{ - -} - -std::string MqttJsonReceiverFbImpl::getSubscribedTopic() const -{ - return topicForSubscribing; -} - -void MqttJsonReceiverFbImpl::clearSubscribedTopic() -{ - topicForSubscribing.clear(); -} - -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/src/mqtt_publisher_fb_impl.cpp b/mqtt_streaming_module/src/mqtt_publisher_fb_impl.cpp deleted file mode 100644 index ab5a0bf..0000000 --- a/mqtt_streaming_module/src/mqtt_publisher_fb_impl.cpp +++ /dev/null @@ -1,426 +0,0 @@ -#include "mqtt_streaming_module/constants.h" -#include "mqtt_streaming_module/handler_factory.h" -#include -#include -#include -#include - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE - -std::atomic MqttPublisherFbImpl::localIndex = 0; - -MqttPublisherFbImpl::MqttPublisherFbImpl(const ContextPtr& ctx, - const ComponentPtr& parent, - const FunctionBlockTypePtr& type, - std::shared_ptr mqttClient, - const PropertyObjectPtr& config) - : FunctionBlock(type, ctx, parent, generateLocalId()), - mqttClient(mqttClient), - jsonDataWorker(loggerComponent), - inputPortCount(0), - running(true), - hasSignalError(false), - hasSettingError(false), - signalStatus(MQTT_PUB_FB_SIG_STATUS_TYPE, - MQTT_PUB_FB_SIG_STATUS_NAME, - statusContainer, - signalStatusMap, - SignalStatus::NotConnected, - context.getTypeManager()), - publishingStatus(MQTT_PUB_FB_PUB_STATUS_TYPE, - MQTT_PUB_FB_PUB_STATUS_NAME, - statusContainer, - publishingStatusMap, - PublishingStatus::Ok, - context.getTypeManager()), - settingStatus(MQTT_PUB_FB_SET_STATUS_TYPE, - MQTT_PUB_FB_SET_STATUS_NAME, - statusContainer, - settingStatusMap, - SettingStatus::Valid, - context.getTypeManager()), - skippedMsgCnt(0), - publishingStatusTimer(helper::utils::Timer(1000, false)) -{ - initComponentStatus(); - if (config.assigned()) - initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); - else - initProperties(type.createDefaultConfig()); - - handler = HandlerFactory::create(this->config, globalId.toStdString()); - updateInputPorts(); - validateInputPorts(); - updateStatuses(); - runReaderThread(); -} - -MqttPublisherFbImpl::~MqttPublisherFbImpl() -{ - running = false; - readerThread.join(); -} - -FunctionBlockTypePtr MqttPublisherFbImpl::CreateType() -{ - auto defaultConfig = PropertyObject(); - { - auto builder = - SelectionPropertyBuilder(PROPERTY_NAME_PUB_TOPIC_MODE, List("single-topic", "multiple-topic"), 0) - .setDescription( - "Selects whether to publish all signals to separate MQTT topics (one per signal, single-topic mode) or to a single " - "topic (multiple-topic mode), one for all signals. Choose 0 for single-topic mode, 1 for multiple-topic mode. By " - "default it is set to single-topic mode."); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = - StringPropertyBuilder(PROPERTY_NAME_PUB_TOPIC_NAME, "") - .setDescription( - "Topic name for publishing in multiple-topic mode. If left empty, the Publisher's Global ID is used as the topic name.") - .setVisible(EvalValue(std::string("$") + PROPERTY_NAME_PUB_TOPIC_MODE + " == 1")); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = BoolPropertyBuilder(PROPERTY_NAME_PUB_SHARED_TS, False) - .setVisible(EvalValue(std::string("$") + PROPERTY_NAME_PUB_TOPIC_MODE + " == 1")) - .setDescription("Enables the use of a shared timestamp for all signals when publishing in multiple-topic mode. " - "By default it is set to false."); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = - BoolPropertyBuilder(PROPERTY_NAME_PUB_GROUP_VALUES, False) - .setVisible(EvalValue(std::string("$") + PROPERTY_NAME_PUB_TOPIC_MODE + " == 0")) - .setDescription( - "Enables the use of a sample pack for a signal when publishing in single-topic mode. By default it is set to false."); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = BoolPropertyBuilder(PROPERTY_NAME_PUB_USE_SIGNAL_NAMES, False) - .setDescription("Uses signal names as JSON field names instead of Global IDs. By default it is set to false."); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = - IntPropertyBuilder(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE, DEFAULT_PUB_PACK_SIZE) - .setMinValue(1) - .setVisible(EvalValue(std::string("($") + PROPERTY_NAME_PUB_TOPIC_MODE + " == 0) && " + std::string("($") + - PROPERTY_NAME_PUB_GROUP_VALUES + ")")) - .setDescription(fmt::format("Set the size of the sample pack when publishing grouped values in single-topic mode. " - "By default it is set to {}.", - DEFAULT_PUB_PACK_SIZE)); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = - IntPropertyBuilder(PROPERTY_NAME_PUB_QOS, DEFAULT_PUB_QOS) - .setMinValue(0) - .setMaxValue(2) - .setSuggestedValues(List(0, 1, 2)) - .setDescription( - fmt::format("MQTT Quality of Service level for published messages. It can be 0 (at most once), 1 (at least once), or 2 " - "(exactly once). By default it is set to {}.", - DEFAULT_PUB_QOS)); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = - IntPropertyBuilder(PROPERTY_NAME_PUB_READ_PERIOD, DEFAULT_PUB_READ_PERIOD) - .setMinValue(0) - .setUnit(Unit("ms")) - .setDescription(fmt::format("Polling period in milliseconds, which specifies how often the server collects and publishes " - "the connected signals’ data to an MQTT broker. By default it is set to {} ms.", - DEFAULT_PUB_READ_PERIOD)); - defaultConfig.addProperty(builder.build()); - } - const auto fbType = FunctionBlockType(PUB_FB_NAME, - PUB_FB_NAME, - "The Publisher function block allows converting openDAQ signal samples into JSON messages and " - "publishing them to MQTT topics in different ways.", - defaultConfig); - return fbType; -} - -PublisherFbConfig MqttPublisherFbImpl::getFbConfig() const -{ - return config; -} - -void MqttPublisherFbImpl::onConnected(const InputPortPtr& inputPort) -{ - auto lock = this->getRecursiveConfigLock(); - - updateInputPorts(); - LOG_T("Connected to port {}", inputPort.getLocalId()); - validateInputPorts(); - updateStatuses(); -} - -void MqttPublisherFbImpl::onDisconnected(const InputPortPtr& inputPort) -{ - auto lock = this->getRecursiveConfigLock(); - - updateInputPorts(); - LOG_T("Disconnected from port {}", inputPort.getLocalId()); - validateInputPorts(); - updateStatuses(); -} - -void MqttPublisherFbImpl::updateInputPorts() -{ - for (auto it = signalContexts.begin(); it != signalContexts.end();) - { - if (!it->inputPort.getSignal().assigned()) - { - removeInputPort(it->inputPort); - it = signalContexts.erase(it); - } - else - ++it; - } - - const auto inputPort = createAndAddInputPort(fmt::format("Input{}", inputPortCount++), PacketReadyNotification::SameThread); - - signalContexts.emplace_back(SignalContext{0, inputPort, {}}); - for (size_t i = 0; i < signalContexts.size(); i++) - signalContexts[i].index = i; -} - -void MqttPublisherFbImpl::updateStatuses() -{ - auto buildErrorString = [this](const std::vector& errors) - { - std::string allMessages; - for (const auto& msg : errors) - { - LOG_E("{}", msg); - allMessages += msg + "; "; - } - return allMessages; - }; - - if (hasSignalError) - { - signalStatus.setStatus(SignalStatus::Invalid, buildErrorString(signalErrors)); - } - else if (signalContexts.size() == 1) // no one input port is connected - { - signalStatus.setStatus(SignalStatus::NotConnected); - } - else - { - signalStatus.setStatus(SignalStatus::Valid); - } - - if (hasSettingError) - { - settingStatus.setStatus(SettingStatus::Invalid, buildErrorString(settingErrors)); - } - else - { - settingStatus.setStatus(SettingStatus::Valid); - } - - if (hasSignalError) - { - setComponentStatusWithMessage(ComponentStatus::Error, "Some connected signals were invalidated!"); - } - else if (hasSettingError) - { - setComponentStatusWithMessage(ComponentStatus::Error, "Some property has wrong value!"); - } - else if (skippedMsgCnt != 0) - { - setComponentStatusWithMessage(ComponentStatus::Warning, "Some messages were not published!"); - } - else - { - setComponentStatus(ComponentStatus::Ok); - } - updatePublishingStatus(true); -} - -void MqttPublisherFbImpl::validateInputPorts() -{ - skippedMsgCnt = 0; - publishedMsgCnt = 0; - if (signalContexts.size() == 1) // no one input port is connected - { - hasSignalError = false; - } - else - { - const auto status = handler->validateSignalContexts(signalContexts); - hasSignalError = !status.success; - signalErrors = std::move(status.messages); - if (status.success) - handler->signalListChanged(signalContexts); - } -} - -void MqttPublisherFbImpl::initProperties(const PropertyObjectPtr& config) -{ - for (const auto& prop : config.getAllProperties()) - { - const auto propName = prop.getName(); - if (!objPtr.hasProperty(propName)) - { - if (const auto internalProp = prop.asPtrOrNull(true); internalProp.assigned()) - { - objPtr.addProperty(internalProp.clone()); - objPtr.setPropertyValue(propName, prop.getValue()); - objPtr.getOnPropertyValueWrite(prop.getName()) += - [this](PropertyObjectPtr& obj, PropertyValueEventArgsPtr& args) { propertyChanged(); }; - } - } - else - { - objPtr.setPropertyValue(propName, prop.getValue()); - } - } - readProperties(); -} - -void MqttPublisherFbImpl::readProperties() -{ - auto lock = this->getRecursiveConfigLock(); - int tmpTopicMode = readProperty(PROPERTY_NAME_PUB_TOPIC_MODE, 0); - - config.sharedTs = readProperty(PROPERTY_NAME_PUB_SHARED_TS, false); - config.groupValues = readProperty(PROPERTY_NAME_PUB_GROUP_VALUES, false); - config.useSignalNames = readProperty(PROPERTY_NAME_PUB_USE_SIGNAL_NAMES, false); - config.groupValuesPackSize = readProperty(PROPERTY_NAME_PUB_GROUP_VALUES_PACK_SIZE, DEFAULT_PUB_PACK_SIZE); - config.qos = readProperty(PROPERTY_NAME_PUB_QOS, DEFAULT_PUB_QOS); - config.periodMs = readProperty(PROPERTY_NAME_PUB_READ_PERIOD, DEFAULT_PUB_READ_PERIOD); - config.topicName = readProperty(PROPERTY_NAME_PUB_TOPIC_NAME, globalId.toStdString()); - if (config.topicName.empty()) - config.topicName = globalId.toStdString(); - - settingErrors.clear(); - hasSettingError = false; - if (config.topicMode == TopicMode::Multi || config.sharedTs) - { - auto result = mqtt::MqttDataWrapper::validateTopic(config.topicName, loggerComponent); - hasSettingError = !result.success; - settingErrors.push_back(std::move(result.msg)); - } - if (config.qos < 0 || config.qos > 2) - { - config.qos = DEFAULT_PUB_QOS; - hasSettingError = true; - settingErrors.push_back("QoS level must be 0, 1, or 2."); - } - if (config.periodMs < 0) - { - config.periodMs = DEFAULT_PUB_READ_PERIOD; - hasSettingError = true; - settingErrors.push_back("Reader period must be non-negative."); - } - if (tmpTopicMode < static_cast(TopicMode::_count) && tmpTopicMode >= 0) - { - config.topicMode = static_cast(tmpTopicMode); - } - else - { - config.topicMode = TopicMode::Single; - hasSettingError = true; - settingErrors.push_back("Topic mode has invalid value."); - } - -} - -void MqttPublisherFbImpl::propertyChanged() -{ - auto lock = this->getRecursiveConfigLock(); - readProperties(); - handler = HandlerFactory::create(this->config, globalId.toStdString()); - validateInputPorts(); - updateStatuses(); -} - -template -retT MqttPublisherFbImpl::readProperty(const std::string& propertyName, const retT defaultValue) -{ - retT returnValue{}; - if (objPtr.hasProperty(propertyName)) - { - auto property = objPtr.getPropertyValue(propertyName).asPtrOrNull(); - if (property.assigned()) - { - returnValue = property.getValue(defaultValue); - } - } - return returnValue; -} - -void MqttPublisherFbImpl::runReaderThread() -{ - LOGP_D("Using separate thread for rendering") - - readerThread = std::thread([this] { readerLoop(); }); -} - -void MqttPublisherFbImpl::readerLoop() -{ - while (running) - { - { - MqttData msgs; - auto lock = this->getRecursiveConfigLock(); - if (hasSignalError == false && hasSettingError == false) - { - msgs = handler->processSignalContexts(signalContexts); - } - sendMessages(msgs); - updatePublishingStatus(false); - } - std::this_thread::sleep_for(std::chrono::milliseconds(config.periodMs)); - } -} - -void MqttPublisherFbImpl::sendMessages(const MqttData& data) -{ - for (const auto& [topic, msg] : data) - { - auto status = mqttClient->publish(topic, (void*)msg.c_str(), msg.length(), config.qos); - if (!status.success) - { - ++skippedMsgCnt; - lastSkippedReason = std::move(status.msg); - LOG_W("Failed to publish data to {}; reason - {}", topic, lastSkippedReason); - } - else - { - ++publishedMsgCnt; - } - } -} - -std::string MqttPublisherFbImpl::generateLocalId() -{ - return std::string(MQTT_LOCAL_PUB_FB_ID_PREFIX + std::to_string(localIndex++)); -} - -void MqttPublisherFbImpl::updatePublishingStatus(bool force) -{ - if (publishingStatusTimer.expired() || force) - { - publishingStatusTimer.restart(); - if (skippedMsgCnt != 0) - { - if (statusContainer.getStatus("ComponentStatus") == ComponentStatus::Ok) - setComponentStatusWithMessage(ComponentStatus::Warning, "Some messages were not published!"); - publishingStatus.setStatus(PublishingStatus::SampleSkipped, - fmt::format("Published: {}; Skipped: {}; last reason - {}", - publishedMsgCnt.load(), - skippedMsgCnt.load(), - lastSkippedReason)); - } - else - { - publishingStatus.setStatus(PublishingStatus::Ok, fmt::format("Published: {};", publishedMsgCnt.load())); - } - } -} -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/src/mqtt_raw_receiver_fb_impl.cpp b/mqtt_streaming_module/src/mqtt_raw_receiver_fb_impl.cpp deleted file mode 100644 index bfa6a38..0000000 --- a/mqtt_streaming_module/src/mqtt_raw_receiver_fb_impl.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "MqttDataWrapper.h" -#include "mqtt_streaming_module/constants.h" -#include "mqtt_streaming_module/helper.h" -#include -#include -#include - -BEGIN_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE - -constexpr int MQTT_RAW_FB_UNSUBSCRIBE_TOUT = 3000; - -std::atomic MqttRawReceiverFbImpl::localIndex = 0; - -MqttRawReceiverFbImpl::MqttRawReceiverFbImpl(const ContextPtr& ctx, - const ComponentPtr& parent, - const FunctionBlockTypePtr& type, - std::shared_ptr subscriber, - const PropertyObjectPtr& config) - : MqttBaseFb(ctx, parent, type, generateLocalId(), subscriber, config) -{ - if (config.assigned()) - initProperties(populateDefaultConfig(type.createDefaultConfig(), config)); - else - initProperties(type.createDefaultConfig()); - - createSignals(); - subscribeToTopic(); -} - -MqttRawReceiverFbImpl::~MqttRawReceiverFbImpl() -{ - unsubscribeFromTopic(); -} - -FunctionBlockTypePtr MqttRawReceiverFbImpl::CreateType() -{ - auto defaultConfig = PropertyObject(); - { - auto builder = - IntPropertyBuilder(PROPERTY_NAME_SUB_QOS, DEFAULT_SUB_QOS) - .setMinValue(0) - .setMaxValue(2) - .setSuggestedValues(List(0, 1, 2)) - .setDescription( - fmt::format("MQTT Quality of Service level for subscribing. It can be 0 (at most once), 1 (at least once), or 2 " - "(exactly once). By default it is set to {}.", - DEFAULT_SUB_QOS)); - defaultConfig.addProperty(builder.build()); - } - { - auto builder = - StringPropertyBuilder(PROPERTY_NAME_TOPIC, "").setDescription("An MQTT topic to subscribe to for receiving raw binary data."); - defaultConfig.addProperty(builder.build()); - } - const auto fbType = - FunctionBlockType(RAW_FB_NAME, - RAW_FB_NAME, - "The raw MQTT function block allows subscribing to an MQTT topic and converting MQTT payloads into " - "openDAQ signal binary data samples.", - defaultConfig); - return fbType; -} - -std::string MqttRawReceiverFbImpl::generateLocalId() -{ - return std::string(MQTT_LOCAL_RAW_FB_ID_PREFIX + std::to_string(localIndex++)); -} - -void MqttRawReceiverFbImpl::readProperties() -{ - auto lock = std::lock_guard(sync); - topicForSubscribing.clear(); - bool isPresent = false; - if (objPtr.hasProperty(PROPERTY_NAME_TOPIC)) - { - auto topicStr = objPtr.getPropertyValue(PROPERTY_NAME_TOPIC).asPtrOrNull(); - if (topicStr.assigned()) - { - isPresent = true; - const auto validationStatus = mqtt::MqttDataWrapper::validateTopic(topicStr, loggerComponent); - if (validationStatus.success) - { - LOG_I("An MQTT topic: {}", topicStr.toStdString()); - topicForSubscribing = topicStr.toStdString(); - setComponentStatus(ComponentStatus::Ok); - subscriptionStatus.setStatus(SubscriptionStatus::WaitingForData, "Subscribing to topic: " + topicForSubscribing); - } - else - { - setComponentStatus(ComponentStatus::Warning); - subscriptionStatus.setStatus(SubscriptionStatus::InvalidTopicName, validationStatus.msg); - } - } - } - - if (objPtr.hasProperty(PROPERTY_NAME_SUB_QOS)) - { - auto qosProp = objPtr.getPropertyValue(PROPERTY_NAME_SUB_QOS).asPtrOrNull(); - if (qosProp.assigned()) - { - const auto qos = qosProp.getValue(DEFAULT_SUB_QOS); - this->qos = (qos < 0 || qos > 2) ? DEFAULT_SUB_QOS : qos; - } - } - - if (!isPresent) - { - LOG_W("\'{}\' property is missing!", PROPERTY_NAME_TOPIC); - setComponentStatus(ComponentStatus::Warning); - subscriptionStatus.setStatus(SubscriptionStatus::InvalidTopicName, "The topic property is not set!"); - } - if (topicForSubscribing.empty()) - { - LOG_W("No topic to subscribe to!"); - } -} - -void MqttRawReceiverFbImpl::propertyChanged() -{ - auto result = unsubscribeFromTopic(); - if (result.success == false) - { - LOG_W("Failed to unsubscribe from the previous topic before subscribing to a new one; reason: {}", result.msg); - return; - } - readProperties(); - result = subscribeToTopic(); -} - -void MqttRawReceiverFbImpl::processMessage(const mqtt::MqttMessage& msg) -{ - const std::string topic(msg.getTopic()); - - auto lock = std::lock_guard(sync); - if (topicForSubscribing == topic) - { - if (subscriptionStatus.getStatus() == SubscriptionStatus::WaitingForData) - { - subscriptionStatus.setStatus(SubscriptionStatus::HasData); - } - const auto outputPacket = BinaryDataPacket(nullptr, outputSignal.getDescriptor(), msg.getData().size()); - memcpy(outputPacket.getData(), msg.getData().data(), msg.getData().size()); - outputSignal.sendPacket(outputPacket); - } -} - -void MqttRawReceiverFbImpl::createSignals() -{ - auto lock = std::lock_guard(sync); - const auto signalDsc = DataDescriptorBuilder().setSampleType(SampleType::Binary).build(); - outputSignal = createAndAddSignal(DEFAULT_SIGNAL_NAME, signalDsc); -} - -std::string MqttRawReceiverFbImpl::getSubscribedTopic() const -{ - return topicForSubscribing; -} - -void MqttRawReceiverFbImpl::clearSubscribedTopic() -{ - topicForSubscribing.clear(); -} - -END_NAMESPACE_OPENDAQ_MQTT_STREAMING_MODULE diff --git a/mqtt_streaming_module/tests/test_mqtt_json_fb.cpp b/mqtt_streaming_module/tests/test_mqtt_json_fb.cpp deleted file mode 100644 index 817be45..0000000 --- a/mqtt_streaming_module/tests/test_mqtt_json_fb.cpp +++ /dev/null @@ -1,386 +0,0 @@ -#include "mqtt_streaming_module/mqtt_json_receiver_fb_impl.h" -#include "test_daq_test_helper.h" -#include "test_data.h" -#include -#include -#include -#include -#include -#include - -using namespace daq; -using namespace daq::modules::mqtt_streaming_module; - -namespace daq::modules::mqtt_streaming_module -{ -class MqttJsonFbHelper -{ -public: - std::unique_ptr obj; - - auto getSignals() - { - auto signalList = List(); - obj->getSignals(&signalList); - return signalList; - } - - std::string buildTopicName(const std::string& postfix = "") - { - return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + postfix; - } - - std::string buildClientId() - { - return std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + "_ClientId"; - } -}; - -class MqttJsonFbTest : public testing::Test, public DaqTestHelper, public MqttJsonFbHelper -{ -}; - -class MqttJsonFbTopicPTest : public ::testing::TestWithParam>, - public DaqTestHelper, - public MqttJsonFbHelper -{ -}; - -class MqttJsonFbConfigPTest : public ::testing::TestWithParam, - public DaqTestHelper, - public MqttJsonFbHelper -{ -}; - -class MqttJsonFbConfigFilePTest : public ::testing::TestWithParam, - public DaqTestHelper, - public MqttJsonFbHelper -{ -}; -} // namespace daq::modules::mqtt_streaming_module - -TEST_F(MqttJsonFbTest, DefaultConfig) -{ - StartUp(); - daq::DictPtr fbTypes; - daq::FunctionBlockTypePtr fbt; - daq::PropertyObjectPtr defaultConfig; - ASSERT_NO_THROW(fbTypes = rootMqttFb.getAvailableFunctionBlockTypes()); - ASSERT_NO_THROW(fbt = fbTypes.get(JSON_FB_NAME)); - ASSERT_NO_THROW(defaultConfig = fbt.createDefaultConfig()); - - ASSERT_TRUE(defaultConfig.assigned()); - - ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 4u); - - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_JSON_CONFIG)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_JSON_CONFIG).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_JSON_CONFIG).asPtr().getLength(), 0u); - - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_JSON_CONFIG_FILE)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_JSON_CONFIG_FILE).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_JSON_CONFIG_FILE).asPtr().getLength(), 0u); - - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_TOPIC)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_TOPIC).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_TOPIC).asPtr().getLength(), 0u); - - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SUB_QOS)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SUB_QOS).getValueType(), CoreType::ctInt); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SUB_QOS).asPtr().getValue(DEFAULT_SUB_QOS), DEFAULT_SUB_QOS); -} - -TEST_F(MqttJsonFbTest, Config) -{ - StartUp(); - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_TOPIC, buildTopicName()); - daq::FunctionBlockPtr jsonFb; - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - - const auto allProperties = jsonFb.getAllProperties(); - ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount()); - - for (const auto& pror : config.getAllProperties()) - { - const auto propName = pror.getName(); - ASSERT_TRUE(jsonFb.hasProperty(propName)); - ASSERT_EQ(jsonFb.getPropertyValue(propName), config.getPropertyValue(propName)); - } -} - -TEST_F(MqttJsonFbTest, CreationWithDefaultConfig) -{ - StartUp(); - daq::FunctionBlockPtr jsonFb; - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME)); - EXPECT_EQ(jsonFb.getSignals().getCount(), 0u); - ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Warning", daqInstance.getContext().getTypeManager())); -} - -TEST_F(MqttJsonFbTest, CreationWithPartialConfig) -{ - // If FB has only one property, partial config is equivalent to custom config - StartUp(); - daq::FunctionBlockPtr jsonFb; - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, String(buildTopicName()))); - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - EXPECT_EQ(jsonFb.getSignals().getCount(), 0u); - ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); -} - -TEST_F(MqttJsonFbTest, CreationWithCustomConfig) -{ - // If FB has only one property, partial config is equivalent to custom config - StartUp(); - daq::FunctionBlockPtr jsonFb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_TOPIC, String(buildTopicName())); - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - EXPECT_EQ(jsonFb.getSignals().getCount(), 0u); - ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); -} - -TEST_P(MqttJsonFbTopicPTest, CheckJsonFbTopic) -{ - auto [topic, result] = GetParam(); - StartUp(); - - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_TOPIC, topic); - daq::FunctionBlockPtr fb; - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - auto signals = fb.getSignals(); - ASSERT_EQ(signals.getCount(), 0); - const auto expectedComponentStatus = result ? "Ok" : "Warning"; - EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", expectedComponentStatus, daqInstance.getContext().getTypeManager())); - if (result) - { - EXPECT_NE(fb.getStatusContainer().getStatus("SubscriptionStatus"), - EnumerationWithIntValue(MQTT_FB_SUB_STATUS_TYPE, - static_cast(MqttBaseFb::SubscriptionStatus::InvalidTopicName), - daqInstance.getContext().getTypeManager())); - } - else - { - EXPECT_EQ(fb.getStatusContainer().getStatus("SubscriptionStatus"), - EnumerationWithIntValue(MQTT_FB_SUB_STATUS_TYPE, - static_cast(MqttBaseFb::SubscriptionStatus::InvalidTopicName), - daqInstance.getContext().getTypeManager())); - } -} - -INSTANTIATE_TEST_SUITE_P(TopicTest, - MqttJsonFbTopicPTest, - ::testing::Values(std::make_pair("", false), - std::make_pair("goodTopic/test", true), - std::make_pair("/goodTopic/test0", true), - std::make_pair("badTopic/+/test/topic", false), - std::make_pair("badTopic/+/+/topic", false), - std::make_pair("badTopic/#", false))); - -TEST_F(MqttJsonFbTest, RemovingNestedFunctionBlock) -{ - StartUp(); - daq::FunctionBlockPtr jsonFb; - { - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, String(buildTopicName()))); - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - } - daq::FunctionBlockPtr jsonDecoderFb; - { - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_VALUE_NAME, String("temp"))); - ASSERT_NO_THROW(jsonDecoderFb = jsonFb.addFunctionBlock(JSON_DECODER_FB_NAME, config)); - } - ASSERT_EQ(jsonFb.getFunctionBlocks().getCount(), 1u); - - ASSERT_NO_THROW(jsonFb.removeFunctionBlock(jsonDecoderFb)); - ASSERT_EQ(jsonFb.getFunctionBlocks().getCount(), 0u); -} - -TEST_F(MqttJsonFbTest, TwoFbCreation) -{ - StartUp(); - { - daq::FunctionBlockPtr fb; - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, buildTopicName("0"))); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - } - { - daq::FunctionBlockPtr fb; - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, buildTopicName("1"))); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - } - auto fbs = rootMqttFb.getFunctionBlocks(); - ASSERT_EQ(fbs.getCount(), 2u); -} - -TEST_F(MqttJsonFbTest, PropertyChanged) -{ - StartUp(); - - daq::FunctionBlockPtr fb; - auto config = PropertyObject(); - auto topic = buildTopicName("0"); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, topic)); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - auto jsonFb = reinterpret_cast(*fb); - - ASSERT_EQ(topic, jsonFb->getSubscribedTopic()); - topic = buildTopicName("1"); - fb.setPropertyValue(PROPERTY_NAME_TOPIC, topic); - ASSERT_EQ(topic, jsonFb->getSubscribedTopic()); -} - -TEST_F(MqttJsonFbTest, JsonInit0) -{ - StartUp(); - daq::FunctionBlockPtr jsonFb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_JSON_CONFIG, String(VALID_JSON_1_TOPIC_0)); - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - ASSERT_EQ(jsonFb.getFunctionBlocks().getCount(), 3u); - ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - auto lambda = [&](FunctionBlockPtr nestedFb, std::string name, std::string value, std::string ts, std::string symbol) - { - EXPECT_EQ(nestedFb.getSignals()[0].getName().toStdString(), name); - if (!symbol.empty()) - EXPECT_EQ(nestedFb.getSignals()[0].getDescriptor().getUnit().getSymbol().toStdString(), symbol); - EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_VALUE_NAME).asPtr().toStdString(), value); - EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_TS_NAME).asPtr().toStdString(), ts); - }; - EXPECT_EQ(jsonFb.getPropertyValue(PROPERTY_NAME_TOPIC).asPtr().toStdString(), "openDAQ/RefDev0/IO/AI/RefCh0/Sig/AI0"); - - lambda(jsonFb.getFunctionBlocks()[0], "AI0", "value", "timestamp", "V"); - lambda(jsonFb.getFunctionBlocks()[1], "AI1", "value1", "", ""); - lambda(jsonFb.getFunctionBlocks()[2], "AI2", "value2", "", "W"); - -} - -TEST_F(MqttJsonFbTest, JsonInit1) -{ - StartUp(); - daq::FunctionBlockPtr jsonFb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_JSON_CONFIG, String(VALID_JSON_1_TOPIC_1)); - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - ASSERT_EQ(jsonFb.getFunctionBlocks().getCount(), 3u); - ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - auto lambda = [&](FunctionBlockPtr nestedFb, std::string name, std::string value, std::string ts, std::string symbol) - { - EXPECT_EQ(nestedFb.getSignals()[0].getName().toStdString(), name); - if (!symbol.empty()) - EXPECT_EQ(nestedFb.getSignals()[0].getDescriptor().getUnit().getSymbol().toStdString(), symbol); - EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_VALUE_NAME).asPtr().toStdString(), value); - EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_TS_NAME).asPtr().toStdString(), ts); - }; - EXPECT_EQ(jsonFb.getPropertyValue(PROPERTY_NAME_TOPIC).asPtr().toStdString(), "/mirip/UNet3AC2/sensor/data"); - - lambda(jsonFb.getFunctionBlocks()[0], "temp", "temp", "ts", "°C"); - lambda(jsonFb.getFunctionBlocks()[1], "humidity", "humi", "ts", "%"); - lambda(jsonFb.getFunctionBlocks()[2], "tds", "tds_value", "ts", "ppm"); - -} - -TEST_P(MqttJsonFbConfigPTest, JsonWrongInit) -{ - const auto configJson = GetParam(); - StartUp(); - daq::FunctionBlockPtr jsonFb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_JSON_CONFIG, String(configJson)); - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - EXPECT_EQ(jsonFb.getFunctionBlocks().getCount(), 0u); - EXPECT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); - EXPECT_EQ(jsonFb.getPropertyValue(PROPERTY_NAME_TOPIC).asPtr().toStdString(), ""); -} - -INSTANTIATE_TEST_SUITE_P( - JsonConfigTest, - MqttJsonFbConfigPTest, - ::testing::Values( - VALID_JSON_3_TOPIC_2, - WILDCARD_JSON_0, - WILDCARD_JSON_1, - INVALID_JSON_1, - INVALID_JSON_3)); - -TEST_P(MqttJsonFbConfigFilePTest, JsonInitFromFile) -{ - const auto configJson = GetParam(); - StartUp(); - daq::FunctionBlockPtr jsonFb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_JSON_CONFIG_FILE, String(configJson)); - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); -} - -INSTANTIATE_TEST_SUITE_P(JsonConfigTest, - MqttJsonFbConfigFilePTest, - ::testing::Values("data/public-example0.json", - "data/public-example1.json", - "data/public-example2.json", - "data/public-example3.json")); - -TEST_F(MqttJsonFbTest, JsonInitFromFileWithChecking) -{ - StartUp(); - daq::FunctionBlockPtr jsonFb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_JSON_CONFIG_FILE, String("data/public-example0.json")); - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - ASSERT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - ASSERT_EQ(jsonFb.getFunctionBlocks().getCount(), 3u); - auto lambda = [&](FunctionBlockPtr nestedFb, std::string name, std::string value, std::string ts, std::string symbol) - { - EXPECT_EQ(nestedFb.getSignals()[0].getName().toStdString(), name); - if (!symbol.empty()) - EXPECT_EQ(nestedFb.getSignals()[0].getDescriptor().getUnit().getSymbol().toStdString(), symbol); - EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_VALUE_NAME).asPtr().toStdString(), value); - EXPECT_EQ(nestedFb.getPropertyValue(PROPERTY_NAME_TS_NAME).asPtr().toStdString(), ts); - }; - EXPECT_EQ(jsonFb.getPropertyValue(PROPERTY_NAME_TOPIC).asPtr().toStdString(), "/mirip/UNet3AC2/sensor/data"); - - lambda(jsonFb.getFunctionBlocks()[0], "temp", "temp", "ts", "°C"); - lambda(jsonFb.getFunctionBlocks()[1], "humidity", "humi", "ts", "%"); - lambda(jsonFb.getFunctionBlocks()[2], "tds", "tds_value", "ts", "ppm"); - -} - -TEST_F(MqttJsonFbTest, JsonInitFromFileWrongPath) -{ - StartUp(); - daq::FunctionBlockPtr jsonFb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(JSON_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_JSON_CONFIG_FILE, String("/justWrongPath/wrongFile.txt")); - ASSERT_NO_THROW(jsonFb = rootMqttFb.addFunctionBlock(JSON_FB_NAME, config)); - EXPECT_EQ(jsonFb.getFunctionBlocks().getCount(), 0u); - EXPECT_EQ(jsonFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Error", daqInstance.getContext().getTypeManager())); - EXPECT_EQ(jsonFb.getPropertyValue(PROPERTY_NAME_TOPIC).asPtr().toStdString(), ""); -} diff --git a/mqtt_streaming_module/tests/test_mqtt_raw_fb.cpp b/mqtt_streaming_module/tests/test_mqtt_raw_fb.cpp deleted file mode 100644 index a0ce92b..0000000 --- a/mqtt_streaming_module/tests/test_mqtt_raw_fb.cpp +++ /dev/null @@ -1,392 +0,0 @@ -#include "MqttAsyncClientWrapper.h" -#include "mqtt_streaming_module/mqtt_raw_receiver_fb_impl.h" -#include "test_daq_test_helper.h" -#include -#include -#include -#include -#include -#include -#include - -using namespace daq; -using namespace daq::modules::mqtt_streaming_module; - -namespace daq::modules::mqtt_streaming_module -{ -class MqttRawFbTest : public testing::Test, public DaqTestHelper -{ -public: - std::unique_ptr obj; - - void onSignalsMessage(mqtt::MqttMessage& msg) - { - mqtt::MqttAsyncClient unused; - obj->onSignalsMessage(unused, msg); - } - - void CreateRawFB(std::string topic) - { - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, "")); - const auto fbType = FunctionBlockType(RAW_FB_NAME, RAW_FB_NAME, "", config); - config.setPropertyValue(PROPERTY_NAME_TOPIC, topic); - obj = std::make_unique(NullContext(), nullptr, fbType, nullptr, config); - } - - std::string buildTopicName(const std::string& postfix = "") - { - return std::string("test/topic/") + std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + postfix; - } -}; - -class MqttRawFbPTest : public ::testing::TestWithParam>, - public DaqTestHelper -{ -}; -} // namespace daq::modules::mqtt_streaming_module - -TEST_F(MqttRawFbTest, DefaultRawFbConfig) -{ - StartUp(); - daq::DictPtr fbTypes; - daq::FunctionBlockTypePtr fbt; - daq::PropertyObjectPtr defaultConfig; - ASSERT_NO_THROW(fbTypes = rootMqttFb.getAvailableFunctionBlockTypes()); - ASSERT_NO_THROW(fbt = fbTypes.get(RAW_FB_NAME)); - ASSERT_NO_THROW(defaultConfig = fbt.createDefaultConfig()); - - ASSERT_TRUE(defaultConfig.assigned()); - - ASSERT_EQ(defaultConfig.getAllProperties().getCount(), 2u); - - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_TOPIC)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_TOPIC).getValueType(), CoreType::ctString); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_TOPIC).asPtr().getLength(), 0u); - - ASSERT_TRUE(defaultConfig.hasProperty(PROPERTY_NAME_SUB_QOS)); - ASSERT_EQ(defaultConfig.getProperty(PROPERTY_NAME_SUB_QOS).getValueType(), CoreType::ctInt); - ASSERT_EQ(defaultConfig.getPropertyValue(PROPERTY_NAME_SUB_QOS).asPtr().getValue(DEFAULT_SUB_QOS), DEFAULT_SUB_QOS); -} - -TEST_F(MqttRawFbTest, Creation) -{ - StartUp(); - daq::FunctionBlockPtr rawFb; - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, buildTopicName())); - ASSERT_NO_THROW(rawFb = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config)); - ASSERT_EQ(rawFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); -} - -TEST_F(MqttRawFbTest, CheckRawFbWithEmptyConfig) -{ - StartUp(); - daq::FunctionBlockPtr rawFb; - auto config = PropertyObject(); - ASSERT_NO_THROW(rawFb = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config)); - auto signals = rawFb.getSignals(); - ASSERT_EQ(signals.getCount(), 1u); -} - -TEST_F(MqttRawFbTest, TwoFbCreation) -{ - StartUp(); - { - daq::FunctionBlockPtr fb; - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, buildTopicName("0"))); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config)); - EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - } - { - daq::FunctionBlockPtr fb; - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, buildTopicName("1"))); - ASSERT_NO_THROW(fb = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config)); - EXPECT_EQ(fb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - } - auto fbs = rootMqttFb.getFunctionBlocks(); - ASSERT_EQ(fbs.getCount(), 2u); -} - -TEST_F(MqttRawFbTest, CheckRawFbWithDefaultConfig) -{ - StartUp(); - daq::FunctionBlockPtr rawFb; - ASSERT_NO_THROW(rawFb = rootMqttFb.addFunctionBlock(RAW_FB_NAME)); - auto signals = rawFb.getSignals(); - ASSERT_EQ(signals.getCount(), 1u); -} - -TEST_F(MqttRawFbTest, CheckRawFbWithPartialConfig) -{ - // If FB has only one property, partial config is equivalent to custom config - StartUp(); - daq::FunctionBlockPtr rawFb; - auto config = PropertyObject(); - config.addProperty(StringProperty(PROPERTY_NAME_TOPIC, "")); - ASSERT_NO_THROW(rawFb = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config)); -} - -TEST_F(MqttRawFbTest, CheckRawFbWithCustomConfig) -{ - // If FB has only one property, partial config is equivalent to custom config - StartUp(); - daq::FunctionBlockPtr rawFb; - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_TOPIC, buildTopicName()); - ASSERT_NO_THROW(rawFb = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config)); -} - -TEST_F(MqttRawFbTest, CheckRawFbConfig) -{ - StartUp(); - - const auto topic = buildTopicName(); - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_TOPIC, topic); - daq::FunctionBlockPtr rawFb; - ASSERT_NO_THROW(rawFb = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config)); - - const auto allProperties = rawFb.getAllProperties(); - ASSERT_EQ(allProperties.getCount(), config.getAllProperties().getCount()); - - for (const auto& pror : config.getAllProperties()) - { - const auto propName = pror.getName(); - ASSERT_TRUE(rawFb.hasProperty(propName)); - ASSERT_EQ(rawFb.getPropertyValue(propName), config.getPropertyValue(propName)); - } -} - -TEST_F(MqttRawFbTest, CheckRawFbSubscriptionStatusWaitingForData) -{ - StartUp(); - - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_TOPIC, buildTopicName()); - daq::FunctionBlockPtr rawFb; - ASSERT_NO_THROW(rawFb = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config)); - EXPECT_EQ(rawFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - - EXPECT_EQ(rawFb.getStatusContainer().getStatus("SubscriptionStatus"), - EnumerationWithIntValue(MQTT_FB_SUB_STATUS_TYPE, - static_cast(MqttBaseFb::SubscriptionStatus::WaitingForData), - daqInstance.getContext().getTypeManager())); -} - -TEST_P(MqttRawFbPTest, CheckRawFbTopic) -{ - auto [topic, result] = GetParam(); - StartUp(); - - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_TOPIC, topic); - daq::FunctionBlockPtr rawFb; - ASSERT_NO_THROW(rawFb = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config)); - auto signals = rawFb.getSignals(); - ASSERT_EQ(signals.getCount(), 1); - const auto expectedComponentStatus = result ? "Ok" : "Warning"; - EXPECT_EQ(rawFb.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", expectedComponentStatus, daqInstance.getContext().getTypeManager())); - if (result) - { - EXPECT_NE(rawFb.getStatusContainer().getStatus("SubscriptionStatus"), - EnumerationWithIntValue(MQTT_FB_SUB_STATUS_TYPE, - static_cast(MqttBaseFb::SubscriptionStatus::InvalidTopicName), - daqInstance.getContext().getTypeManager())); - } - else - { - EXPECT_EQ(rawFb.getStatusContainer().getStatus("SubscriptionStatus"), - EnumerationWithIntValue(MQTT_FB_SUB_STATUS_TYPE, - static_cast(MqttBaseFb::SubscriptionStatus::InvalidTopicName), - daqInstance.getContext().getTypeManager())); - } -} - -INSTANTIATE_TEST_SUITE_P(TopicTest, - MqttRawFbPTest, - ::testing::Values(std::make_pair("", false), - std::make_pair("goodTopic/test", true), - std::make_pair("/goodTopic/test0", true), - std::make_pair("badTopic/+/test/topic", false), - std::make_pair("badTopic/+/+/topic", false), - std::make_pair("badTopic/#", false))); - -TEST_F(MqttRawFbTest, CheckRawFbDataTransfer) -{ - const auto topic = buildTopicName(); - const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, - std::vector{0x11, 0x12, 0x13, 0x14}, - std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, - std::vector{0x31}, - std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; - std::vector> dataToReceive; - - CreateRawFB({topic}); - - auto signalList = List(); - obj->getSignals(&signalList); - auto reader = daq::PacketReader(signalList[0]); - - for (const auto& data : dataToSend) - { - mqtt::MqttMessage msg = {topic, data, 1, 0}; - onSignalsMessage(msg); - } - - while (!reader.getEmpty()) - { - auto packet = reader.read(); - if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) - { - continue; - } - if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) - { - std::vector readData(dataPacket.getDataSize()); - memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); - dataToReceive.push_back(std::move(readData)); - } - } - ASSERT_EQ(dataToSend.size(), dataToReceive.size()); - ASSERT_EQ(dataToSend, dataToReceive); -} - -TEST_F(MqttRawFbTest, CheckRawFbFullDataTransfer) -{ - const std::string topic = buildTopicName(); - - StartUp(); - - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - - config.setPropertyValue(PROPERTY_NAME_TOPIC, topic); - auto singal = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config).getSignals()[0]; - auto reader = daq::PacketReader(singal); - - MqttAsyncClientWrapper publisher("testPublisherId"); - ASSERT_TRUE(publisher.connect("127.0.0.1")); - - const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, - std::vector{0x11, 0x12, 0x13, 0x14}, - std::vector{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, - std::vector{0x31}, - std::vector{0x41, 0x42, 0x43, 0x44, 0x45}}; - std::vector> dataToReceive; - - for (const auto& data : dataToSend) - { - mqtt::MqttMessage msg = {topic, data, 1, 0}; - ASSERT_TRUE(publisher.publishMsg(msg)); - } - helper::utils::Timer tmr(3000, true); - while ((!reader.getEmpty() || !tmr.expired()) && dataToReceive.size() != dataToSend.size()) - { - auto packet = reader.read(); - if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) - { - continue; - } - if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) - { - std::vector readData(dataPacket.getDataSize()); - memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); - dataToReceive.push_back(std::move(readData)); - } - } - - ASSERT_EQ(dataToSend.size(), dataToReceive.size()); - ASSERT_EQ(dataToSend, dataToReceive); -} - -TEST_F(MqttRawFbTest, CheckRawFbFullDataTransferWithReconfiguring) -{ - const std::string topic0 = buildTopicName("0"); - const std::string topic1 = buildTopicName("1"); - const auto dataToSend = std::vector>{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}, - std::vector{0x11, 0x12, 0x13, 0x14}}; - std::vector> dataToReceive; - - StartUp(); - - auto config = rootMqttFb.getAvailableFunctionBlockTypes().get(RAW_FB_NAME).createDefaultConfig(); - config.setPropertyValue(PROPERTY_NAME_TOPIC, topic0); - auto rawFB = rootMqttFb.addFunctionBlock(RAW_FB_NAME, config); - auto singal = rawFB.getSignals()[0]; - auto reader = daq::PacketReader(singal); - - const auto stHasData = EnumerationWithIntValue(MQTT_FB_SUB_STATUS_TYPE, - static_cast(MqttBaseFb::SubscriptionStatus::HasData), - daqInstance.getContext().getTypeManager()); - - const auto stWaitData = EnumerationWithIntValue(MQTT_FB_SUB_STATUS_TYPE, - static_cast(MqttBaseFb::SubscriptionStatus::WaitingForData), - daqInstance.getContext().getTypeManager()); - - MqttAsyncClientWrapper publisher("testPublisherId"); - ASSERT_TRUE(publisher.connect("127.0.0.1")); - EXPECT_EQ(rawFB.getStatusContainer().getStatus("SubscriptionStatus"), stWaitData); - - mqtt::MqttMessage msg = {topic0, dataToSend[0], 2, 0}; - ASSERT_TRUE(publisher.publishMsg(msg)); - - auto readerLambda = [&reader, &dataToReceive]() - { - while (!reader.getEmpty()) - { - auto packet = reader.read(); - if (const auto eventPacket = packet.asPtrOrNull(); eventPacket.assigned()) - { - continue; - } - if (const auto dataPacket = packet.asPtrOrNull(); dataPacket.assigned()) - { - std::vector readData(dataPacket.getDataSize()); - memcpy(readData.data(), dataPacket.getData(), dataPacket.getDataSize()); - dataToReceive.push_back(std::move(readData)); - } - } - }; - helper::utils::Timer tmr(1000, true); - - bool hasData = false; - while (tmr.expired() == false && hasData == false) - hasData = rawFB.getStatusContainer().getStatus("SubscriptionStatus") == stHasData; - - EXPECT_TRUE(hasData); - - readerLambda(); - ASSERT_EQ(dataToReceive.size(), 1u); - ASSERT_EQ(dataToSend[0], dataToReceive[0]); - - dataToReceive.clear(); - - ASSERT_NO_THROW(rawFB.setPropertyValue(PROPERTY_NAME_TOPIC, topic1)); - EXPECT_EQ(rawFB.getStatusContainer().getStatus("ComponentStatus"), - Enumeration("ComponentStatusType", "Ok", daqInstance.getContext().getTypeManager())); - EXPECT_EQ(rawFB.getStatusContainer().getStatus("SubscriptionStatus"), stWaitData); - - msg = {topic1, dataToSend[1], 2, 0}; - ASSERT_TRUE(publisher.publishMsg(msg)); - tmr.restart(); - - hasData = false; - while (tmr.expired() == false && hasData == false) - hasData = rawFB.getStatusContainer().getStatus("SubscriptionStatus") == stHasData; - - EXPECT_TRUE(hasData); - - readerLambda(); - ASSERT_EQ(dataToReceive.size(), 1u); - ASSERT_EQ(dataToSend[1], dataToReceive[0]); -} diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt new file mode 100644 index 0000000..6e825c2 --- /dev/null +++ b/shared/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.10) +list(APPEND CMAKE_MESSAGE_CONTEXT shared) + +add_subdirectory(mqtt_streaming_protocol) +add_subdirectory(helper_utils) \ No newline at end of file diff --git a/helper_utils/CMakeLists.txt b/shared/helper_utils/CMakeLists.txt similarity index 100% rename from helper_utils/CMakeLists.txt rename to shared/helper_utils/CMakeLists.txt diff --git a/helper_utils/include/mqtt_streaming_helper/timer.h b/shared/helper_utils/include/mqtt_streaming_helper/timer.h similarity index 100% rename from helper_utils/include/mqtt_streaming_helper/timer.h rename to shared/helper_utils/include/mqtt_streaming_helper/timer.h diff --git a/mqtt_streaming_protocol/CMakeLists.txt b/shared/mqtt_streaming_protocol/CMakeLists.txt similarity index 89% rename from mqtt_streaming_protocol/CMakeLists.txt rename to shared/mqtt_streaming_protocol/CMakeLists.txt index 870c4f3..b03ad05 100644 --- a/mqtt_streaming_protocol/CMakeLists.txt +++ b/shared/mqtt_streaming_protocol/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10) set_cmake_folder_context(TARGET_FOLDER_NAME) -set(MQTT_STREAMING_PROTOCOL_VERSION ${OPENDAQ_PACKAGE_VERSION}) +set(MQTT_STREAMING_PROTOCOL_VERSION ${MQTT_MODULE_VERSION}) set(MQTT_STREAMING_PROTOCOL_PRJ_NAME "OpenDaqMqttStreamingProtocol") message(STATUS "${MQTT_STREAMING_PROTOCOL_PRJ_NAME} version: ${MQTT_STREAMING_PROTOCOL_VERSION}") diff --git a/mqtt_streaming_protocol/include/MqttAsyncClient.h b/shared/mqtt_streaming_protocol/include/MqttAsyncClient.h similarity index 100% rename from mqtt_streaming_protocol/include/MqttAsyncClient.h rename to shared/mqtt_streaming_protocol/include/MqttAsyncClient.h diff --git a/mqtt_streaming_protocol/include/MqttDataWrapper.h b/shared/mqtt_streaming_protocol/include/MqttDataWrapper.h similarity index 74% rename from mqtt_streaming_protocol/include/MqttDataWrapper.h rename to shared/mqtt_streaming_protocol/include/MqttDataWrapper.h index d39239c..54aaef7 100644 --- a/mqtt_streaming_protocol/include/MqttDataWrapper.h +++ b/shared/mqtt_streaming_protocol/include/MqttDataWrapper.h @@ -14,6 +14,26 @@ namespace mqtt { +template +struct container_traits +{ + static constexpr bool is_vector = false; + using value_type = T; +}; + +template +struct container_traits> +{ + static constexpr bool is_vector = true; + using value_type = U; +}; + +template +inline constexpr bool is_std_vector_v = container_traits::is_vector; + +template +using sample_type_t = typename container_traits::value_type; + struct SampleData { double value; @@ -62,6 +82,19 @@ class MqttDataWrapper final msg(msg) { } + + CmdResult addError(const std::string& newmsg) + { + success = false; + msg += newmsg; + return *this; + } + CmdResult merge(const CmdResult& other) + { + success = success && other.success; + msg += other.msg; + return *this; + } }; MqttDataWrapper(daq::LoggerComponentPtr loggerComponent); @@ -90,18 +123,21 @@ class MqttDataWrapper final std::pair> extractDataSamples(const MqttMsgDescriptor& msgDescriptor, const std::string& json); void sendDataSamples(const DataPackets& dataPackets); template - DataPackets buildDataPackets(T value, uint64_t timestamp); + DataPackets buildDataPackets(const T& value, uint64_t timestamp); + template + DataPackets buildDataPackets(const T& value, const std::vector& timestamp); template - DataPackets buildDataPackets(T value); + DataPackets buildDataPackets(const T& value); daq::DataPacketPtr buildDomainDataPacket(daq::GenericSignalConfigPtr<> signalConfig, uint64_t timestamp); + daq::DataPacketPtr buildDomainDataPacket(daq::GenericSignalConfigPtr<> signalConfig, const std::vector& timestamp); template daq::DataPacketPtr buildDataPacket(daq::GenericSignalConfigPtr<> signalConfig, - T value, + const T& value, const daq::DataPacketPtr domainPacket); template daq::DataPacketPtr createEmptyDataPacket(const daq::GenericSignalConfigPtr<> signalConfig, - const daq::DataPacketPtr domainPacket, T value); - template void copyDataIntoPacket(daq::DataPacketPtr dataPacket, T value); + const daq::DataPacketPtr domainPacket, const T& value); + template void copyDataIntoPacket(daq::DataPacketPtr dataPacket, const T& value); daq::UnitPtr extractSignalUnit(const rapidjson::Value& signalObj); std::string extractValueFieldName(const rapidjson::Value& signalObj); std::string extractTimestampFieldName(const rapidjson::Value& signalObj); diff --git a/mqtt_streaming_protocol/include/MqttMessage.h b/shared/mqtt_streaming_protocol/include/MqttMessage.h similarity index 100% rename from mqtt_streaming_protocol/include/MqttMessage.h rename to shared/mqtt_streaming_protocol/include/MqttMessage.h diff --git a/mqtt_streaming_protocol/include/MqttSettings.h b/shared/mqtt_streaming_protocol/include/MqttSettings.h similarity index 100% rename from mqtt_streaming_protocol/include/MqttSettings.h rename to shared/mqtt_streaming_protocol/include/MqttSettings.h diff --git a/mqtt_streaming_protocol/include/timestampConverter.h b/shared/mqtt_streaming_protocol/include/timestampConverter.h similarity index 100% rename from mqtt_streaming_protocol/include/timestampConverter.h rename to shared/mqtt_streaming_protocol/include/timestampConverter.h diff --git a/mqtt_streaming_protocol/src/CMakeLists.txt b/shared/mqtt_streaming_protocol/src/CMakeLists.txt similarity index 100% rename from mqtt_streaming_protocol/src/CMakeLists.txt rename to shared/mqtt_streaming_protocol/src/CMakeLists.txt diff --git a/mqtt_streaming_protocol/src/MqttAsyncClient.cpp b/shared/mqtt_streaming_protocol/src/MqttAsyncClient.cpp similarity index 100% rename from mqtt_streaming_protocol/src/MqttAsyncClient.cpp rename to shared/mqtt_streaming_protocol/src/MqttAsyncClient.cpp diff --git a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp b/shared/mqtt_streaming_protocol/src/MqttDataWrapper.cpp similarity index 51% rename from mqtt_streaming_protocol/src/MqttDataWrapper.cpp rename to shared/mqtt_streaming_protocol/src/MqttDataWrapper.cpp index 7e940c2..5701dd4 100644 --- a/mqtt_streaming_protocol/src/MqttDataWrapper.cpp +++ b/shared/mqtt_streaming_protocol/src/MqttDataWrapper.cpp @@ -1,6 +1,5 @@ #include "MqttDataWrapper.h" -#include "rapidjson/writer.h" #include #include #include @@ -14,10 +13,101 @@ #include #include -namespace mqtt +namespace{ + +template +std::vector parseHomogeneousArrayInternal( + const rapidjson::Value::ConstArray& arr, + IsFn isValid, + GetFn getValue) { + std::vector out; + out.reserve(arr.Size()); + + for (const auto& x : arr) + { + if (!isValid(x)) + return std::vector{}; + + out.push_back(getValue(x)); + } + return out; +} -static const char* TOPIC_ALL_SIGNALS_PREFIX = "openDAQ"; +template +std::pair> parseHomogeneousArray( + const rapidjson::Value::ConstArray& arr) +{ + return std::pair{mqtt::MqttDataWrapper::CmdResult{false, {}}, std::vector{}}; +} + +template <> +std::pair> parseHomogeneousArray( + const rapidjson::Value::ConstArray& arr) +{ + std::pair> result{{true, {}}, {}}; + result.second = parseHomogeneousArrayInternal( + arr, + [](const auto& x) { return x.IsInt64() || x.IsUint64(); }, + [](const auto& x) { return x.GetInt64(); }); + if (result.second.empty()) + { + result.first.addError("Mixed types in value array (expected integers). "); + } + return result; +} + +template <> +std::pair> parseHomogeneousArray( + const rapidjson::Value::ConstArray& arr) +{ + std::pair> result{{true, {}}, {}}; + result.second = parseHomogeneousArrayInternal( + arr, + [](const auto& x) { return x.IsUint64(); }, + [](const auto& x) { return x.GetUint64(); }); + if (result.second.empty()) + { + result.first.addError("Mixed types in value array (expected unsigned integers). "); + } + return result; +} + +template <> +std::pair> parseHomogeneousArray( + const rapidjson::Value::ConstArray& arr) +{ + std::pair> result{{true, {}}, {}}; + result.second = parseHomogeneousArrayInternal( + arr, + [](const auto& x) { return x.IsDouble(); }, + [](const auto& x) { return x.GetDouble(); }); + if (result.second.empty()) + { + result.first.addError("Mixed types in value array (expected doubles). "); + } + return result; +} + +template <> +std::pair> parseHomogeneousArray( + const rapidjson::Value::ConstArray& arr) +{ + std::pair> result{{true, {}}, {}}; + result.second = parseHomogeneousArrayInternal( + arr, + [](const auto& x) { return x.IsString(); }, + [](const auto& x) { return std::string{x.GetString()}; }); + if (result.second.empty()) + { + result.first.addError("Mixed types in value array (expected strings). "); + } + return result; +} +} + +namespace mqtt +{ MqttDataWrapper::MqttDataWrapper(daq::LoggerComponentPtr loggerComponent) : loggerComponent(loggerComponent) @@ -183,11 +273,6 @@ MqttDataWrapper::CmdResult MqttDataWrapper::createAndSendDataPacket(const std::s return status; } -// bool MqttDataWrapper::hasDomainSignal(const SignalId& signalId) const -// { -// return (msgDescriptor.signalName == signalId.signalName) ? !msgDescriptor.tsFieldName.empty() : false; -// } - void MqttDataWrapper::setValueFieldName(std::string valueFieldName) { msgDescriptor.valueFieldName = std::move(valueFieldName); @@ -198,22 +283,24 @@ void MqttDataWrapper::setTimestampFieldName(std::string tsFieldName) msgDescriptor.tsFieldName = std::move(tsFieldName); } -std::pair> MqttDataWrapper::extractDataSamples(const MqttMsgDescriptor& msgDescriptor, const std::string& json) +std::pair> MqttDataWrapper::extractDataSamples(const MqttMsgDescriptor& msgDescriptor, + const std::string& json) { - using ValueVariant = std::variant; + using ValueVariant = std::variant, std::vector, std::vector>; ValueVariant value{}; std::vector outputData; - uint64_t ts = 0; + std::vector ts; bool hasTS = false; bool hasValue = false; CmdResult result(true); + try { rapidjson::Document jsonDocument; jsonDocument.Parse(json); if (jsonDocument.HasParseError()) { - return std::pair{CmdResult(false, "Error parsing mqtt payload as JSON"), outputData}; + return std::pair{result.addError("Error parsing mqtt payload as JSON"), outputData}; } if (jsonDocument.IsObject()) @@ -224,39 +311,115 @@ std::pair> MqttDataWrapper: if (!msgDescriptor.valueFieldName.empty() && name == msgDescriptor.valueFieldName) { const auto& v = jsonDocument[name]; - hasValue = true; - if (v.IsInt64()) - value = v.GetInt64(); - else if (v.IsUint64()) - value = static_cast(v.GetUint64()); - else if (v.IsDouble()) - value = v.GetDouble(); - else if (v.IsString()) - value = std::string(v.GetString()); + if (v.IsArray()) + { + const auto& arr = v.GetArray(); + if (arr.Empty()) + { + result.addError("Value field is an array but it is empty. "); + } + else if (arr[0].IsInt64() || arr[0].IsUint64()) + { + auto [parsingStatus, out] = parseHomogeneousArray(arr); + result.merge(parsingStatus); + hasValue = parsingStatus.success; + if (parsingStatus.success) + value = std::move(out); + } + else if (arr[0].IsDouble()) + { + auto [parsingStatus, out] = parseHomogeneousArray(arr); + result.merge(parsingStatus); + hasValue = parsingStatus.success; + if (parsingStatus.success) + value = std::move(out); + } + else if (arr[0].IsString()) + { + auto [parsingStatus, out] = parseHomogeneousArray(arr); + result.merge(parsingStatus); + hasValue = parsingStatus.success; + if (parsingStatus.success) + value = std::move(out); + } + else + { + result.addError(fmt::format("Unsupported value type for '{}' array. ", name)); + } + } else { - result.success = false; - result.msg = fmt::format("Unsupported value type for '{}'.", name); - hasValue = false; + hasValue = true; + if (v.IsInt64()) + value = std::vector{v.GetInt64()}; + else if (v.IsUint64()) + value = std::vector{static_cast(v.GetUint64())}; + else if (v.IsDouble()) + value = std::vector{v.GetDouble()}; + else if (v.IsString()) + value = std::vector{std::string(v.GetString())}; + else + { + result.addError(fmt::format("Unsupported value type for '{}'. ", name)); + hasValue = false; + } } } else if (!msgDescriptor.tsFieldName.empty() && name == msgDescriptor.tsFieldName) { - if (jsonDocument[name].IsInt() || jsonDocument[name].IsUint64() || - jsonDocument[name].IsInt64()) + const auto& tsField = jsonDocument[name]; + if (tsField.IsArray()) { - ts = mqtt::utils::numericToMicroseconds(jsonDocument[name].GetUint64()); - hasTS = true; - } - else if (jsonDocument[name].IsString()) - { - ts = utils::toUnixTicks(jsonDocument[name].GetString()); - hasTS = true; + const auto& arr = tsField.GetArray(); + if (arr.Empty()) + { + result.addError("Timestamp field is an array but it is empty. "); + } + else if (arr[0].IsInt() || arr[0].IsUint64() || arr[0].IsInt64()) + { + auto [parsingStatus, out] = parseHomogeneousArray(arr); + result.merge(parsingStatus); + hasTS = parsingStatus.success; + if (parsingStatus.success) + ts = std::move(out); + + std::for_each(ts.begin(), ts.end(), [](auto& val) { val = mqtt::utils::numericToMicroseconds(val); }); + } + else if (arr[0].IsString()) + { + std::vector stringTs; + auto [parsingStatus, out] = parseHomogeneousArray(arr); + result.merge(parsingStatus); + hasTS = parsingStatus.success; + if (parsingStatus.success) + stringTs = std::move(out); + ts.reserve(stringTs.size()); + std::for_each(stringTs.cbegin(), + stringTs.cend(), + [&ts](const auto& val) { ts.push_back(utils::toUnixTicks(val)); }); + hasTS = true; + } + else + { + result.addError("Timestamp value type is not supported. "); + } } else { - result.success = false; - result.msg = "Timestamp value type is not supported."; + if (tsField.IsInt() || tsField.IsUint64() || tsField.IsInt64()) + { + ts.push_back(mqtt::utils::numericToMicroseconds(tsField.GetUint64())); + hasTS = true; + } + else if (tsField.IsString()) + { + ts.push_back(utils::toUnixTicks(tsField.GetString())); + hasTS = true; + } + else + { + result.addError("Timestamp value type is not supported. "); + } } } else @@ -268,31 +431,47 @@ std::pair> MqttDataWrapper: } catch (...) { - result.success = false; - result.msg = "Error deserializing MQTT payload"; + result.addError("Error deserializing MQTT payload. "); } if (!hasValue || (!msgDescriptor.tsFieldName.empty() && !hasTS)) { - result.success = false; - result.msg = "Not all required fields are present in the JSON message."; + result.addError("Not all required fields are present in the JSON message. "); + if (!hasValue) + result.addError(fmt::format("Couldn't extract value field (\"{}\") from the JSON message. ", msgDescriptor.valueFieldName)); + if (!msgDescriptor.tsFieldName.empty() && !hasTS) + result.addError(fmt::format("Couldn't extract timestamp field (\"{}\") from the JSON message. ", msgDescriptor.tsFieldName)); } if (result.success) { - // TODO : value [1, 2, 3, ...] support - DataPackets dataPackets; std::visit( - [&](auto&& val) + [&](auto&& values) { - using T = std::decay_t; - if (hasTS) - dataPackets = buildDataPackets(val, ts); + using T = std::decay_t; + if (hasTS && ts.size() != values.size()) + { + result.addError("Timestamp and value array sizes do not match. "); + return; + } + if constexpr (std::is_same_v>) + { + for (size_t i = 0; i < values.size(); ++i) + { + DataPackets dp = hasTS ? buildDataPackets(values[i], ts[i]) : buildDataPackets(values[i]); + + if (dp.dataPacket.assigned()) + outputData.push_back(std::move(dp)); + } + } else - dataPackets = buildDataPackets(val); + { + DataPackets dp = hasTS ? buildDataPackets(values, ts) : buildDataPackets(values); + + if (dp.dataPacket.assigned()) + outputData.push_back(std::move(dp)); + } }, value); - if (dataPackets.dataPacket.assigned()) - outputData.push_back(std::move(dataPackets)); } return std::pair{result, outputData}; } @@ -308,7 +487,7 @@ void MqttDataWrapper::sendDataSamples(const DataPackets& dataPackets) template DataPackets -MqttDataWrapper::buildDataPackets(T value, uint64_t timestamp) +MqttDataWrapper::buildDataPackets(const T& value, uint64_t timestamp) { DataPackets dataPackets; @@ -320,7 +499,19 @@ MqttDataWrapper::buildDataPackets(T value, uint64_t timestamp) template DataPackets -MqttDataWrapper::buildDataPackets(T value) +MqttDataWrapper::buildDataPackets(const T& value, const std::vector& timestamp) +{ + DataPackets dataPackets; + + dataPackets.domainDataPacket = buildDomainDataPacket(outputSignal, timestamp); + dataPackets.dataPacket = buildDataPacket(outputSignal, value, dataPackets.domainDataPacket); + + return dataPackets; +} + +template +DataPackets +MqttDataWrapper::buildDataPackets(const T& value) { DataPackets dataPackets; dataPackets.dataPacket = buildDataPacket(outputSignal, value, daq::DataPacketPtr()); @@ -372,62 +563,70 @@ bool MqttDataWrapper::isTypeTheSame(daq::SampleType sampleType) return false; } -template -daq::DataPacketPtr MqttDataWrapper::buildDataPacket(daq::GenericSignalConfigPtr<> signalConfig, T value, const daq::DataPacketPtr domainPacket) +template +daq::DataPacketPtr +MqttDataWrapper::buildDataPacket(daq::GenericSignalConfigPtr<> signalConfig, const T& value, const daq::DataPacketPtr domainPacket) { const auto curType = signalConfig.getDescriptor().getSampleType(); - if (isTypeTheSame(curType) == false) + using ActualType = sample_type_t; + if (!isTypeTheSame(curType)) { - if constexpr (std::is_same_v) - { - // because daq::SampleType::String is not implemented properly, we use Binary type for string data - // daq::SampleType::BinaryData != daq::SampleType::String - auto descriptor = DataDescriptorBuilderCopy(signalConfig.getDescriptor()).setSampleType(daq::SampleType::Binary).build(); - signalConfig.setDescriptor(descriptor); - } - else - { - auto descriptor = DataDescriptorBuilderCopy(signalConfig.getDescriptor()).setSampleType(daq::SampleTypeFromType::SampleType).build(); - signalConfig.setDescriptor(descriptor); - } + auto descriptor = + DataDescriptorBuilderCopy(signalConfig.getDescriptor()).setSampleType(daq::SampleTypeFromType::SampleType).build(); + signalConfig.setDescriptor(descriptor); } - daq::DataPacketPtr dataPacket = createEmptyDataPacket(signalConfig, domainPacket, value); + + auto dataPacket = createEmptyDataPacket(signalConfig, domainPacket, value); copyDataIntoPacket(dataPacket, value); return dataPacket; } template -daq::DataPacketPtr MqttDataWrapper::createEmptyDataPacket(const daq::GenericSignalConfigPtr<> signalConfig, const daq::DataPacketPtr domainPacket, T value) +daq::DataPacketPtr MqttDataWrapper::createEmptyDataPacket(const daq::GenericSignalConfigPtr<> signalConfig, const daq::DataPacketPtr domainPacket, const T& value) { daq::DataPacketPtr dataPacket; - if constexpr (std::is_same_v) + uint64_t size = 1; + using ActualType = sample_type_t; + if constexpr (is_std_vector_v || std::is_same_v) { - dataPacket = daq::BinaryDataPacket(domainPacket, signalConfig.getDescriptor(), value.size()); + size = value.size(); + } + + if constexpr (std::is_same_v) + { + dataPacket = daq::BinaryDataPacket(domainPacket, signalConfig.getDescriptor(), size); } else { if (signalConfig.getDomainSignal().assigned() && domainPacket.assigned()) { - dataPacket = DataPacketWithDomain(domainPacket, signalConfig.getDescriptor(), 1); + dataPacket = DataPacketWithDomain(domainPacket, signalConfig.getDescriptor(), size); } else { - dataPacket = DataPacket(signalConfig.getDescriptor(), 1); + dataPacket = DataPacket(signalConfig.getDescriptor(), size); } } + return dataPacket; } template -void MqttDataWrapper::copyDataIntoPacket(daq::DataPacketPtr dataPacket, T value) +void MqttDataWrapper::copyDataIntoPacket(daq::DataPacketPtr dataPacket, const T& value) { if (!dataPacket.assigned()) return; - if constexpr (std::is_same_v) + + using ActualType = sample_type_t; + if constexpr (is_std_vector_v && !std::is_same_v) { - memcpy(dataPacket.getData(), value.c_str(), value.size()); + std::memcpy(dataPacket.getRawData(), value.data(), value.size() * sizeof(ActualType)); } - else + else if constexpr (!is_std_vector_v && std::is_same_v) + { + std::memcpy(dataPacket.getData(), value.c_str(), value.size()); + } + else if constexpr (!is_std_vector_v && !std::is_same_v) { auto outputData = reinterpret_cast(dataPacket.getRawData()); *outputData = value; @@ -446,6 +645,18 @@ daq::DataPacketPtr MqttDataWrapper::buildDomainDataPacket(daq::GenericSignalConf return dataPacket; } +daq::DataPacketPtr MqttDataWrapper::buildDomainDataPacket(daq::GenericSignalConfigPtr<> signalConfig, const std::vector& timestamp) +{ + daq::DataPacketPtr dataPacket; + if (signalConfig.getDomainSignal().assigned()) + { + dataPacket = daq::DataPacket(signalConfig.getDomainSignal().getDescriptor(), timestamp.size()); + std::memcpy(dataPacket.getRawData(), timestamp.data(), timestamp.size() * sizeof(uint64_t)); + } + + return dataPacket; +} + daq::UnitPtr MqttDataWrapper::extractSignalUnit(const rapidjson::Value& signalObj) { daq::UnitPtr unit; diff --git a/mqtt_streaming_protocol/tests/CMakeLists.txt b/shared/mqtt_streaming_protocol/tests/CMakeLists.txt similarity index 100% rename from mqtt_streaming_protocol/tests/CMakeLists.txt rename to shared/mqtt_streaming_protocol/tests/CMakeLists.txt diff --git a/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp b/shared/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp similarity index 100% rename from mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp rename to shared/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.cpp diff --git a/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.h b/shared/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.h similarity index 100% rename from mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.h rename to shared/mqtt_streaming_protocol/tests/MqttAsyncClientWrapper.h diff --git a/mqtt_streaming_protocol/tests/test_app.cpp b/shared/mqtt_streaming_protocol/tests/test_app.cpp similarity index 100% rename from mqtt_streaming_protocol/tests/test_app.cpp rename to shared/mqtt_streaming_protocol/tests/test_app.cpp diff --git a/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp b/shared/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp similarity index 100% rename from mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp rename to shared/mqtt_streaming_protocol/tests/test_mqtt_streaming_protocol.cpp